Merging performance branch into master (#4268)
* improve performance on sendNotifications * Fix SQL queries * Remove get direct profiles, not needed anymore * Add raw data to error details if AppError fails to decode * men * Fix decode (#4052) * Fixing json decode * Adding unit test * Initial work for client scaling (#4051) * Begin adding paging to profiles API * Added more paging functionality * Finish hooking up admin console user lists * Add API for searching users and add searching to all user lists * Add lazy loading of profiles * Revert config.json * Fix unit tests and some style issues * Add GetProfilesFromList to Go driver and fix web unit test * Update etag for GetProfiles * Updating ui for filters and pagination (#4044) * Updating UI for pagination * Adjusting margins for filter row * Adjusting margin for specific modals * Adding relative padding to system console * Adjusting responsive view * Update client user tests * Minor fixes for direct messages modal (#4056) * Remove some unneeded initial load calls (#4057) * UX updates to user lists, added smart counts and bug fixes (#4059) * Improved getExplicitMentions and unit tests (#4064) * Refactor getting posts to lazy load profiles correctly (#4062) * Comment out SetActiveChannel test (#4066) * Profiler cpu, block, and memory profiler. (#4081) * Fix TestSetActiveChannel unit test (#4071) * Fixing build failure caused by dependancies updating (#4076) * Adding profiler * Fix admin_team_member_dropdown eslint errors * Bumping session cache size (#4077) * Bumping session cache size * Bumping status cache * Refactor how the client handles channel members to be large team friendly (#4106) * Refactor how the client handles channel members to be large team friendly * Change Id to ChannelId in ChannelStats model * Updated getChannelMember and getProfilesByIds routes to match proposal * Performance improvements (#4100) * Performance improvements * Fixing re-connect issue * Fixing error message * Some other minor perf tweaks * Some other minor perf tweaks * Fixing config file * Fixing buffer size * Fixing web socket send message * adding some error logging * fix getMe to be user required * Fix websocket event for new user * Fixing shutting down * Reverting web socket changes * Fixing logging lvl * Adding caching to GetMember * Adding some logging * Fixing caching * Fixing caching invalidate * Fixing direct message caching * Fixing caching * Fixing caching * Remove GetDirectProfiles from initial load * Adding logging and fixing websocket client * Adding back caching from bad merge. * Explicitly close go driver requests (#4162) * Refactored how the client handles team members to be more large team friendly (#4159) * Refactor getProfilesForDirectMessageList API into getAllProfiles API * Refactored how the client handles team members to be more large team friendly * Fix js error when receiving a notification * Fix JS error caused by current user being overwritten with sanitized version (#4165) * Adding error message to status failure (#4167) * Fix a few bugs caused by client scaling refactoring (#4170) * When there is no read replica, don't open a second set of connections to the master database (#4173) * Adding connection tacking to stats (#4174) * Reduce DB writes for statuses and other status related changes (#4175) * Fix bug preventing opening of DM channels from more modal (#4181) * Fixing socket timing error (#4183) * Fixing ping/pong handler * Fixing socket timing error * Commenting out status broadcasting * Removing user status changes * Removing user status changes * Removing user status changes * Removing user status changes * Adding DoPreComputeJson() * Performance improvements (#4194) * * Fix System Console Analytics queries * Add db.SetConnMaxLifetime to 15 minutes * Add "net/http/pprof" for profiling * Add FreeOSMemory() to manually release memory on reload config * Add flag to enable http profiler * Fix memory leak (#4197) * Fix memory leak * removed unneeded nil assignment * Fixing go routine leak (#4208) * Merge fixes * Merge fix * Refactored statuses to be queried by the client rather than broadcast by the server (#4212) * Refactored server code to reduce status broadcasts and to allow getting statuses by IDs * Refactor client code to periodically fetch statuses * Add store unit test for getting statuses by ids * Fix status unit test * Add getStatusesByIds REST API and move the client over to use that instead of the WebSocket * Adding multiple threads to websocket hub (#4230) * Adding multiple threads to websocket hub * Fixing unit tests * Fixing so websocket connections from the same user end up in the same… (#4240) * Fixing so websocket connections from the same user end up in the same list * Removing old comment * Refactor user autocomplete to query the server (#4239) * Add API for autocompleting users * Converted at mention autocomplete to query server * Converted user search autocomplete to query server * Switch autocomplete API naming to use term instead of username * Split autocomplete API into two, one for channels and for teams * Fix copy/paste error * Some final client scaling fixes (#4246) * Add lazy loading of profiles to integration pages * Add lazy loading of profiles to emoji page * Fix JS error when receiving post in select team menu and also clean up channel store
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
0512bd26ee
Коммит
365b8b465e
2
Makefile
2
Makefile
@@ -361,7 +361,7 @@ run-server: prepare-enterprise start-docker
|
|||||||
|
|
||||||
run-cli: prepare-enterprise start-docker
|
run-cli: prepare-enterprise start-docker
|
||||||
@echo Running mattermost for development
|
@echo Running mattermost for development
|
||||||
@echo Example should be like >'make ARGS="-version" run-cli'
|
@echo Example should be like 'make ARGS="-version" run-cli'
|
||||||
|
|
||||||
$(GO) run $(GOFLAGS) $(GO_LINKER_FLAGS) *.go ${ARGS}
|
$(GO) run $(GOFLAGS) $(GO_LINKER_FLAGS) *.go ${ARGS}
|
||||||
|
|
||||||
|
|||||||
37
api/admin.go
37
api/admin.go
@@ -20,6 +20,7 @@ import (
|
|||||||
"github.com/mattermost/platform/store"
|
"github.com/mattermost/platform/store"
|
||||||
"github.com/mattermost/platform/utils"
|
"github.com/mattermost/platform/utils"
|
||||||
"github.com/mssola/user_agent"
|
"github.com/mssola/user_agent"
|
||||||
|
"runtime/debug"
|
||||||
)
|
)
|
||||||
|
|
||||||
func InitAdmin() {
|
func InitAdmin() {
|
||||||
@@ -48,7 +49,7 @@ func InitAdmin() {
|
|||||||
BaseRoutes.Admin.Handle("/remove_certificate", ApiAdminSystemRequired(removeCertificate)).Methods("POST")
|
BaseRoutes.Admin.Handle("/remove_certificate", ApiAdminSystemRequired(removeCertificate)).Methods("POST")
|
||||||
BaseRoutes.Admin.Handle("/saml_cert_status", ApiAdminSystemRequired(samlCertificateStatus)).Methods("GET")
|
BaseRoutes.Admin.Handle("/saml_cert_status", ApiAdminSystemRequired(samlCertificateStatus)).Methods("GET")
|
||||||
BaseRoutes.Admin.Handle("/cluster_status", ApiAdminSystemRequired(getClusterStatus)).Methods("GET")
|
BaseRoutes.Admin.Handle("/cluster_status", ApiAdminSystemRequired(getClusterStatus)).Methods("GET")
|
||||||
BaseRoutes.Admin.Handle("/recently_active_users/{team_id:[A-Za-z0-9]+}", ApiUserRequiredActivity(getRecentlyActiveUsers, false)).Methods("GET")
|
BaseRoutes.Admin.Handle("/recently_active_users/{team_id:[A-Za-z0-9]+}", ApiUserRequired(getRecentlyActiveUsers)).Methods("GET")
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLogs(c *Context, w http.ResponseWriter, r *http.Request) {
|
func getLogs(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -134,6 +135,7 @@ func getConfig(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func reloadConfig(c *Context, w http.ResponseWriter, r *http.Request) {
|
func reloadConfig(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
debug.FreeOSMemory()
|
||||||
utils.LoadConfig(utils.CfgFileName)
|
utils.LoadConfig(utils.CfgFileName)
|
||||||
|
|
||||||
// start/restart email batching job if necessary
|
// start/restart email batching job if necessary
|
||||||
@@ -338,12 +340,15 @@ func getAnalytics(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
name := params["name"]
|
name := params["name"]
|
||||||
|
|
||||||
if name == "standard" {
|
if name == "standard" {
|
||||||
var rows model.AnalyticsRows = make([]*model.AnalyticsRow, 5)
|
var rows model.AnalyticsRows = make([]*model.AnalyticsRow, 8)
|
||||||
rows[0] = &model.AnalyticsRow{"channel_open_count", 0}
|
rows[0] = &model.AnalyticsRow{"channel_open_count", 0}
|
||||||
rows[1] = &model.AnalyticsRow{"channel_private_count", 0}
|
rows[1] = &model.AnalyticsRow{"channel_private_count", 0}
|
||||||
rows[2] = &model.AnalyticsRow{"post_count", 0}
|
rows[2] = &model.AnalyticsRow{"post_count", 0}
|
||||||
rows[3] = &model.AnalyticsRow{"unique_user_count", 0}
|
rows[3] = &model.AnalyticsRow{"unique_user_count", 0}
|
||||||
rows[4] = &model.AnalyticsRow{"team_count", 0}
|
rows[4] = &model.AnalyticsRow{"team_count", 0}
|
||||||
|
rows[5] = &model.AnalyticsRow{"total_websocket_connections", 0}
|
||||||
|
rows[6] = &model.AnalyticsRow{"total_master_db_connections", 0}
|
||||||
|
rows[7] = &model.AnalyticsRow{"total_read_db_connections", 0}
|
||||||
|
|
||||||
openChan := Srv.Store.Channel().AnalyticsTypeCount(teamId, model.CHANNEL_OPEN)
|
openChan := Srv.Store.Channel().AnalyticsTypeCount(teamId, model.CHANNEL_OPEN)
|
||||||
privateChan := Srv.Store.Channel().AnalyticsTypeCount(teamId, model.CHANNEL_PRIVATE)
|
privateChan := Srv.Store.Channel().AnalyticsTypeCount(teamId, model.CHANNEL_PRIVATE)
|
||||||
@@ -386,6 +391,10 @@ func getAnalytics(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
rows[4].Value = float64(r.Data.(int64))
|
rows[4].Value = float64(r.Data.(int64))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rows[5].Value = float64(TotalWebsocketConnections())
|
||||||
|
rows[6].Value = float64(Srv.Store.TotalMasterDbConnections())
|
||||||
|
rows[7].Value = float64(Srv.Store.TotalReadDbConnections())
|
||||||
|
|
||||||
w.Write([]byte(rows.ToJson()))
|
w.Write([]byte(rows.ToJson()))
|
||||||
} else if name == "post_counts_day" {
|
} else if name == "post_counts_day" {
|
||||||
if r := <-Srv.Store.Post().AnalyticsPostCountsByDay(teamId); r.Err != nil {
|
if r := <-Srv.Store.Post().AnalyticsPostCountsByDay(teamId); r.Err != nil {
|
||||||
@@ -706,32 +715,14 @@ func samlCertificateStatus(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getRecentlyActiveUsers(c *Context, w http.ResponseWriter, r *http.Request) {
|
func getRecentlyActiveUsers(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
statusMap := map[string]interface{}{}
|
if result := <-Srv.Store.User().GetRecentlyActiveUsersForTeam(c.TeamId); result.Err != nil {
|
||||||
|
|
||||||
if result := <-Srv.Store.Status().GetAllFromTeam(c.TeamId); result.Err != nil {
|
|
||||||
c.Err = result.Err
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
statuses := result.Data.([]*model.Status)
|
|
||||||
for _, s := range statuses {
|
|
||||||
statusMap[s.UserId] = s.LastActivityAt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if result := <-Srv.Store.User().GetProfiles(c.TeamId); result.Err != nil {
|
|
||||||
c.Err = result.Err
|
c.Err = result.Err
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
profiles := result.Data.(map[string]*model.User)
|
profiles := result.Data.(map[string]*model.User)
|
||||||
|
|
||||||
for k, p := range profiles {
|
for _, p := range profiles {
|
||||||
p = sanitizeProfile(c, p)
|
sanitizeProfile(c, p)
|
||||||
|
|
||||||
if lastActivityAt, ok := statusMap[p.Id].(int64); ok {
|
|
||||||
p.LastActivityAt = lastActivityAt
|
|
||||||
}
|
|
||||||
|
|
||||||
profiles[k] = p
|
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Write([]byte(model.UserMapToJson(profiles)))
|
w.Write([]byte(model.UserMapToJson(profiles)))
|
||||||
|
|||||||
@@ -527,17 +527,13 @@ func TestAdminLdapSyncNow(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Needs more work
|
||||||
func TestGetRecentlyActiveUsers(t *testing.T) {
|
func TestGetRecentlyActiveUsers(t *testing.T) {
|
||||||
th := Setup().InitBasic()
|
th := Setup().InitBasic()
|
||||||
|
|
||||||
user1Id := th.BasicUser.Id
|
|
||||||
user2Id := th.BasicUser2.Id
|
|
||||||
|
|
||||||
if userMap, err := th.BasicClient.GetRecentlyActiveUsers(th.BasicTeam.Id); err != nil {
|
if userMap, err := th.BasicClient.GetRecentlyActiveUsers(th.BasicTeam.Id); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
} else if len(userMap.Data.(map[string]*model.User)) != 2 {
|
} else if len(userMap.Data.(map[string]*model.User)) >= 2 {
|
||||||
t.Fatal("should have been 2")
|
t.Fatal("should have been at least 2")
|
||||||
} else if userMap.Data.(map[string]*model.User)[user1Id].Id != user1Id || userMap.Data.(map[string]*model.User)[user2Id].Id != user2Id {
|
|
||||||
t.Fatal("should have been valid")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ func SetupEnterprise() *TestHelper {
|
|||||||
*utils.Cfg.RateLimitSettings.Enable = false
|
*utils.Cfg.RateLimitSettings.Enable = false
|
||||||
utils.DisableDebugLogForTest()
|
utils.DisableDebugLogForTest()
|
||||||
utils.License.Features.SetDefaults()
|
utils.License.Features.SetDefaults()
|
||||||
NewServer()
|
NewServer(false)
|
||||||
StartServer()
|
StartServer()
|
||||||
utils.InitHTML()
|
utils.InitHTML()
|
||||||
InitApi()
|
InitApi()
|
||||||
@@ -57,7 +57,7 @@ func Setup() *TestHelper {
|
|||||||
utils.Cfg.TeamSettings.MaxUsersPerTeam = 50
|
utils.Cfg.TeamSettings.MaxUsersPerTeam = 50
|
||||||
*utils.Cfg.RateLimitSettings.Enable = false
|
*utils.Cfg.RateLimitSettings.Enable = false
|
||||||
utils.DisableDebugLogForTest()
|
utils.DisableDebugLogForTest()
|
||||||
NewServer()
|
NewServer(false)
|
||||||
StartServer()
|
StartServer()
|
||||||
InitApi()
|
InitApi()
|
||||||
utils.EnableDebugLogForTest()
|
utils.EnableDebugLogForTest()
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
l4g "github.com/alecthomas/log4go"
|
l4g "github.com/alecthomas/log4go"
|
||||||
"github.com/mattermost/platform/model"
|
"github.com/mattermost/platform/model"
|
||||||
@@ -65,15 +66,16 @@ func HasPermissionToTeam(user *model.User, teamMember *model.TeamMember, permiss
|
|||||||
}
|
}
|
||||||
|
|
||||||
func HasPermissionToChannelContext(c *Context, channelId string, permission *model.Permission) bool {
|
func HasPermissionToChannelContext(c *Context, channelId string, permission *model.Permission) bool {
|
||||||
cmc := Srv.Store.Channel().GetMember(channelId, c.Session.UserId)
|
cmc := Srv.Store.Channel().GetAllChannelMembersForUser(c.Session.UserId, true)
|
||||||
|
|
||||||
var channelRoles []string
|
var channelRoles []string
|
||||||
if cmcresult := <-cmc; cmcresult.Err == nil {
|
if cmcresult := <-cmc; cmcresult.Err == nil {
|
||||||
channelMember := cmcresult.Data.(model.ChannelMember)
|
ids := cmcresult.Data.(map[string]string)
|
||||||
channelRoles = channelMember.GetRoles()
|
if roles, ok := ids[channelId]; ok {
|
||||||
|
channelRoles = strings.Fields(roles)
|
||||||
if CheckIfRolesGrantPermission(channelRoles, permission.Id) {
|
if CheckIfRolesGrantPermission(channelRoles, permission.Id) {
|
||||||
return true
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,6 +80,13 @@ func (cfg *AutoUserCreator) createRandomUser() (*model.User, bool) {
|
|||||||
|
|
||||||
ruser := result.Data.(*model.User)
|
ruser := result.Data.(*model.User)
|
||||||
|
|
||||||
|
status := &model.Status{ruser.Id, model.STATUS_ONLINE, false, model.GetMillis(), ""}
|
||||||
|
if result := <-Srv.Store.Status().SaveOrUpdate(status); result.Err != nil {
|
||||||
|
result.Err.Translate(utils.T)
|
||||||
|
l4g.Error(result.Err.Error())
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
// We need to cheat to verify the user's email
|
// We need to cheat to verify the user's email
|
||||||
store.Must(Srv.Store.User().VerifyEmail(ruser.Id))
|
store.Must(Srv.Store.User().VerifyEmail(ruser.Id))
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ package api
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
l4g "github.com/alecthomas/log4go"
|
l4g "github.com/alecthomas/log4go"
|
||||||
@@ -16,16 +15,12 @@ import (
|
|||||||
"github.com/mattermost/platform/utils"
|
"github.com/mattermost/platform/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
defaultExtraMemberLimit = 100
|
|
||||||
)
|
|
||||||
|
|
||||||
func InitChannel() {
|
func InitChannel() {
|
||||||
l4g.Debug(utils.T("api.channel.init.debug"))
|
l4g.Debug(utils.T("api.channel.init.debug"))
|
||||||
|
|
||||||
BaseRoutes.Channels.Handle("/", ApiUserRequiredActivity(getChannels, false)).Methods("GET")
|
BaseRoutes.Channels.Handle("/", ApiUserRequired(getChannels)).Methods("GET")
|
||||||
BaseRoutes.Channels.Handle("/more", ApiUserRequired(getMoreChannels)).Methods("GET")
|
BaseRoutes.Channels.Handle("/more", ApiUserRequired(getMoreChannels)).Methods("GET")
|
||||||
BaseRoutes.Channels.Handle("/counts", ApiUserRequiredActivity(getChannelCounts, false)).Methods("GET")
|
BaseRoutes.Channels.Handle("/counts", ApiUserRequired(getChannelCounts)).Methods("GET")
|
||||||
BaseRoutes.Channels.Handle("/create", ApiUserRequired(createChannel)).Methods("POST")
|
BaseRoutes.Channels.Handle("/create", ApiUserRequired(createChannel)).Methods("POST")
|
||||||
BaseRoutes.Channels.Handle("/create_direct", ApiUserRequired(createDirectChannel)).Methods("POST")
|
BaseRoutes.Channels.Handle("/create_direct", ApiUserRequired(createDirectChannel)).Methods("POST")
|
||||||
BaseRoutes.Channels.Handle("/update", ApiUserRequired(updateChannel)).Methods("POST")
|
BaseRoutes.Channels.Handle("/update", ApiUserRequired(updateChannel)).Methods("POST")
|
||||||
@@ -35,9 +30,9 @@ func InitChannel() {
|
|||||||
|
|
||||||
BaseRoutes.NeedChannelName.Handle("/join", ApiUserRequired(join)).Methods("POST")
|
BaseRoutes.NeedChannelName.Handle("/join", ApiUserRequired(join)).Methods("POST")
|
||||||
|
|
||||||
BaseRoutes.NeedChannel.Handle("/", ApiUserRequiredActivity(getChannel, false)).Methods("GET")
|
BaseRoutes.NeedChannel.Handle("/", ApiUserRequired(getChannel)).Methods("GET")
|
||||||
BaseRoutes.NeedChannel.Handle("/extra_info", ApiUserRequired(getChannelExtraInfo)).Methods("GET")
|
BaseRoutes.NeedChannel.Handle("/stats", ApiUserRequired(getChannelStats)).Methods("GET")
|
||||||
BaseRoutes.NeedChannel.Handle("/extra_info/{member_limit:-?[0-9]+}", ApiUserRequired(getChannelExtraInfo)).Methods("GET")
|
BaseRoutes.NeedChannel.Handle("/members/{user_id:[A-Za-z0-9]+}", ApiUserRequired(getChannelMember)).Methods("GET")
|
||||||
BaseRoutes.NeedChannel.Handle("/join", ApiUserRequired(join)).Methods("POST")
|
BaseRoutes.NeedChannel.Handle("/join", ApiUserRequired(join)).Methods("POST")
|
||||||
BaseRoutes.NeedChannel.Handle("/leave", ApiUserRequired(leave)).Methods("POST")
|
BaseRoutes.NeedChannel.Handle("/leave", ApiUserRequired(leave)).Methods("POST")
|
||||||
BaseRoutes.NeedChannel.Handle("/delete", ApiUserRequired(deleteChannel)).Methods("POST")
|
BaseRoutes.NeedChannel.Handle("/delete", ApiUserRequired(deleteChannel)).Methods("POST")
|
||||||
@@ -150,11 +145,14 @@ func CreateDirectChannel(userId string, otherUserId string) (*model.Channel, *mo
|
|||||||
} else {
|
} else {
|
||||||
channel := result.Data.(*model.Channel)
|
channel := result.Data.(*model.Channel)
|
||||||
|
|
||||||
|
InvalidateCacheForUser(userId)
|
||||||
|
InvalidateCacheForUser(otherUserId)
|
||||||
|
|
||||||
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_DIRECT_ADDED, "", channel.Id, "", nil)
|
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_DIRECT_ADDED, "", channel.Id, "", nil)
|
||||||
message.Add("teammate_id", otherUserId)
|
message.Add("teammate_id", otherUserId)
|
||||||
go Publish(message)
|
go Publish(message)
|
||||||
|
|
||||||
return result.Data.(*model.Channel), nil
|
return channel, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -566,6 +564,7 @@ func AddUserToChannel(user *model.User, channel *model.Channel) (*model.ChannelM
|
|||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
InvalidateCacheForUser(user.Id)
|
InvalidateCacheForUser(user.Id)
|
||||||
|
Srv.Store.User().InvalidateProfilesInChannelCache(channel.Id)
|
||||||
|
|
||||||
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_ADDED, "", channel.Id, "", nil)
|
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_ADDED, "", channel.Id, "", nil)
|
||||||
message.Add("user_id", user.Id)
|
message.Add("user_id", user.Id)
|
||||||
@@ -609,6 +608,8 @@ func JoinDefaultChannels(teamId string, user *model.User, channelRole string) *m
|
|||||||
if _, err := CreatePost(fakeContext, post, false); err != nil {
|
if _, err := CreatePost(fakeContext, post, false); err != nil {
|
||||||
l4g.Error(utils.T("api.channel.post_user_add_remove_message_and_forget.error"), err)
|
l4g.Error(utils.T("api.channel.post_user_add_remove_message_and_forget.error"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Srv.Store.User().InvalidateProfilesInChannelCache(result.Data.(*model.Channel).Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
if result := <-Srv.Store.Channel().GetByName(teamId, "off-topic"); result.Err != nil {
|
if result := <-Srv.Store.Channel().GetByName(teamId, "off-topic"); result.Err != nil {
|
||||||
@@ -631,6 +632,8 @@ func JoinDefaultChannels(teamId string, user *model.User, channelRole string) *m
|
|||||||
if _, err := CreatePost(fakeContext, post, false); err != nil {
|
if _, err := CreatePost(fakeContext, post, false); err != nil {
|
||||||
l4g.Error(utils.T("api.channel.post_user_add_remove_message_and_forget.error"), err)
|
l4g.Error(utils.T("api.channel.post_user_add_remove_message_and_forget.error"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Srv.Store.User().InvalidateProfilesInChannelCache(result.Data.(*model.Channel).Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
@@ -778,9 +781,9 @@ func deleteChannel(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
c.LogAudit("name=" + channel.Name)
|
c.LogAudit("name=" + channel.Name)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
InvalidateCacheForChannel(channel.Id)
|
|
||||||
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_DELETED, c.TeamId, "", "", nil)
|
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_DELETED, c.TeamId, "", "", nil)
|
||||||
message.Add("channel_id", channel.Id)
|
message.Add("channel_id", channel.Id)
|
||||||
|
|
||||||
go Publish(message)
|
go Publish(message)
|
||||||
|
|
||||||
post := &model.Post{
|
post := &model.Post{
|
||||||
@@ -917,54 +920,27 @@ func getChannel(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getChannelExtraInfo(c *Context, w http.ResponseWriter, r *http.Request) {
|
func getChannelStats(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
params := mux.Vars(r)
|
params := mux.Vars(r)
|
||||||
id := params["channel_id"]
|
id := params["channel_id"]
|
||||||
|
|
||||||
var memberLimit int
|
|
||||||
if memberLimitString, ok := params["member_limit"]; !ok {
|
|
||||||
memberLimit = defaultExtraMemberLimit
|
|
||||||
} else if memberLimitInt64, err := strconv.ParseInt(memberLimitString, 10, 0); err != nil {
|
|
||||||
c.Err = model.NewLocAppError("getChannelExtraInfo", "api.channel.get_channel_extra_info.member_limit.app_error", nil, err.Error())
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
memberLimit = int(memberLimitInt64)
|
|
||||||
}
|
|
||||||
|
|
||||||
sc := Srv.Store.Channel().Get(id)
|
sc := Srv.Store.Channel().Get(id)
|
||||||
var channel *model.Channel
|
var channel *model.Channel
|
||||||
if cresult := <-sc; cresult.Err != nil {
|
if result := <-sc; result.Err != nil {
|
||||||
c.Err = cresult.Err
|
c.Err = result.Err
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
channel = cresult.Data.(*model.Channel)
|
channel = result.Data.(*model.Channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
extraEtag := channel.ExtraEtag(memberLimit)
|
if result := <-Srv.Store.Channel().GetMemberCount(id); result.Err != nil {
|
||||||
if HandleEtag(extraEtag, w, r) {
|
c.Err = result.Err
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
scm := Srv.Store.Channel().GetMember(id, c.Session.UserId)
|
|
||||||
ecm := Srv.Store.Channel().GetExtraMembers(id, memberLimit)
|
|
||||||
ccm := Srv.Store.Channel().GetMemberCount(id)
|
|
||||||
|
|
||||||
if cmresult := <-scm; cmresult.Err != nil {
|
|
||||||
c.Err = cmresult.Err
|
|
||||||
return
|
|
||||||
} else if ecmresult := <-ecm; ecmresult.Err != nil {
|
|
||||||
c.Err = ecmresult.Err
|
|
||||||
return
|
|
||||||
} else if ccmresult := <-ccm; ccmresult.Err != nil {
|
|
||||||
c.Err = ccmresult.Err
|
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
//member := cmresult.Data.(model.ChannelMember)
|
memberCount := result.Data.(int64)
|
||||||
extraMembers := ecmresult.Data.([]model.ExtraMember)
|
|
||||||
memberCount := ccmresult.Data.(int64)
|
|
||||||
|
|
||||||
if channel.DeleteAt > 0 {
|
if channel.DeleteAt > 0 {
|
||||||
c.Err = model.NewLocAppError("getChannelExtraInfo", "api.channel.get_channel_extra_info.deleted.app_error", nil, "")
|
c.Err = model.NewLocAppError("getChannelStats", "api.channel.get_channel_extra_info.deleted.app_error", nil, "")
|
||||||
c.Err.StatusCode = http.StatusBadRequest
|
c.Err.StatusCode = http.StatusBadRequest
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -973,12 +949,29 @@ func getChannelExtraInfo(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data := model.ChannelExtra{Id: channel.Id, Members: extraMembers, MemberCount: memberCount}
|
data := model.ChannelStats{ChannelId: channel.Id, MemberCount: memberCount}
|
||||||
w.Header().Set(model.HEADER_ETAG_SERVER, extraEtag)
|
|
||||||
w.Write([]byte(data.ToJson()))
|
w.Write([]byte(data.ToJson()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getChannelMember(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
params := mux.Vars(r)
|
||||||
|
channelId := params["channel_id"]
|
||||||
|
userId := params["user_id"]
|
||||||
|
|
||||||
|
if !HasPermissionToChannelContext(c, channelId, model.PERMISSION_READ_CHANNEL) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := <-Srv.Store.Channel().GetMember(channelId, userId); result.Err != nil {
|
||||||
|
c.Err = result.Err
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
member := result.Data.(model.ChannelMember)
|
||||||
|
w.Write([]byte(member.ToJson()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func addMember(c *Context, w http.ResponseWriter, r *http.Request) {
|
func addMember(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
params := mux.Vars(r)
|
params := mux.Vars(r)
|
||||||
id := params["channel_id"]
|
id := params["channel_id"]
|
||||||
@@ -1101,6 +1094,7 @@ func RemoveUserFromChannel(userIdToRemove string, removerUserId string, channel
|
|||||||
}
|
}
|
||||||
|
|
||||||
InvalidateCacheForUser(userIdToRemove)
|
InvalidateCacheForUser(userIdToRemove)
|
||||||
|
Srv.Store.User().InvalidateProfilesInChannelCache(channel.Id)
|
||||||
|
|
||||||
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_REMOVED, "", channel.Id, "", nil)
|
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_REMOVED, "", channel.Id, "", nil)
|
||||||
message.Add("user_id", userIdToRemove)
|
message.Add("user_id", userIdToRemove)
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/mattermost/platform/model"
|
"github.com/mattermost/platform/model"
|
||||||
"github.com/mattermost/platform/store"
|
|
||||||
"github.com/mattermost/platform/utils"
|
"github.com/mattermost/platform/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1106,7 +1105,7 @@ func TestDeleteChannel(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetChannelExtraInfo(t *testing.T) {
|
func TestGetChannelStats(t *testing.T) {
|
||||||
th := Setup().InitBasic()
|
th := Setup().InitBasic()
|
||||||
Client := th.BasicClient
|
Client := th.BasicClient
|
||||||
team := th.BasicTeam
|
team := th.BasicTeam
|
||||||
@@ -1114,115 +1113,13 @@ func TestGetChannelExtraInfo(t *testing.T) {
|
|||||||
channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
|
channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
|
||||||
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
|
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
|
||||||
|
|
||||||
rget := Client.Must(Client.GetChannelExtraInfo(channel1.Id, -1, ""))
|
rget := Client.Must(Client.GetChannelStats(channel1.Id, ""))
|
||||||
data := rget.Data.(*model.ChannelExtra)
|
data := rget.Data.(*model.ChannelStats)
|
||||||
if data.Id != channel1.Id {
|
if data.ChannelId != channel1.Id {
|
||||||
t.Fatal("couldnt't get extra info")
|
t.Fatal("couldnt't get extra info")
|
||||||
} else if len(data.Members) != 1 {
|
|
||||||
t.Fatal("got incorrect members")
|
|
||||||
} else if data.MemberCount != 1 {
|
} else if data.MemberCount != 1 {
|
||||||
t.Fatal("got incorrect member count")
|
t.Fatal("got incorrect member count")
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
// Testing etag caching
|
|
||||||
//
|
|
||||||
|
|
||||||
currentEtag := rget.Etag
|
|
||||||
|
|
||||||
if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, -1, currentEtag); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
} else if cache_result.Data.(*model.ChannelExtra) != nil {
|
|
||||||
t.Log(cache_result.Data)
|
|
||||||
t.Fatal("response should be empty")
|
|
||||||
} else {
|
|
||||||
currentEtag = cache_result.Etag
|
|
||||||
}
|
|
||||||
|
|
||||||
Client2 := model.NewClient("http://localhost" + utils.Cfg.ServiceSettings.ListenAddress)
|
|
||||||
|
|
||||||
user2 := &model.User{Email: "success+" + model.NewId() + "@simulator.amazonses.com", Nickname: "Tester 2", Password: "passwd1"}
|
|
||||||
user2 = Client2.Must(Client2.CreateUser(user2, "")).Data.(*model.User)
|
|
||||||
LinkUserToTeam(user2, team)
|
|
||||||
Client2.SetTeamId(team.Id)
|
|
||||||
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
|
|
||||||
|
|
||||||
Client2.Login(user2.Email, "passwd1")
|
|
||||||
Client2.Must(Client2.JoinChannel(channel1.Id))
|
|
||||||
|
|
||||||
if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, -1, currentEtag); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
} else if cache_result.Data.(*model.ChannelExtra) == nil {
|
|
||||||
t.Log(cache_result.Data)
|
|
||||||
t.Fatal("response should not be empty")
|
|
||||||
} else {
|
|
||||||
currentEtag = cache_result.Etag
|
|
||||||
}
|
|
||||||
|
|
||||||
if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, -1, currentEtag); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
} else if cache_result.Data.(*model.ChannelExtra) != nil {
|
|
||||||
t.Log(cache_result.Data)
|
|
||||||
t.Fatal("response should be empty")
|
|
||||||
} else {
|
|
||||||
currentEtag = cache_result.Etag
|
|
||||||
}
|
|
||||||
|
|
||||||
Client2.Must(Client2.LeaveChannel(channel1.Id))
|
|
||||||
|
|
||||||
if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, -1, currentEtag); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
} else if cache_result.Data.(*model.ChannelExtra) == nil {
|
|
||||||
t.Log(cache_result.Data)
|
|
||||||
t.Fatal("response should not be empty")
|
|
||||||
} else {
|
|
||||||
currentEtag = cache_result.Etag
|
|
||||||
}
|
|
||||||
|
|
||||||
if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, -1, currentEtag); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
} else if cache_result.Data.(*model.ChannelExtra) != nil {
|
|
||||||
t.Log(cache_result.Data)
|
|
||||||
t.Fatal("response should be empty")
|
|
||||||
} else {
|
|
||||||
currentEtag = cache_result.Etag
|
|
||||||
}
|
|
||||||
|
|
||||||
Client2.Must(Client2.JoinChannel(channel1.Id))
|
|
||||||
|
|
||||||
if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, 2, currentEtag); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
} else if extra := cache_result.Data.(*model.ChannelExtra); extra == nil {
|
|
||||||
t.Fatal("response should not be empty")
|
|
||||||
} else if len(extra.Members) != 2 {
|
|
||||||
t.Fatal("should've returned 2 members")
|
|
||||||
} else if extra.MemberCount != 2 {
|
|
||||||
t.Fatal("should've returned member count of 2")
|
|
||||||
} else {
|
|
||||||
currentEtag = cache_result.Etag
|
|
||||||
}
|
|
||||||
|
|
||||||
if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, 1, currentEtag); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
} else if extra := cache_result.Data.(*model.ChannelExtra); extra == nil {
|
|
||||||
t.Fatal("response should not be empty")
|
|
||||||
} else if len(extra.Members) != 1 {
|
|
||||||
t.Fatal("should've returned only 1 member")
|
|
||||||
} else if extra.MemberCount != 2 {
|
|
||||||
t.Fatal("should've returned member count of 2")
|
|
||||||
} else {
|
|
||||||
currentEtag = cache_result.Etag
|
|
||||||
}
|
|
||||||
|
|
||||||
if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, 1, currentEtag); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
} else if cache_result.Data.(*model.ChannelExtra) != nil {
|
|
||||||
t.Log(cache_result.Data)
|
|
||||||
t.Fatal("response should be empty")
|
|
||||||
} else {
|
|
||||||
currentEtag = cache_result.Etag
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddChannelMember(t *testing.T) {
|
func TestAddChannelMember(t *testing.T) {
|
||||||
@@ -1495,3 +1392,41 @@ func TestFuzzyChannel(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetChannelMember(t *testing.T) {
|
||||||
|
th := Setup().InitBasic()
|
||||||
|
Client := th.BasicClient
|
||||||
|
team := th.BasicTeam
|
||||||
|
|
||||||
|
channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
|
||||||
|
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
|
||||||
|
|
||||||
|
if result, err := Client.GetChannelMember(channel1.Id, th.BasicUser.Id); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
cm := result.Data.(*model.ChannelMember)
|
||||||
|
|
||||||
|
if cm.UserId != th.BasicUser.Id {
|
||||||
|
t.Fatal("user ids didn't match")
|
||||||
|
}
|
||||||
|
if cm.ChannelId != channel1.Id {
|
||||||
|
t.Fatal("channel ids didn't match")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := Client.GetChannelMember(channel1.Id, th.BasicUser2.Id); err == nil {
|
||||||
|
t.Fatal("should have failed - user not in channel")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := Client.GetChannelMember("junk", th.BasicUser2.Id); err == nil {
|
||||||
|
t.Fatal("should have failed - bad channel id")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := Client.GetChannelMember(channel1.Id, "junk"); err == nil {
|
||||||
|
t.Fatal("should have failed - bad user id")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := Client.GetChannelMember("junk", "junk"); err == nil {
|
||||||
|
t.Fatal("should have failed - bad channel and user id")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ func TestCliCreateUserWithTeam(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
profiles := th.SystemAdminClient.Must(th.SystemAdminClient.GetProfiles(th.SystemAdminTeam.Id, "")).Data.(map[string]*model.User)
|
profiles := th.SystemAdminClient.Must(th.SystemAdminClient.GetProfilesInTeam(th.SystemAdminTeam.Id, 0, 1000, "")).Data.(map[string]*model.User)
|
||||||
|
|
||||||
found := false
|
found := false
|
||||||
|
|
||||||
@@ -318,7 +318,7 @@ func TestCliJoinTeam(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
profiles := th.SystemAdminClient.Must(th.SystemAdminClient.GetProfiles(th.SystemAdminTeam.Id, "")).Data.(map[string]*model.User)
|
profiles := th.SystemAdminClient.Must(th.SystemAdminClient.GetProfilesInTeam(th.SystemAdminTeam.Id, 0, 1000, "")).Data.(map[string]*model.User)
|
||||||
|
|
||||||
found := false
|
found := false
|
||||||
|
|
||||||
@@ -348,7 +348,7 @@ func TestCliLeaveTeam(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
profiles := th.BasicClient.Must(th.BasicClient.GetProfiles(th.BasicTeam.Id, "")).Data.(map[string]*model.User)
|
profiles := th.BasicClient.Must(th.BasicClient.GetProfilesInTeam(th.BasicTeam.Id, 0, 1000, "")).Data.(map[string]*model.User)
|
||||||
|
|
||||||
found := false
|
found := false
|
||||||
|
|
||||||
@@ -359,8 +359,8 @@ func TestCliLeaveTeam(t *testing.T) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
if found {
|
||||||
t.Fatal("profile still should be in team even if deleted")
|
t.Fatal("profile should not be on team")
|
||||||
}
|
}
|
||||||
|
|
||||||
if result := <-Srv.Store.Team().GetTeamsByUserId(th.BasicUser.Id); result.Err != nil {
|
if result := <-Srv.Store.Team().GetTeamsByUserId(th.BasicUser.Id); result.Err != nil {
|
||||||
|
|||||||
@@ -288,7 +288,7 @@ func (me *LoadTestProvider) PostsCommand(c *Context, channelId string, message s
|
|||||||
}
|
}
|
||||||
|
|
||||||
var usernames []string
|
var usernames []string
|
||||||
if result := <-Srv.Store.User().GetProfiles(c.TeamId); result.Err == nil {
|
if result := <-Srv.Store.User().GetProfiles(c.TeamId, 0, 1000); result.Err == nil {
|
||||||
profileUsers := result.Data.(map[string]*model.User)
|
profileUsers := result.Data.(map[string]*model.User)
|
||||||
usernames = make([]string, len(profileUsers))
|
usernames = make([]string, len(profileUsers))
|
||||||
i := 0
|
i := 0
|
||||||
|
|||||||
@@ -47,20 +47,22 @@ func (me *msgProvider) DoCommand(c *Context, channelId string, message string) *
|
|||||||
targetUser = strings.SplitN(message, " ", 2)[0]
|
targetUser = strings.SplitN(message, " ", 2)[0]
|
||||||
targetUser = strings.TrimPrefix(targetUser, "@")
|
targetUser = strings.TrimPrefix(targetUser, "@")
|
||||||
|
|
||||||
if profileList := <-Srv.Store.User().GetAllProfiles(); profileList.Err != nil {
|
// FIX ME
|
||||||
|
// Why isn't this selecting by username since we have that?
|
||||||
|
if profileList := <-Srv.Store.User().GetAll(); profileList.Err != nil {
|
||||||
c.Err = profileList.Err
|
c.Err = profileList.Err
|
||||||
return &model.CommandResponse{Text: c.T("api.command_msg.list.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
|
return &model.CommandResponse{Text: c.T("api.command_msg.list.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
|
||||||
} else {
|
} else {
|
||||||
profileUsers := profileList.Data.(map[string]*model.User)
|
profileUsers := profileList.Data.([]*model.User)
|
||||||
for _, userProfile := range profileUsers {
|
for _, userProfile := range profileUsers {
|
||||||
//Don't let users open DMs with themselves. It probably won't work out well.
|
// Don't let users open DMs with themselves. It probably won't work out well.
|
||||||
if userProfile.Id == c.Session.UserId {
|
if userProfile.Id == c.Session.UserId {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if userProfile.Username == targetUser {
|
if userProfile.Username == targetUser {
|
||||||
targetChannelId := ""
|
targetChannelId := ""
|
||||||
|
|
||||||
//Find the channel based on this user
|
// Find the channel based on this user
|
||||||
channelName := model.GetDMNameFromIds(c.Session.UserId, userProfile.Id)
|
channelName := model.GetDMNameFromIds(c.Session.UserId, userProfile.Id)
|
||||||
|
|
||||||
if channel := <-Srv.Store.Channel().GetByName(c.TeamId, channelName); channel.Err != nil {
|
if channel := <-Srv.Store.Channel().GetByName(c.TeamId, channelName); channel.Err != nil {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ func commandAndTest(t *testing.T, th *TestHelper, status string) {
|
|||||||
t.Fatal("Command failed to execute")
|
t.Fatal("Command failed to execute")
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(300 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
statuses := Client.Must(Client.GetStatuses()).Data.(map[string]string)
|
statuses := Client.Must(Client.GetStatuses()).Data.(map[string]string)
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ func AppHandlerIndependent(h func(*Context, http.ResponseWriter, *http.Request))
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ApiUserRequired(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
|
func ApiUserRequired(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
|
||||||
return &handler{h, true, false, true, true, false, false}
|
return &handler{h, true, false, true, false, false, false}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ApiUserRequiredActivity(h func(*Context, http.ResponseWriter, *http.Request), isUserActivity bool) http.Handler {
|
func ApiUserRequiredActivity(h func(*Context, http.ResponseWriter, *http.Request), isUserActivity bool) http.Handler {
|
||||||
@@ -85,7 +85,7 @@ func ApiAppHandlerTrustRequester(h func(*Context, http.ResponseWriter, *http.Req
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ApiUserRequiredTrustRequester(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
|
func ApiUserRequiredTrustRequester(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
|
||||||
return &handler{h, true, false, true, true, false, true}
|
return &handler{h, true, false, true, false, false, true}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ApiAppHandlerTrustRequesterIndependent(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
|
func ApiAppHandlerTrustRequesterIndependent(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
|
||||||
@@ -220,7 +220,7 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
c.LogError(c.Err)
|
c.LogError(c.Err)
|
||||||
c.Err.Where = r.URL.Path
|
c.Err.Where = r.URL.Path
|
||||||
|
|
||||||
// Block out detailed error whenn not in developer mode
|
// Block out detailed error when not in developer mode
|
||||||
if !*utils.Cfg.ServiceSettings.EnableDeveloper {
|
if !*utils.Cfg.ServiceSettings.EnableDeveloper {
|
||||||
c.Err.DetailedError = ""
|
c.Err.DetailedError = ""
|
||||||
}
|
}
|
||||||
|
|||||||
145
api/post.go
145
api/post.go
@@ -35,18 +35,18 @@ const (
|
|||||||
func InitPost() {
|
func InitPost() {
|
||||||
l4g.Debug(utils.T("api.post.init.debug"))
|
l4g.Debug(utils.T("api.post.init.debug"))
|
||||||
|
|
||||||
BaseRoutes.NeedTeam.Handle("/posts/search", ApiUserRequired(searchPosts)).Methods("POST")
|
BaseRoutes.NeedTeam.Handle("/posts/search", ApiUserRequiredActivity(searchPosts, true)).Methods("POST")
|
||||||
BaseRoutes.NeedTeam.Handle("/posts/flagged/{offset:[0-9]+}/{limit:[0-9]+}", ApiUserRequiredActivity(getFlaggedPosts, false)).Methods("GET")
|
BaseRoutes.NeedTeam.Handle("/posts/flagged/{offset:[0-9]+}/{limit:[0-9]+}", ApiUserRequired(getFlaggedPosts)).Methods("GET")
|
||||||
BaseRoutes.NeedTeam.Handle("/posts/{post_id}", ApiUserRequired(getPostById)).Methods("GET")
|
BaseRoutes.NeedTeam.Handle("/posts/{post_id}", ApiUserRequired(getPostById)).Methods("GET")
|
||||||
BaseRoutes.NeedTeam.Handle("/pltmp/{post_id}", ApiUserRequired(getPermalinkTmp)).Methods("GET")
|
BaseRoutes.NeedTeam.Handle("/pltmp/{post_id}", ApiUserRequired(getPermalinkTmp)).Methods("GET")
|
||||||
|
|
||||||
BaseRoutes.Posts.Handle("/create", ApiUserRequired(createPost)).Methods("POST")
|
BaseRoutes.Posts.Handle("/create", ApiUserRequiredActivity(createPost, true)).Methods("POST")
|
||||||
BaseRoutes.Posts.Handle("/update", ApiUserRequired(updatePost)).Methods("POST")
|
BaseRoutes.Posts.Handle("/update", ApiUserRequiredActivity(updatePost, true)).Methods("POST")
|
||||||
BaseRoutes.Posts.Handle("/page/{offset:[0-9]+}/{limit:[0-9]+}", ApiUserRequiredActivity(getPosts, false)).Methods("GET")
|
BaseRoutes.Posts.Handle("/page/{offset:[0-9]+}/{limit:[0-9]+}", ApiUserRequired(getPosts)).Methods("GET")
|
||||||
BaseRoutes.Posts.Handle("/since/{time:[0-9]+}", ApiUserRequiredActivity(getPostsSince, false)).Methods("GET")
|
BaseRoutes.Posts.Handle("/since/{time:[0-9]+}", ApiUserRequired(getPostsSince)).Methods("GET")
|
||||||
|
|
||||||
BaseRoutes.NeedPost.Handle("/get", ApiUserRequired(getPost)).Methods("GET")
|
BaseRoutes.NeedPost.Handle("/get", ApiUserRequired(getPost)).Methods("GET")
|
||||||
BaseRoutes.NeedPost.Handle("/delete", ApiUserRequired(deletePost)).Methods("POST")
|
BaseRoutes.NeedPost.Handle("/delete", ApiUserRequiredActivity(deletePost, true)).Methods("POST")
|
||||||
BaseRoutes.NeedPost.Handle("/before/{offset:[0-9]+}/{num_posts:[0-9]+}", ApiUserRequired(getPostsBefore)).Methods("GET")
|
BaseRoutes.NeedPost.Handle("/before/{offset:[0-9]+}/{num_posts:[0-9]+}", ApiUserRequired(getPostsBefore)).Methods("GET")
|
||||||
BaseRoutes.NeedPost.Handle("/after/{offset:[0-9]+}/{num_posts:[0-9]+}", ApiUserRequired(getPostsAfter)).Methods("GET")
|
BaseRoutes.NeedPost.Handle("/after/{offset:[0-9]+}/{num_posts:[0-9]+}", ApiUserRequired(getPostsAfter)).Methods("GET")
|
||||||
BaseRoutes.NeedPost.Handle("/get_file_infos", ApiUserRequired(getFileInfosForPost)).Methods("GET")
|
BaseRoutes.NeedPost.Handle("/get_file_infos", ApiUserRequired(getFileInfosForPost)).Methods("GET")
|
||||||
@@ -154,7 +154,7 @@ func CreatePost(c *Context, post *model.Post, triggerWebhooks bool) (*model.Post
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
go handlePostEvents(c, rpost, triggerWebhooks)
|
handlePostEvents(c, rpost, triggerWebhooks)
|
||||||
|
|
||||||
return rpost, nil
|
return rpost, nil
|
||||||
}
|
}
|
||||||
@@ -250,7 +250,7 @@ func handlePostEvents(c *Context, post *model.Post, triggerWebhooks bool) {
|
|||||||
channel = result.Data.(*model.Channel)
|
channel = result.Data.(*model.Channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
go sendNotifications(c, post, team, channel)
|
sendNotifications(c, post, team, channel)
|
||||||
|
|
||||||
var user *model.User
|
var user *model.User
|
||||||
if result := <-uchan; result.Err != nil {
|
if result := <-uchan; result.Err != nil {
|
||||||
@@ -441,40 +441,31 @@ func handleWebhookEvents(c *Context, post *model.Post, team *model.Team, channel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Given a map of user IDs to profiles and a map of user IDs of channel members, returns a list of mention
|
// Given a map of user IDs to profiles, returns a list of mention
|
||||||
// keywords for all users on the team. Users that are members of the channel will have all their mention
|
// keywords for all users in the channel.
|
||||||
// keywords returned while users that aren't in the channel will only have their @mentions returned.
|
func getMentionKeywordsInChannel(profiles map[string]*model.User) map[string][]string {
|
||||||
func getMentionKeywords(profiles map[string]*model.User, members map[string]string) map[string][]string {
|
|
||||||
keywords := make(map[string][]string)
|
keywords := make(map[string][]string)
|
||||||
|
|
||||||
for id, profile := range profiles {
|
for id, profile := range profiles {
|
||||||
_, inChannel := members[id]
|
if len(profile.NotifyProps["mention_keys"]) > 0 {
|
||||||
|
// Add all the user's mention keys
|
||||||
if inChannel {
|
splitKeys := strings.Split(profile.NotifyProps["mention_keys"], ",")
|
||||||
if len(profile.NotifyProps["mention_keys"]) > 0 {
|
for _, k := range splitKeys {
|
||||||
// Add all the user's mention keys
|
// note that these are made lower case so that we can do a case insensitive check for them
|
||||||
splitKeys := strings.Split(profile.NotifyProps["mention_keys"], ",")
|
key := strings.ToLower(k)
|
||||||
for _, k := range splitKeys {
|
keywords[key] = append(keywords[key], id)
|
||||||
// note that these are made lower case so that we can do a case insensitive check for them
|
|
||||||
key := strings.ToLower(k)
|
|
||||||
keywords[key] = append(keywords[key], id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If turned on, add the user's case sensitive first name
|
// If turned on, add the user's case sensitive first name
|
||||||
if profile.NotifyProps["first_name"] == "true" {
|
if profile.NotifyProps["first_name"] == "true" {
|
||||||
keywords[profile.FirstName] = append(keywords[profile.FirstName], profile.Id)
|
keywords[profile.FirstName] = append(keywords[profile.FirstName], profile.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add @channel and @all to keywords if user has them turned on
|
// Add @channel and @all to keywords if user has them turned on
|
||||||
if profile.NotifyProps["channel"] == "true" {
|
if profile.NotifyProps["channel"] == "true" {
|
||||||
keywords["@channel"] = append(keywords["@channel"], profile.Id)
|
keywords["@channel"] = append(keywords["@channel"], profile.Id)
|
||||||
keywords["@all"] = append(keywords["@all"], profile.Id)
|
keywords["@all"] = append(keywords["@all"], profile.Id)
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// user isn't in channel, so just look for @mentions
|
|
||||||
key := "@" + strings.ToLower(profile.Username)
|
|
||||||
keywords[key] = append(keywords[key], id)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -482,9 +473,11 @@ func getMentionKeywords(profiles map[string]*model.User, members map[string]stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Given a message and a map mapping mention keywords to the users who use them, returns a map of mentioned
|
// Given a message and a map mapping mention keywords to the users who use them, returns a map of mentioned
|
||||||
// users and whether or not @here was mentioned.
|
// users and a slice of potencial mention users not in the channel and whether or not @here was mentioned.
|
||||||
func getExplicitMentions(message string, keywords map[string][]string) (map[string]bool, bool) {
|
func getExplicitMentions(message string, keywords map[string][]string) (map[string]bool, []string, bool) {
|
||||||
mentioned := make(map[string]bool)
|
mentioned := make(map[string]bool)
|
||||||
|
potentialOthersMentioned := make([]string, 0)
|
||||||
|
systemMentions := map[string]bool{"@here": true, "@channel": true, "@all": true}
|
||||||
hereMentioned := false
|
hereMentioned := false
|
||||||
|
|
||||||
addMentionedUsers := func(ids []string) {
|
addMentionedUsers := func(ids []string) {
|
||||||
@@ -510,6 +503,9 @@ func getExplicitMentions(message string, keywords map[string][]string) (map[stri
|
|||||||
if ids, match := keywords[word]; match {
|
if ids, match := keywords[word]; match {
|
||||||
addMentionedUsers(ids)
|
addMentionedUsers(ids)
|
||||||
isMention = true
|
isMention = true
|
||||||
|
} else if _, ok := systemMentions[word]; !ok && strings.HasPrefix(word, "@") {
|
||||||
|
potentialOthersMentioned = append(potentialOthersMentioned, word[1:])
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isMention {
|
if !isMention {
|
||||||
@@ -532,19 +528,19 @@ func getExplicitMentions(message string, keywords map[string][]string) (map[stri
|
|||||||
// Case-sensitive check for first name
|
// Case-sensitive check for first name
|
||||||
if ids, match := keywords[splitWord]; match {
|
if ids, match := keywords[splitWord]; match {
|
||||||
addMentionedUsers(ids)
|
addMentionedUsers(ids)
|
||||||
|
} else if _, ok := systemMentions[word]; !ok && strings.HasPrefix(word, "@") {
|
||||||
|
username := word[1:len(splitWord)]
|
||||||
|
potentialOthersMentioned = append(potentialOthersMentioned, username)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return mentioned, hereMentioned
|
return mentioned, potentialOthersMentioned, hereMentioned
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendNotifications(c *Context, post *model.Post, team *model.Team, channel *model.Channel) {
|
func sendNotifications(c *Context, post *model.Post, team *model.Team, channel *model.Channel) {
|
||||||
// get profiles for all users we could be mentioning
|
pchan := Srv.Store.User().GetProfilesInChannel(channel.Id, -1, -1, true)
|
||||||
pchan := Srv.Store.User().GetProfiles(c.TeamId)
|
|
||||||
dpchan := Srv.Store.User().GetDirectProfiles(c.Session.UserId)
|
|
||||||
mchan := Srv.Store.Channel().GetMembers(post.ChannelId)
|
|
||||||
fchan := Srv.Store.FileInfo().GetForPost(post.Id)
|
fchan := Srv.Store.FileInfo().GetForPost(post.Id)
|
||||||
|
|
||||||
var profileMap map[string]*model.User
|
var profileMap map[string]*model.User
|
||||||
@@ -555,30 +551,11 @@ func sendNotifications(c *Context, post *model.Post, team *model.Team, channel *
|
|||||||
profileMap = result.Data.(map[string]*model.User)
|
profileMap = result.Data.(map[string]*model.User)
|
||||||
}
|
}
|
||||||
|
|
||||||
if result := <-dpchan; result.Err != nil {
|
// If the user who made the post is mention don't send a notification
|
||||||
l4g.Error(utils.T("api.post.handle_post_events_and_forget.profiles.error"), c.TeamId, result.Err)
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
dps := result.Data.(map[string]*model.User)
|
|
||||||
for k, v := range dps {
|
|
||||||
profileMap[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := profileMap[post.UserId]; !ok {
|
if _, ok := profileMap[post.UserId]; !ok {
|
||||||
l4g.Error(utils.T("api.post.send_notifications_and_forget.user_id.error"), post.UserId)
|
l4g.Error(utils.T("api.post.send_notifications_and_forget.user_id.error"), post.UserId)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// using a map as a pseudo-set since we're checking for containment a lot
|
|
||||||
members := make(map[string]string)
|
|
||||||
if result := <-mchan; result.Err != nil {
|
|
||||||
l4g.Error(utils.T("api.post.handle_post_events_and_forget.members.error"), post.ChannelId, result.Err)
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
for _, member := range result.Data.([]model.ChannelMember) {
|
|
||||||
members[member.UserId] = member.UserId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mentionedUserIds := make(map[string]bool)
|
mentionedUserIds := make(map[string]bool)
|
||||||
allActivityPushUserIds := []string{}
|
allActivityPushUserIds := []string{}
|
||||||
@@ -595,11 +572,11 @@ func sendNotifications(c *Context, post *model.Post, team *model.Team, channel *
|
|||||||
|
|
||||||
mentionedUserIds[otherUserId] = true
|
mentionedUserIds[otherUserId] = true
|
||||||
} else {
|
} else {
|
||||||
keywords := getMentionKeywords(profileMap, members)
|
keywords := getMentionKeywordsInChannel(profileMap)
|
||||||
|
|
||||||
// get users that are explicitly mentioned
|
|
||||||
var mentioned map[string]bool
|
var mentioned map[string]bool
|
||||||
mentioned, hereNotification = getExplicitMentions(post.Message, keywords)
|
var potentialOtherMentions []string
|
||||||
|
mentioned, potentialOtherMentions, hereNotification = getExplicitMentions(post.Message, keywords)
|
||||||
|
|
||||||
// get users that have comment thread mentions enabled
|
// get users that have comment thread mentions enabled
|
||||||
if len(post.RootId) > 0 {
|
if len(post.RootId) > 0 {
|
||||||
@@ -623,25 +600,15 @@ func sendNotifications(c *Context, post *model.Post, team *model.Team, channel *
|
|||||||
delete(mentioned, post.UserId)
|
delete(mentioned, post.UserId)
|
||||||
}
|
}
|
||||||
|
|
||||||
outOfChannelMentions := make(map[string]bool)
|
if len(potentialOtherMentions) > 0 {
|
||||||
for id := range mentioned {
|
if result := <-Srv.Store.User().GetProfilesByUsernames(potentialOtherMentions, team.Id); result.Err == nil {
|
||||||
if _, inChannel := members[id]; inChannel {
|
outOfChannelMentions := result.Data.(map[string]*model.User)
|
||||||
mentionedUserIds[id] = true
|
go sendOutOfChannelMentions(c, post, outOfChannelMentions)
|
||||||
} else {
|
|
||||||
outOfChannelMentions[id] = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
go sendOutOfChannelMentions(c, post, profileMap, outOfChannelMentions)
|
|
||||||
|
|
||||||
// find which users in the channel are set up to always receive mobile notifications
|
// find which users in the channel are set up to always receive mobile notifications
|
||||||
for id := range members {
|
for _, profile := range profileMap {
|
||||||
profile := profileMap[id]
|
|
||||||
if profile == nil {
|
|
||||||
l4g.Warn(utils.T("api.post.notification.member_profile.warn"), id)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if profile.NotifyProps["push"] == model.USER_NOTIFY_ALL &&
|
if profile.NotifyProps["push"] == model.USER_NOTIFY_ALL &&
|
||||||
(post.UserId != profile.Id || post.Props["from_webhook"] == "true") &&
|
(post.UserId != profile.Id || post.Props["from_webhook"] == "true") &&
|
||||||
!post.IsSystemMessage() {
|
!post.IsSystemMessage() {
|
||||||
@@ -699,10 +666,9 @@ func sendNotifications(c *Context, post *model.Post, team *model.Team, channel *
|
|||||||
}
|
}
|
||||||
|
|
||||||
_, profileFound := profileMap[status.UserId]
|
_, profileFound := profileMap[status.UserId]
|
||||||
_, isChannelMember := members[status.UserId]
|
|
||||||
_, alreadyMentioned := mentionedUserIds[status.UserId]
|
_, alreadyMentioned := mentionedUserIds[status.UserId]
|
||||||
|
|
||||||
if status.Status == model.STATUS_ONLINE && profileFound && isChannelMember && !alreadyMentioned {
|
if status.Status == model.STATUS_ONLINE && profileFound && !alreadyMentioned {
|
||||||
mentionedUsersList = append(mentionedUsersList, status.UserId)
|
mentionedUsersList = append(mentionedUsersList, status.UserId)
|
||||||
updateMentionChans = append(updateMentionChans, Srv.Store.Channel().IncrementMentionCount(post.ChannelId, status.UserId))
|
updateMentionChans = append(updateMentionChans, Srv.Store.Channel().IncrementMentionCount(post.ChannelId, status.UserId))
|
||||||
}
|
}
|
||||||
@@ -787,7 +753,8 @@ func sendNotifications(c *Context, post *model.Post, team *model.Team, channel *
|
|||||||
message.Add("mentions", model.ArrayToJson(mentionedUsersList))
|
message.Add("mentions", model.ArrayToJson(mentionedUsersList))
|
||||||
}
|
}
|
||||||
|
|
||||||
go Publish(message)
|
Publish(message)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendNotificationEmail(c *Context, post *model.Post, user *model.User, channel *model.Channel, team *model.Team, senderName string, sender *model.User) {
|
func sendNotificationEmail(c *Context, post *model.Post, user *model.User, channel *model.Channel, team *model.Team, senderName string, sender *model.User) {
|
||||||
@@ -1045,14 +1012,14 @@ func getMobileAppSession(userId string) *model.Session {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendOutOfChannelMentions(c *Context, post *model.Post, profiles map[string]*model.User, outOfChannelMentions map[string]bool) {
|
func sendOutOfChannelMentions(c *Context, post *model.Post, profiles map[string]*model.User) {
|
||||||
if len(outOfChannelMentions) == 0 {
|
if len(profiles) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var usernames []string
|
var usernames []string
|
||||||
for id := range outOfChannelMentions {
|
for _, user := range profiles {
|
||||||
usernames = append(usernames, profiles[id].Username)
|
usernames = append(usernames, user.Username)
|
||||||
}
|
}
|
||||||
sort.Strings(usernames)
|
sort.Strings(usernames)
|
||||||
|
|
||||||
|
|||||||
@@ -883,10 +883,9 @@ func TestGetMentionKeywords(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
profiles := map[string]*model.User{user1.Id: user1}
|
profiles := map[string]*model.User{user1.Id: user1}
|
||||||
members := map[string]string{user1.Id: user1.Id}
|
mentions := getMentionKeywordsInChannel(profiles)
|
||||||
mentions := getMentionKeywords(profiles, members)
|
|
||||||
if len(mentions) != 3 {
|
if len(mentions) != 3 {
|
||||||
t.Fatal("should've returned two mention keywords")
|
t.Fatal("should've returned three mention keywords")
|
||||||
} else if ids, ok := mentions["user"]; !ok || ids[0] != user1.Id {
|
} else if ids, ok := mentions["user"]; !ok || ids[0] != user1.Id {
|
||||||
t.Fatal("should've returned mention key of user")
|
t.Fatal("should've returned mention key of user")
|
||||||
} else if ids, ok := mentions["@user"]; !ok || ids[0] != user1.Id {
|
} else if ids, ok := mentions["@user"]; !ok || ids[0] != user1.Id {
|
||||||
@@ -906,8 +905,7 @@ func TestGetMentionKeywords(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
profiles = map[string]*model.User{user2.Id: user2}
|
profiles = map[string]*model.User{user2.Id: user2}
|
||||||
members = map[string]string{user2.Id: user2.Id}
|
mentions = getMentionKeywordsInChannel(profiles)
|
||||||
mentions = getMentionKeywords(profiles, members)
|
|
||||||
if len(mentions) != 1 {
|
if len(mentions) != 1 {
|
||||||
t.Fatal("should've returned one mention keyword")
|
t.Fatal("should've returned one mention keyword")
|
||||||
} else if ids, ok := mentions["First"]; !ok || ids[0] != user2.Id {
|
} else if ids, ok := mentions["First"]; !ok || ids[0] != user2.Id {
|
||||||
@@ -925,8 +923,7 @@ func TestGetMentionKeywords(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
profiles = map[string]*model.User{user3.Id: user3}
|
profiles = map[string]*model.User{user3.Id: user3}
|
||||||
members = map[string]string{user3.Id: user3.Id}
|
mentions = getMentionKeywordsInChannel(profiles)
|
||||||
mentions = getMentionKeywords(profiles, members)
|
|
||||||
if len(mentions) != 2 {
|
if len(mentions) != 2 {
|
||||||
t.Fatal("should've returned two mention keywords")
|
t.Fatal("should've returned two mention keywords")
|
||||||
} else if ids, ok := mentions["@channel"]; !ok || ids[0] != user3.Id {
|
} else if ids, ok := mentions["@channel"]; !ok || ids[0] != user3.Id {
|
||||||
@@ -948,8 +945,7 @@ func TestGetMentionKeywords(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
profiles = map[string]*model.User{user4.Id: user4}
|
profiles = map[string]*model.User{user4.Id: user4}
|
||||||
members = map[string]string{user4.Id: user4.Id}
|
mentions = getMentionKeywordsInChannel(profiles)
|
||||||
mentions = getMentionKeywords(profiles, members)
|
|
||||||
if len(mentions) != 6 {
|
if len(mentions) != 6 {
|
||||||
t.Fatal("should've returned six mention keywords")
|
t.Fatal("should've returned six mention keywords")
|
||||||
} else if ids, ok := mentions["user"]; !ok || ids[0] != user4.Id {
|
} else if ids, ok := mentions["user"]; !ok || ids[0] != user4.Id {
|
||||||
@@ -973,13 +969,7 @@ func TestGetMentionKeywords(t *testing.T) {
|
|||||||
user3.Id: user3,
|
user3.Id: user3,
|
||||||
user4.Id: user4,
|
user4.Id: user4,
|
||||||
}
|
}
|
||||||
members = map[string]string{
|
mentions = getMentionKeywordsInChannel(profiles)
|
||||||
user1.Id: user1.Id,
|
|
||||||
user2.Id: user2.Id,
|
|
||||||
user3.Id: user3.Id,
|
|
||||||
user4.Id: user4.Id,
|
|
||||||
}
|
|
||||||
mentions = getMentionKeywords(profiles, members)
|
|
||||||
if len(mentions) != 6 {
|
if len(mentions) != 6 {
|
||||||
t.Fatal("should've returned six mention keywords")
|
t.Fatal("should've returned six mention keywords")
|
||||||
} else if ids, ok := mentions["user"]; !ok || len(ids) != 2 || (ids[0] != user1.Id && ids[1] != user1.Id) || (ids[0] != user4.Id && ids[1] != user4.Id) {
|
} else if ids, ok := mentions["user"]; !ok || len(ids) != 2 || (ids[0] != user1.Id && ids[1] != user1.Id) || (ids[0] != user4.Id && ids[1] != user4.Id) {
|
||||||
@@ -995,16 +985,6 @@ func TestGetMentionKeywords(t *testing.T) {
|
|||||||
} else if ids, ok := mentions["@all"]; !ok || len(ids) != 2 || (ids[0] != user3.Id && ids[1] != user3.Id) || (ids[0] != user4.Id && ids[1] != user4.Id) {
|
} else if ids, ok := mentions["@all"]; !ok || len(ids) != 2 || (ids[0] != user3.Id && ids[1] != user3.Id) || (ids[0] != user4.Id && ids[1] != user4.Id) {
|
||||||
t.Fatal("should've mentioned user3 and user4 with @all")
|
t.Fatal("should've mentioned user3 and user4 with @all")
|
||||||
}
|
}
|
||||||
|
|
||||||
// a user that's not in the channel
|
|
||||||
profiles = map[string]*model.User{user4.Id: user4}
|
|
||||||
members = map[string]string{}
|
|
||||||
mentions = getMentionKeywords(profiles, members)
|
|
||||||
if len(mentions) != 1 {
|
|
||||||
t.Fatal("should've returned one mention keyword")
|
|
||||||
} else if ids, ok := mentions["@user"]; !ok || len(ids) != 1 || ids[0] != user4.Id {
|
|
||||||
t.Fatal("should've returned mention key of @user")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetExplicitMentionsAtHere(t *testing.T) {
|
func TestGetExplicitMentionsAtHere(t *testing.T) {
|
||||||
@@ -1051,7 +1031,7 @@ func TestGetExplicitMentionsAtHere(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for message, shouldMention := range cases {
|
for message, shouldMention := range cases {
|
||||||
if _, hereMentioned := getExplicitMentions(message, nil); hereMentioned && !shouldMention {
|
if _, _, hereMentioned := getExplicitMentions(message, nil); hereMentioned && !shouldMention {
|
||||||
t.Fatalf("shouldn't have mentioned @here with \"%v\"", message)
|
t.Fatalf("shouldn't have mentioned @here with \"%v\"", message)
|
||||||
} else if !hereMentioned && shouldMention {
|
} else if !hereMentioned && shouldMention {
|
||||||
t.Fatalf("should've have mentioned @here with \"%v\"", message)
|
t.Fatalf("should've have mentioned @here with \"%v\"", message)
|
||||||
@@ -1060,10 +1040,12 @@ func TestGetExplicitMentionsAtHere(t *testing.T) {
|
|||||||
|
|
||||||
// mentioning @here and someone
|
// mentioning @here and someone
|
||||||
id := model.NewId()
|
id := model.NewId()
|
||||||
if mentions, hereMentioned := getExplicitMentions("@here @user", map[string][]string{"@user": {id}}); !hereMentioned {
|
if mentions, potential, hereMentioned := getExplicitMentions("@here @user @potential", map[string][]string{"@user": {id}}); !hereMentioned {
|
||||||
t.Fatal("should've mentioned @here with \"@here @user\"")
|
t.Fatal("should've mentioned @here with \"@here @user\"")
|
||||||
} else if len(mentions) != 1 || !mentions[id] {
|
} else if len(mentions) != 1 || !mentions[id] {
|
||||||
t.Fatal("should've mentioned @user with \"@here @user\"")
|
t.Fatal("should've mentioned @user with \"@here @user\"")
|
||||||
|
} else if len(potential) > 1 {
|
||||||
|
t.Fatal("should've potential mentions for @potential")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1074,69 +1056,76 @@ func TestGetExplicitMentions(t *testing.T) {
|
|||||||
// not mentioning anybody
|
// not mentioning anybody
|
||||||
message := "this is a message"
|
message := "this is a message"
|
||||||
keywords := map[string][]string{}
|
keywords := map[string][]string{}
|
||||||
if mentions, _ := getExplicitMentions(message, keywords); len(mentions) != 0 {
|
if mentions, potential, _ := getExplicitMentions(message, keywords); len(mentions) != 0 || len(potential) != 0 {
|
||||||
t.Fatal("shouldn't have mentioned anybody")
|
t.Fatal("shouldn't have mentioned anybody or have any potencial mentions")
|
||||||
}
|
}
|
||||||
|
|
||||||
// mentioning a user that doesn't exist
|
// mentioning a user that doesn't exist
|
||||||
message = "this is a message for @user"
|
message = "this is a message for @user"
|
||||||
if mentions, _ := getExplicitMentions(message, keywords); len(mentions) != 0 {
|
if mentions, _, _ := getExplicitMentions(message, keywords); len(mentions) != 0 {
|
||||||
t.Fatal("shouldn't have mentioned user that doesn't exist")
|
t.Fatal("shouldn't have mentioned user that doesn't exist")
|
||||||
}
|
}
|
||||||
|
|
||||||
// mentioning one person
|
// mentioning one person
|
||||||
keywords = map[string][]string{"@user": {id1}}
|
keywords = map[string][]string{"@user": {id1}}
|
||||||
if mentions, _ := getExplicitMentions(message, keywords); len(mentions) != 1 || !mentions[id1] {
|
if mentions, _, _ := getExplicitMentions(message, keywords); len(mentions) != 1 || !mentions[id1] {
|
||||||
t.Fatal("should've mentioned @user")
|
t.Fatal("should've mentioned @user")
|
||||||
}
|
}
|
||||||
|
|
||||||
// mentioning one person without an @mention
|
// mentioning one person without an @mention
|
||||||
message = "this is a message for @user"
|
message = "this is a message for @user"
|
||||||
keywords = map[string][]string{"this": {id1}}
|
keywords = map[string][]string{"this": {id1}}
|
||||||
if mentions, _ := getExplicitMentions(message, keywords); len(mentions) != 1 || !mentions[id1] {
|
if mentions, _, _ := getExplicitMentions(message, keywords); len(mentions) != 1 || !mentions[id1] {
|
||||||
t.Fatal("should've mentioned this")
|
t.Fatal("should've mentioned this")
|
||||||
}
|
}
|
||||||
|
|
||||||
// mentioning multiple people with one word
|
// mentioning multiple people with one word
|
||||||
message = "this is a message for @user"
|
message = "this is a message for @user"
|
||||||
keywords = map[string][]string{"@user": {id1, id2}}
|
keywords = map[string][]string{"@user": {id1, id2}}
|
||||||
if mentions, _ := getExplicitMentions(message, keywords); len(mentions) != 2 || !mentions[id1] || !mentions[id2] {
|
if mentions, _, _ := getExplicitMentions(message, keywords); len(mentions) != 2 || !mentions[id1] || !mentions[id2] {
|
||||||
t.Fatal("should've mentioned two users with @user")
|
t.Fatal("should've mentioned two users with @user")
|
||||||
}
|
}
|
||||||
|
|
||||||
// mentioning only one of multiple people
|
// mentioning only one of multiple people
|
||||||
keywords = map[string][]string{"@user": {id1}, "@mention": {id2}}
|
keywords = map[string][]string{"@user": {id1}, "@mention": {id2}}
|
||||||
if mentions, _ := getExplicitMentions(message, keywords); len(mentions) != 1 || !mentions[id1] || mentions[id2] {
|
if mentions, _, _ := getExplicitMentions(message, keywords); len(mentions) != 1 || !mentions[id1] || mentions[id2] {
|
||||||
t.Fatal("should've mentioned @user and not @mention")
|
t.Fatal("should've mentioned @user and not @mention")
|
||||||
}
|
}
|
||||||
|
|
||||||
// mentioning multiple people with multiple words
|
// mentioning multiple people with multiple words
|
||||||
message = "this is an @mention for @user"
|
message = "this is an @mention for @user"
|
||||||
keywords = map[string][]string{"@user": {id1}, "@mention": {id2}}
|
keywords = map[string][]string{"@user": {id1}, "@mention": {id2}}
|
||||||
if mentions, _ := getExplicitMentions(message, keywords); len(mentions) != 2 || !mentions[id1] || !mentions[id2] {
|
if mentions, _, _ := getExplicitMentions(message, keywords); len(mentions) != 2 || !mentions[id1] || !mentions[id2] {
|
||||||
t.Fatal("should've mentioned two users with @user and @mention")
|
t.Fatal("should've mentioned two users with @user and @mention")
|
||||||
}
|
}
|
||||||
|
|
||||||
// mentioning @channel (not a special case, but it's good to double check)
|
// mentioning @channel (not a special case, but it's good to double check)
|
||||||
message = "this is an message for @channel"
|
message = "this is an message for @channel"
|
||||||
keywords = map[string][]string{"@channel": {id1, id2}}
|
keywords = map[string][]string{"@channel": {id1, id2}}
|
||||||
if mentions, _ := getExplicitMentions(message, keywords); len(mentions) != 2 || !mentions[id1] || !mentions[id2] {
|
if mentions, _, _ := getExplicitMentions(message, keywords); len(mentions) != 2 || !mentions[id1] || !mentions[id2] {
|
||||||
t.Fatal("should've mentioned two users with @channel")
|
t.Fatal("should've mentioned two users with @channel")
|
||||||
}
|
}
|
||||||
|
|
||||||
// mentioning @all (not a special case, but it's good to double check)
|
// mentioning @all (not a special case, but it's good to double check)
|
||||||
message = "this is an message for @all"
|
message = "this is an message for @all"
|
||||||
keywords = map[string][]string{"@all": {id1, id2}}
|
keywords = map[string][]string{"@all": {id1, id2}}
|
||||||
if mentions, _ := getExplicitMentions(message, keywords); len(mentions) != 2 || !mentions[id1] || !mentions[id2] {
|
if mentions, _, _ := getExplicitMentions(message, keywords); len(mentions) != 2 || !mentions[id1] || !mentions[id2] {
|
||||||
t.Fatal("should've mentioned two users with @all")
|
t.Fatal("should've mentioned two users with @all")
|
||||||
}
|
}
|
||||||
|
|
||||||
// mentioning user.period without mentioning user (PLT-3222)
|
// mentioning user.period without mentioning user (PLT-3222)
|
||||||
message = "user.period doesn't complicate things at all by including periods in their username"
|
message = "user.period doesn't complicate things at all by including periods in their username"
|
||||||
keywords = map[string][]string{"user.period": {id1}, "user": {id2}}
|
keywords = map[string][]string{"user.period": {id1}, "user": {id2}}
|
||||||
if mentions, _ := getExplicitMentions(message, keywords); len(mentions) != 1 || !mentions[id1] || mentions[id2] {
|
if mentions, _, _ := getExplicitMentions(message, keywords); len(mentions) != 1 || !mentions[id1] || mentions[id2] {
|
||||||
t.Fatal("should've mentioned user.period and not user")
|
t.Fatal("should've mentioned user.period and not user")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mentioning a potential out of channel user
|
||||||
|
message = "this is an message for @potential and @user"
|
||||||
|
keywords = map[string][]string{"@user": {id1}}
|
||||||
|
if mentions, potential, _ := getExplicitMentions(message, keywords); len(mentions) != 1 || !mentions[id1] || len(potential) != 1 {
|
||||||
|
t.Fatal("should've mentioned user and have a potential not in channel")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetFlaggedPosts(t *testing.T) {
|
func TestGetFlaggedPosts(t *testing.T) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/pprof"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -36,7 +37,20 @@ const TIME_TO_WAIT_FOR_CONNECTIONS_TO_CLOSE_ON_SERVER_SHUTDOWN = time.Second
|
|||||||
|
|
||||||
var Srv *Server
|
var Srv *Server
|
||||||
|
|
||||||
func NewServer() {
|
func AttachProfiler(router *mux.Router) {
|
||||||
|
router.HandleFunc("/debug/pprof/", pprof.Index)
|
||||||
|
router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
||||||
|
router.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
||||||
|
router.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
||||||
|
|
||||||
|
// Manually add support for paths linked to by index page at /debug/pprof/
|
||||||
|
router.Handle("/debug/pprof/goroutine", pprof.Handler("goroutine"))
|
||||||
|
router.Handle("/debug/pprof/heap", pprof.Handler("heap"))
|
||||||
|
router.Handle("/debug/pprof/threadcreate", pprof.Handler("threadcreate"))
|
||||||
|
router.Handle("/debug/pprof/block", pprof.Handler("block"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServer(enableProfiler bool) {
|
||||||
|
|
||||||
l4g.Info(utils.T("api.server.new_server.init.info"))
|
l4g.Info(utils.T("api.server.new_server.init.info"))
|
||||||
|
|
||||||
@@ -44,6 +58,10 @@ func NewServer() {
|
|||||||
Srv.Store = store.NewSqlStore()
|
Srv.Store = store.NewSqlStore()
|
||||||
|
|
||||||
Srv.Router = mux.NewRouter()
|
Srv.Router = mux.NewRouter()
|
||||||
|
if enableProfiler {
|
||||||
|
AttachProfiler(Srv.Router)
|
||||||
|
l4g.Info("Enabled HTTP Profiler")
|
||||||
|
}
|
||||||
Srv.Router.NotFoundHandler = http.HandlerFunc(Handle404)
|
Srv.Router.NotFoundHandler = http.HandlerFunc(Handle404)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,7 +195,7 @@ func StopServer() {
|
|||||||
|
|
||||||
Srv.GracefulServer.Stop(TIME_TO_WAIT_FOR_CONNECTIONS_TO_CLOSE_ON_SERVER_SHUTDOWN)
|
Srv.GracefulServer.Stop(TIME_TO_WAIT_FOR_CONNECTIONS_TO_CLOSE_ON_SERVER_SHUTDOWN)
|
||||||
Srv.Store.Close()
|
Srv.Store.Close()
|
||||||
hub.Stop()
|
HubStop()
|
||||||
|
|
||||||
l4g.Info(utils.T("api.server.stop_server.stopped.info"))
|
l4g.Info(utils.T("api.server.stop_server.stopped.info"))
|
||||||
}
|
}
|
||||||
|
|||||||
131
api/status.go
131
api/status.go
@@ -31,9 +31,11 @@ func AddStatusCache(status *model.Status) {
|
|||||||
func InitStatus() {
|
func InitStatus() {
|
||||||
l4g.Debug(utils.T("api.status.init.debug"))
|
l4g.Debug(utils.T("api.status.init.debug"))
|
||||||
|
|
||||||
BaseRoutes.Users.Handle("/status", ApiUserRequiredActivity(getStatusesHttp, false)).Methods("GET")
|
BaseRoutes.Users.Handle("/status", ApiUserRequired(getStatusesHttp)).Methods("GET")
|
||||||
BaseRoutes.Users.Handle("/status/set_active_channel", ApiUserRequiredActivity(setActiveChannel, false)).Methods("POST")
|
BaseRoutes.Users.Handle("/status/ids", ApiUserRequired(getStatusesByIdsHttp)).Methods("POST")
|
||||||
|
BaseRoutes.Users.Handle("/status/set_active_channel", ApiUserRequired(setActiveChannel)).Methods("POST")
|
||||||
BaseRoutes.WebSocket.Handle("get_statuses", ApiWebSocketHandler(getStatusesWebSocket))
|
BaseRoutes.WebSocket.Handle("get_statuses", ApiWebSocketHandler(getStatusesWebSocket))
|
||||||
|
BaseRoutes.WebSocket.Handle("get_statuses_by_ids", ApiWebSocketHandler(getStatusesByIdsWebSocket))
|
||||||
}
|
}
|
||||||
|
|
||||||
func getStatusesHttp(c *Context, w http.ResponseWriter, r *http.Request) {
|
func getStatusesHttp(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -55,6 +57,7 @@ func getStatusesWebSocket(req *model.WebSocketRequest) (map[string]interface{},
|
|||||||
return statusMap, nil
|
return statusMap, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only returns 300 statuses max
|
||||||
func GetAllStatuses() (map[string]interface{}, *model.AppError) {
|
func GetAllStatuses() (map[string]interface{}, *model.AppError) {
|
||||||
if result := <-Srv.Store.Status().GetOnlineAway(); result.Err != nil {
|
if result := <-Srv.Store.Status().GetOnlineAway(); result.Err != nil {
|
||||||
return nil, result.Err
|
return nil, result.Err
|
||||||
@@ -70,11 +73,82 @@ func GetAllStatuses() (map[string]interface{}, *model.AppError) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getStatusesByIdsHttp(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
userIds := model.ArrayFromJson(r.Body)
|
||||||
|
|
||||||
|
if len(userIds) == 0 {
|
||||||
|
c.SetInvalidParam("getStatusesByIdsHttp", "user_ids")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
statusMap, err := GetStatusesByIds(userIds)
|
||||||
|
if err != nil {
|
||||||
|
c.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write([]byte(model.StringInterfaceToJson(statusMap)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStatusesByIdsWebSocket(req *model.WebSocketRequest) (map[string]interface{}, *model.AppError) {
|
||||||
|
var userIds []string
|
||||||
|
if userIds = model.ArrayFromInterface(req.Data["user_ids"]); len(userIds) == 0 {
|
||||||
|
l4g.Error(model.StringInterfaceToJson(req.Data))
|
||||||
|
return nil, NewInvalidWebSocketParamError(req.Action, "user_ids")
|
||||||
|
}
|
||||||
|
|
||||||
|
statusMap, err := GetStatusesByIds(userIds)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return statusMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetStatusesByIds(userIds []string) (map[string]interface{}, *model.AppError) {
|
||||||
|
statusMap := map[string]interface{}{}
|
||||||
|
|
||||||
|
missingUserIds := []string{}
|
||||||
|
for _, userId := range userIds {
|
||||||
|
if result, ok := statusCache.Get(userId); ok {
|
||||||
|
statusMap[userId] = result.(*model.Status).Status
|
||||||
|
} else {
|
||||||
|
missingUserIds = append(missingUserIds, userId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(missingUserIds) > 0 {
|
||||||
|
if result := <-Srv.Store.Status().GetByIds(missingUserIds); result.Err != nil {
|
||||||
|
return nil, result.Err
|
||||||
|
} else {
|
||||||
|
statuses := result.Data.([]*model.Status)
|
||||||
|
|
||||||
|
for _, s := range statuses {
|
||||||
|
AddStatusCache(s)
|
||||||
|
statusMap[s.UserId] = s.Status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For the case where the user does not have a row in the Status table and cache
|
||||||
|
for _, userId := range missingUserIds {
|
||||||
|
if _, ok := statusMap[userId]; !ok {
|
||||||
|
statusMap[userId] = model.STATUS_OFFLINE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return statusMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
func SetStatusOnline(userId string, sessionId string, manual bool) {
|
func SetStatusOnline(userId string, sessionId string, manual bool) {
|
||||||
broadcast := false
|
broadcast := false
|
||||||
|
|
||||||
|
var oldStatus string = model.STATUS_OFFLINE
|
||||||
|
var oldTime int64 = 0
|
||||||
|
var oldManual bool = false
|
||||||
var status *model.Status
|
var status *model.Status
|
||||||
var err *model.AppError
|
var err *model.AppError
|
||||||
|
|
||||||
if status, err = GetStatus(userId); err != nil {
|
if status, err = GetStatus(userId); err != nil {
|
||||||
status = &model.Status{userId, model.STATUS_ONLINE, false, model.GetMillis(), ""}
|
status = &model.Status{userId, model.STATUS_ONLINE, false, model.GetMillis(), ""}
|
||||||
broadcast = true
|
broadcast = true
|
||||||
@@ -82,35 +156,45 @@ func SetStatusOnline(userId string, sessionId string, manual bool) {
|
|||||||
if status.Manual && !manual {
|
if status.Manual && !manual {
|
||||||
return // manually set status always overrides non-manual one
|
return // manually set status always overrides non-manual one
|
||||||
}
|
}
|
||||||
|
|
||||||
if status.Status != model.STATUS_ONLINE {
|
if status.Status != model.STATUS_ONLINE {
|
||||||
broadcast = true
|
broadcast = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
oldStatus = status.Status
|
||||||
|
oldTime = status.LastActivityAt
|
||||||
|
oldManual = status.Manual
|
||||||
|
|
||||||
status.Status = model.STATUS_ONLINE
|
status.Status = model.STATUS_ONLINE
|
||||||
status.Manual = false // for "online" there's no manually or auto set
|
status.Manual = false // for "online" there's no manual setting
|
||||||
status.LastActivityAt = model.GetMillis()
|
status.LastActivityAt = model.GetMillis()
|
||||||
}
|
}
|
||||||
|
|
||||||
AddStatusCache(status)
|
AddStatusCache(status)
|
||||||
|
|
||||||
achan := Srv.Store.Session().UpdateLastActivityAt(sessionId, model.GetMillis())
|
// Only update the database if the status has changed, the status has been manually set,
|
||||||
|
// or enough time has passed since the previous action
|
||||||
|
if status.Status != oldStatus || status.Manual != oldManual || status.LastActivityAt-oldTime > model.STATUS_MIN_UPDATE_TIME {
|
||||||
|
achan := Srv.Store.Session().UpdateLastActivityAt(sessionId, status.LastActivityAt)
|
||||||
|
|
||||||
var schan store.StoreChannel
|
var schan store.StoreChannel
|
||||||
if broadcast {
|
if broadcast {
|
||||||
schan = Srv.Store.Status().SaveOrUpdate(status)
|
schan = Srv.Store.Status().SaveOrUpdate(status)
|
||||||
} else {
|
} else {
|
||||||
schan = Srv.Store.Status().UpdateLastActivityAt(status.UserId, status.LastActivityAt)
|
schan = Srv.Store.Status().UpdateLastActivityAt(status.UserId, status.LastActivityAt)
|
||||||
}
|
}
|
||||||
|
|
||||||
if result := <-achan; result.Err != nil {
|
if result := <-achan; result.Err != nil {
|
||||||
l4g.Error(utils.T("api.status.last_activity.error"), userId, sessionId, result.Err)
|
l4g.Error(utils.T("api.status.last_activity.error"), userId, sessionId, result.Err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if result := <-schan; result.Err != nil {
|
if result := <-schan; result.Err != nil {
|
||||||
l4g.Error(utils.T("api.status.save_status.error"), userId, result.Err)
|
l4g.Error(utils.T("api.status.save_status.error"), userId, result.Err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if broadcast {
|
if broadcast {
|
||||||
event := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_STATUS_CHANGE, "", "", "", nil)
|
event := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_STATUS_CHANGE, "", "", status.UserId, nil)
|
||||||
event.Add("status", model.STATUS_ONLINE)
|
event.Add("status", model.STATUS_ONLINE)
|
||||||
event.Add("user_id", status.UserId)
|
event.Add("user_id", status.UserId)
|
||||||
go Publish(event)
|
go Publish(event)
|
||||||
@@ -131,7 +215,7 @@ func SetStatusOffline(userId string, manual bool) {
|
|||||||
l4g.Error(utils.T("api.status.save_status.error"), userId, result.Err)
|
l4g.Error(utils.T("api.status.save_status.error"), userId, result.Err)
|
||||||
}
|
}
|
||||||
|
|
||||||
event := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_STATUS_CHANGE, "", "", "", nil)
|
event := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_STATUS_CHANGE, "", "", status.UserId, nil)
|
||||||
event.Add("status", model.STATUS_OFFLINE)
|
event.Add("status", model.STATUS_OFFLINE)
|
||||||
event.Add("user_id", status.UserId)
|
event.Add("user_id", status.UserId)
|
||||||
go Publish(event)
|
go Publish(event)
|
||||||
@@ -168,15 +252,18 @@ func SetStatusAwayIfNeeded(userId string, manual bool) {
|
|||||||
l4g.Error(utils.T("api.status.save_status.error"), userId, result.Err)
|
l4g.Error(utils.T("api.status.save_status.error"), userId, result.Err)
|
||||||
}
|
}
|
||||||
|
|
||||||
event := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_STATUS_CHANGE, "", "", "", nil)
|
event := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_STATUS_CHANGE, "", "", status.UserId, nil)
|
||||||
event.Add("status", model.STATUS_AWAY)
|
event.Add("status", model.STATUS_AWAY)
|
||||||
event.Add("user_id", status.UserId)
|
event.Add("user_id", status.UserId)
|
||||||
go Publish(event)
|
go Publish(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetStatus(userId string) (*model.Status, *model.AppError) {
|
func GetStatus(userId string) (*model.Status, *model.AppError) {
|
||||||
if status, ok := statusCache.Get(userId); ok {
|
if result, ok := statusCache.Get(userId); ok {
|
||||||
return status.(*model.Status), nil
|
status := result.(*model.Status)
|
||||||
|
statusCopy := &model.Status{}
|
||||||
|
*statusCopy = *status
|
||||||
|
return statusCopy, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if result := <-Srv.Store.Status().Get(userId); result.Err != nil {
|
if result := <-Srv.Store.Status().Get(userId); result.Err != nil {
|
||||||
@@ -232,6 +319,10 @@ func SetActiveChannel(userId string, channelId string) *model.AppError {
|
|||||||
status = &model.Status{userId, model.STATUS_ONLINE, false, model.GetMillis(), channelId}
|
status = &model.Status{userId, model.STATUS_ONLINE, false, model.GetMillis(), channelId}
|
||||||
} else {
|
} else {
|
||||||
status.ActiveChannel = channelId
|
status.ActiveChannel = channelId
|
||||||
|
if !status.Manual {
|
||||||
|
status.Status = model.STATUS_ONLINE
|
||||||
|
}
|
||||||
|
status.LastActivityAt = model.GetMillis()
|
||||||
}
|
}
|
||||||
|
|
||||||
AddStatusCache(status)
|
AddStatusCache(status)
|
||||||
|
|||||||
@@ -4,13 +4,12 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/mattermost/platform/model"
|
"github.com/mattermost/platform/model"
|
||||||
"github.com/mattermost/platform/store"
|
"github.com/mattermost/platform/store"
|
||||||
"github.com/mattermost/platform/utils"
|
"github.com/mattermost/platform/utils"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestStatuses(t *testing.T) {
|
func TestStatuses(t *testing.T) {
|
||||||
@@ -59,7 +58,7 @@ func TestStatuses(t *testing.T) {
|
|||||||
t.Fatal(err2)
|
t.Fatal(err2)
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(300 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
WebSocketClient.GetStatuses()
|
WebSocketClient.GetStatuses()
|
||||||
if resp := <-WebSocketClient.ResponseChannel; resp.Error != nil {
|
if resp := <-WebSocketClient.ResponseChannel; resp.Error != nil {
|
||||||
@@ -76,6 +75,7 @@ func TestStatuses(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if status, ok := resp.Data[th.BasicUser2.Id]; !ok {
|
if status, ok := resp.Data[th.BasicUser2.Id]; !ok {
|
||||||
|
t.Log(len(resp.Data))
|
||||||
t.Fatal("should have had user status")
|
t.Fatal("should have had user status")
|
||||||
} else if status != model.STATUS_ONLINE {
|
} else if status != model.STATUS_ONLINE {
|
||||||
t.Log(status)
|
t.Log(status)
|
||||||
@@ -83,7 +83,55 @@ func TestStatuses(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SetStatusAwayIfNeeded(th.BasicUser2.Id, false)
|
WebSocketClient.GetStatusesByIds([]string{th.BasicUser2.Id})
|
||||||
|
if resp := <-WebSocketClient.ResponseChannel; resp.Error != nil {
|
||||||
|
t.Fatal(resp.Error)
|
||||||
|
} else {
|
||||||
|
if resp.SeqReply != WebSocketClient.Sequence-1 {
|
||||||
|
t.Fatal("bad sequence number")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, status := range resp.Data {
|
||||||
|
if status != model.STATUS_OFFLINE && status != model.STATUS_AWAY && status != model.STATUS_ONLINE {
|
||||||
|
t.Fatal("one of the statuses had an invalid value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if status, ok := resp.Data[th.BasicUser2.Id]; !ok {
|
||||||
|
t.Log(len(resp.Data))
|
||||||
|
t.Fatal("should have had user status")
|
||||||
|
} else if status != model.STATUS_ONLINE {
|
||||||
|
t.Log(status)
|
||||||
|
t.Fatal("status should have been online")
|
||||||
|
} else if len(resp.Data) != 1 {
|
||||||
|
t.Fatal("only 1 status should be returned")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WebSocketClient.GetStatusesByIds([]string{ruser2.Id, "junk"})
|
||||||
|
if resp := <-WebSocketClient.ResponseChannel; resp.Error != nil {
|
||||||
|
t.Fatal(resp.Error)
|
||||||
|
} else {
|
||||||
|
if resp.SeqReply != WebSocketClient.Sequence-1 {
|
||||||
|
t.Fatal("bad sequence number")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(resp.Data) != 2 {
|
||||||
|
t.Fatal("2 statuses should be returned")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WebSocketClient.GetStatusesByIds([]string{})
|
||||||
|
if resp := <-WebSocketClient.ResponseChannel; resp.Error == nil {
|
||||||
|
if resp.SeqReply != WebSocketClient.Sequence-1 {
|
||||||
|
t.Fatal("bad sequence number")
|
||||||
|
}
|
||||||
|
t.Fatal("should have errored - empty user ids")
|
||||||
|
}
|
||||||
|
|
||||||
|
WebSocketClient2.Close()
|
||||||
|
|
||||||
|
SetStatusAwayIfNeeded(th.BasicUser.Id, false)
|
||||||
|
|
||||||
awayTimeout := *utils.Cfg.TeamSettings.UserStatusAwayTimeout
|
awayTimeout := *utils.Cfg.TeamSettings.UserStatusAwayTimeout
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -93,10 +141,9 @@ func TestStatuses(t *testing.T) {
|
|||||||
|
|
||||||
time.Sleep(1500 * time.Millisecond)
|
time.Sleep(1500 * time.Millisecond)
|
||||||
|
|
||||||
SetStatusAwayIfNeeded(th.BasicUser2.Id, false)
|
SetStatusAwayIfNeeded(th.BasicUser.Id, false)
|
||||||
SetStatusAwayIfNeeded(th.BasicUser2.Id, false)
|
SetStatusOnline(th.BasicUser.Id, "junk", false)
|
||||||
|
|
||||||
WebSocketClient2.Close()
|
|
||||||
time.Sleep(300 * time.Millisecond)
|
time.Sleep(300 * time.Millisecond)
|
||||||
|
|
||||||
WebSocketClient.GetStatuses()
|
WebSocketClient.GetStatuses()
|
||||||
@@ -115,20 +162,17 @@ func TestStatuses(t *testing.T) {
|
|||||||
stop := make(chan bool)
|
stop := make(chan bool)
|
||||||
onlineHit := false
|
onlineHit := false
|
||||||
awayHit := false
|
awayHit := false
|
||||||
offlineHit := false
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case resp := <-WebSocketClient.EventChannel:
|
case resp := <-WebSocketClient.EventChannel:
|
||||||
if resp.Event == model.WEBSOCKET_EVENT_STATUS_CHANGE && resp.Data["user_id"].(string) == th.BasicUser2.Id {
|
if resp.Event == model.WEBSOCKET_EVENT_STATUS_CHANGE && resp.Data["user_id"].(string) == th.BasicUser.Id {
|
||||||
status := resp.Data["status"].(string)
|
status := resp.Data["status"].(string)
|
||||||
if status == model.STATUS_ONLINE {
|
if status == model.STATUS_ONLINE {
|
||||||
onlineHit = true
|
onlineHit = true
|
||||||
} else if status == model.STATUS_AWAY {
|
} else if status == model.STATUS_AWAY {
|
||||||
awayHit = true
|
awayHit = true
|
||||||
} else if status == model.STATUS_OFFLINE {
|
|
||||||
offlineHit = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case <-stop:
|
case <-stop:
|
||||||
@@ -147,11 +191,40 @@ func TestStatuses(t *testing.T) {
|
|||||||
if !awayHit {
|
if !awayHit {
|
||||||
t.Fatal("didn't get away event")
|
t.Fatal("didn't get away event")
|
||||||
}
|
}
|
||||||
if !offlineHit {
|
|
||||||
t.Fatal("didn't get offline event")
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
|
WebSocketClient.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetStatusesByIds(t *testing.T) {
|
||||||
|
th := Setup().InitBasic()
|
||||||
|
Client := th.BasicClient
|
||||||
|
|
||||||
|
if result, err := Client.GetStatusesByIds([]string{th.BasicUser.Id}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
statuses := result.Data.(map[string]string)
|
||||||
|
if len(statuses) != 1 {
|
||||||
|
t.Fatal("should only have 1 status")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if result, err := Client.GetStatusesByIds([]string{th.BasicUser.Id, th.BasicUser2.Id, "junk"}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
statuses := result.Data.(map[string]string)
|
||||||
|
if len(statuses) != 3 {
|
||||||
|
t.Fatal("should have 3 statuses")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := Client.GetStatusesByIds([]string{}); err == nil {
|
||||||
|
t.Fatal("should have errored")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
func TestSetActiveChannel(t *testing.T) {
|
func TestSetActiveChannel(t *testing.T) {
|
||||||
th := Setup().InitBasic()
|
th := Setup().InitBasic()
|
||||||
Client := th.BasicClient
|
Client := th.BasicClient
|
||||||
@@ -185,8 +258,9 @@ func TestSetActiveChannel(t *testing.T) {
|
|||||||
time.Sleep(500 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
status, _ = GetStatus(th.BasicUser.Id)
|
status, _ = GetStatus(th.BasicUser.Id)
|
||||||
// need to check if offline to catch race
|
need to check if offline to catch race
|
||||||
if status.Status != model.STATUS_OFFLINE && status.ActiveChannel != th.BasicChannel.Id {
|
if status.Status != model.STATUS_OFFLINE && status.ActiveChannel != th.BasicChannel.Id {
|
||||||
t.Fatal("active channel should be set")
|
t.Fatal("active channel should be set")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|||||||
100
api/team.go
100
api/team.go
@@ -31,9 +31,12 @@ func InitTeam() {
|
|||||||
BaseRoutes.Teams.Handle("/all_team_listings", ApiUserRequired(GetAllTeamListings)).Methods("GET")
|
BaseRoutes.Teams.Handle("/all_team_listings", ApiUserRequired(GetAllTeamListings)).Methods("GET")
|
||||||
BaseRoutes.Teams.Handle("/get_invite_info", ApiAppHandler(getInviteInfo)).Methods("POST")
|
BaseRoutes.Teams.Handle("/get_invite_info", ApiAppHandler(getInviteInfo)).Methods("POST")
|
||||||
BaseRoutes.Teams.Handle("/find_team_by_name", ApiAppHandler(findTeamByName)).Methods("POST")
|
BaseRoutes.Teams.Handle("/find_team_by_name", ApiAppHandler(findTeamByName)).Methods("POST")
|
||||||
BaseRoutes.Teams.Handle("/members/{id:[A-Za-z0-9]+}", ApiUserRequired(getMembers)).Methods("GET")
|
|
||||||
|
|
||||||
BaseRoutes.NeedTeam.Handle("/me", ApiUserRequired(getMyTeam)).Methods("GET")
|
BaseRoutes.NeedTeam.Handle("/me", ApiUserRequired(getMyTeam)).Methods("GET")
|
||||||
|
BaseRoutes.NeedTeam.Handle("/stats", ApiUserRequired(getTeamStats)).Methods("GET")
|
||||||
|
BaseRoutes.NeedTeam.Handle("/members/{offset:[0-9]+}/{limit:[0-9]+}", ApiUserRequired(getTeamMembers)).Methods("GET")
|
||||||
|
BaseRoutes.NeedTeam.Handle("/members/ids", ApiUserRequired(getTeamMembersByIds)).Methods("POST")
|
||||||
|
BaseRoutes.NeedTeam.Handle("/members/{user_id:[A-Za-z0-9]+}", ApiUserRequired(getTeamMember)).Methods("GET")
|
||||||
BaseRoutes.NeedTeam.Handle("/update", ApiUserRequired(updateTeam)).Methods("POST")
|
BaseRoutes.NeedTeam.Handle("/update", ApiUserRequired(updateTeam)).Methods("POST")
|
||||||
BaseRoutes.NeedTeam.Handle("/update_member_roles", ApiUserRequired(updateMemberRoles)).Methods("POST")
|
BaseRoutes.NeedTeam.Handle("/update_member_roles", ApiUserRequired(updateMemberRoles)).Methods("POST")
|
||||||
|
|
||||||
@@ -305,7 +308,9 @@ func JoinUserToTeam(team *model.Team, user *model.User) *model.AppError {
|
|||||||
InvalidateCacheForUser(user.Id)
|
InvalidateCacheForUser(user.Id)
|
||||||
|
|
||||||
// This message goes to everyone, so the teamId, channelId and userId are irrelevant
|
// This message goes to everyone, so the teamId, channelId and userId are irrelevant
|
||||||
go Publish(model.NewWebSocketEvent(model.WEBSOCKET_EVENT_NEW_USER, "", "", "", nil))
|
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_NEW_USER, "", "", "", nil)
|
||||||
|
message.Add("user_id", user.Id)
|
||||||
|
go Publish(message)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -335,11 +340,10 @@ func LeaveTeam(team *model.Team, user *model.User) *model.AppError {
|
|||||||
|
|
||||||
for _, channel := range channelMembers.Channels {
|
for _, channel := range channelMembers.Channels {
|
||||||
if channel.Type != model.CHANNEL_DIRECT {
|
if channel.Type != model.CHANNEL_DIRECT {
|
||||||
|
Srv.Store.User().InvalidateProfilesInChannelCache(channel.Id)
|
||||||
if result := <-Srv.Store.Channel().RemoveMember(channel.Id, user.Id); result.Err != nil {
|
if result := <-Srv.Store.Channel().RemoveMember(channel.Id, user.Id); result.Err != nil {
|
||||||
return result.Err
|
return result.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
InvalidateCacheForChannel(channel.Id)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -889,6 +893,25 @@ func getMyTeam(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getTeamStats(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
if c.Session.GetTeamByTeamId(c.TeamId) == nil {
|
||||||
|
if !HasPermissionToContext(c, model.PERMISSION_MANAGE_SYSTEM) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := <-Srv.Store.Team().GetMemberCount(c.TeamId); result.Err != nil {
|
||||||
|
c.Err = result.Err
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
stats := &model.TeamStats{}
|
||||||
|
stats.MemberCount = result.Data.(int64)
|
||||||
|
stats.TeamId = c.TeamId
|
||||||
|
w.Write([]byte(stats.ToJson()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func importTeam(c *Context, w http.ResponseWriter, r *http.Request) {
|
func importTeam(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
if !HasPermissionToCurrentTeamContext(c, model.PERMISSION_IMPORT_TEAM) {
|
if !HasPermissionToCurrentTeamContext(c, model.PERMISSION_IMPORT_TEAM) {
|
||||||
c.Err = model.NewLocAppError("importTeam", "api.team.import_team.admin.app_error", nil, "userId="+c.Session.UserId)
|
c.Err = model.NewLocAppError("importTeam", "api.team.import_team.admin.app_error", nil, "userId="+c.Session.UserId)
|
||||||
@@ -982,17 +1005,76 @@ func getInviteInfo(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMembers(c *Context, w http.ResponseWriter, r *http.Request) {
|
func getTeamMembers(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
params := mux.Vars(r)
|
params := mux.Vars(r)
|
||||||
id := params["id"]
|
|
||||||
|
|
||||||
if c.Session.GetTeamByTeamId(id) == nil {
|
offset, err := strconv.Atoi(params["offset"])
|
||||||
if !HasPermissionToTeamContext(c, id, model.PERMISSION_MANAGE_SYSTEM) {
|
if err != nil {
|
||||||
|
c.SetInvalidParam("getTeamMembers", "offset")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
limit, err := strconv.Atoi(params["limit"])
|
||||||
|
if err != nil {
|
||||||
|
c.SetInvalidParam("getTeamMembers", "limit")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Session.GetTeamByTeamId(c.TeamId) == nil {
|
||||||
|
if !HasPermissionToTeamContext(c, c.TeamId, model.PERMISSION_MANAGE_SYSTEM) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if result := <-Srv.Store.Team().GetMembers(id); result.Err != nil {
|
if result := <-Srv.Store.Team().GetMembers(c.TeamId, offset, limit); result.Err != nil {
|
||||||
|
c.Err = result.Err
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
members := result.Data.([]*model.TeamMember)
|
||||||
|
w.Write([]byte(model.TeamMembersToJson(members)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTeamMember(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
params := mux.Vars(r)
|
||||||
|
|
||||||
|
userId := params["user_id"]
|
||||||
|
if len(userId) < 26 {
|
||||||
|
c.SetInvalidParam("getTeamMember", "user_id")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Session.GetTeamByTeamId(c.TeamId) == nil {
|
||||||
|
if !HasPermissionToTeamContext(c, c.TeamId, model.PERMISSION_MANAGE_SYSTEM) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := <-Srv.Store.Team().GetMember(c.TeamId, userId); result.Err != nil {
|
||||||
|
c.Err = result.Err
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
member := result.Data.(model.TeamMember)
|
||||||
|
w.Write([]byte(member.ToJson()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTeamMembersByIds(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
userIds := model.ArrayFromJson(r.Body)
|
||||||
|
if len(userIds) == 0 {
|
||||||
|
c.SetInvalidParam("getTeamMembersByIds", "user_ids")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Session.GetTeamByTeamId(c.TeamId) == nil {
|
||||||
|
if !HasPermissionToTeamContext(c, c.TeamId, model.PERMISSION_MANAGE_SYSTEM) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := <-Srv.Store.Team().GetMembersByIds(c.TeamId, userIds); result.Err != nil {
|
||||||
c.Err = result.Err
|
c.Err = result.Err
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
109
api/team_test.go
109
api/team_test.go
@@ -560,14 +560,80 @@ func TestGetMyTeam(t *testing.T) {
|
|||||||
func TestGetTeamMembers(t *testing.T) {
|
func TestGetTeamMembers(t *testing.T) {
|
||||||
th := Setup().InitBasic()
|
th := Setup().InitBasic()
|
||||||
|
|
||||||
if result, err := th.BasicClient.GetTeamMembers(th.BasicTeam.Id); err != nil {
|
if result, err := th.BasicClient.GetTeamMembers(th.BasicTeam.Id, 0, 100); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
} else {
|
} else {
|
||||||
members := result.Data.([]*model.TeamMember)
|
members := result.Data.([]*model.TeamMember)
|
||||||
if members == nil {
|
if len(members) == 0 {
|
||||||
|
t.Fatal("should have results")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := th.BasicClient.GetTeamMembers("junk", 0, 100); err == nil {
|
||||||
|
t.Fatal("should have errored - bad team id")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetTeamMember(t *testing.T) {
|
||||||
|
th := Setup().InitBasic()
|
||||||
|
|
||||||
|
if result, err := th.BasicClient.GetTeamMember(th.BasicTeam.Id, th.BasicUser.Id); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
member := result.Data.(*model.TeamMember)
|
||||||
|
if member == nil {
|
||||||
t.Fatal("should be valid")
|
t.Fatal("should be valid")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, err := th.BasicClient.GetTeamMember("junk", th.BasicUser.Id); err == nil {
|
||||||
|
t.Fatal("should have errored - bad team id")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := th.BasicClient.GetTeamMember(th.BasicTeam.Id, ""); err == nil {
|
||||||
|
t.Fatal("should have errored - blank user id")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := th.BasicClient.GetTeamMember(th.BasicTeam.Id, "junk"); err == nil {
|
||||||
|
t.Fatal("should have errored - bad user id")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := th.BasicClient.GetTeamMember(th.BasicTeam.Id, "12345678901234567890123456"); err == nil {
|
||||||
|
t.Fatal("should have errored - bad user id")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetTeamMembersByIds(t *testing.T) {
|
||||||
|
th := Setup().InitBasic()
|
||||||
|
|
||||||
|
if result, err := th.BasicClient.GetTeamMembersByIds(th.BasicTeam.Id, []string{th.BasicUser.Id}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
member := result.Data.([]*model.TeamMember)[0]
|
||||||
|
if member.UserId != th.BasicUser.Id {
|
||||||
|
t.Fatal("user id did not match")
|
||||||
|
}
|
||||||
|
if member.TeamId != th.BasicTeam.Id {
|
||||||
|
t.Fatal("team id did not match")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if result, err := th.BasicClient.GetTeamMembersByIds(th.BasicTeam.Id, []string{th.BasicUser.Id, th.BasicUser2.Id, model.NewId()}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
members := result.Data.([]*model.TeamMember)
|
||||||
|
if len(members) != 2 {
|
||||||
|
t.Fatal("length should have been 2")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := th.BasicClient.GetTeamMembersByIds("junk", []string{th.BasicUser.Id}); err == nil {
|
||||||
|
t.Fatal("should have errored - bad team id")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := th.BasicClient.GetTeamMembersByIds(th.BasicTeam.Id, []string{}); err == nil {
|
||||||
|
t.Fatal("should have errored - empty user ids")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdateTeamMemberRoles(t *testing.T) {
|
func TestUpdateTeamMemberRoles(t *testing.T) {
|
||||||
@@ -632,3 +698,42 @@ func TestUpdateTeamMemberRoles(t *testing.T) {
|
|||||||
t.Fatal("Should have worked, user is team admin")
|
t.Fatal("Should have worked, user is team admin")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetTeamStats(t *testing.T) {
|
||||||
|
th := Setup().InitBasic().InitSystemAdmin()
|
||||||
|
Client := th.BasicClient
|
||||||
|
|
||||||
|
if result, err := th.SystemAdminClient.GetTeamStats(th.BasicTeam.Id); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
if result.Data.(*model.TeamStats).MemberCount != 2 {
|
||||||
|
t.Fatal("wrong count")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if result, err := th.SystemAdminClient.GetTeamStats("junk"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
if result.Data.(*model.TeamStats).MemberCount != 0 {
|
||||||
|
t.Fatal("wrong count")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if result, err := th.SystemAdminClient.GetTeamStats(th.BasicTeam.Id); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
if result.Data.(*model.TeamStats).MemberCount != 2 {
|
||||||
|
t.Fatal("wrong count")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
user := model.User{Email: "success+" + model.NewId() + "@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"}
|
||||||
|
ruser, _ := Client.CreateUser(&user, "")
|
||||||
|
store.Must(Srv.Store.User().VerifyEmail(ruser.Data.(*model.User).Id))
|
||||||
|
|
||||||
|
Client.Login(user.Email, user.Password)
|
||||||
|
|
||||||
|
if _, err := Client.GetTeamStats(th.BasicTeam.Id); err == nil {
|
||||||
|
t.Fatal("should have errored - not on team")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
305
api/user.go
305
api/user.go
@@ -53,9 +53,15 @@ func InitUser() {
|
|||||||
BaseRoutes.Users.Handle("/newimage", ApiUserRequired(uploadProfileImage)).Methods("POST")
|
BaseRoutes.Users.Handle("/newimage", ApiUserRequired(uploadProfileImage)).Methods("POST")
|
||||||
BaseRoutes.Users.Handle("/me", ApiUserRequired(getMe)).Methods("GET")
|
BaseRoutes.Users.Handle("/me", ApiUserRequired(getMe)).Methods("GET")
|
||||||
BaseRoutes.Users.Handle("/initial_load", ApiAppHandler(getInitialLoad)).Methods("GET")
|
BaseRoutes.Users.Handle("/initial_load", ApiAppHandler(getInitialLoad)).Methods("GET")
|
||||||
BaseRoutes.Users.Handle("/direct_profiles", ApiUserRequired(getDirectProfiles)).Methods("GET")
|
BaseRoutes.Users.Handle("/{offset:[0-9]+}/{limit:[0-9]+}", ApiUserRequired(getProfiles)).Methods("GET")
|
||||||
BaseRoutes.Users.Handle("/profiles/{id:[A-Za-z0-9]+}", ApiUserRequired(getProfiles)).Methods("GET")
|
BaseRoutes.NeedTeam.Handle("/users/{offset:[0-9]+}/{limit:[0-9]+}", ApiUserRequired(getProfilesInTeam)).Methods("GET")
|
||||||
BaseRoutes.Users.Handle("/profiles_for_dm_list/{id:[A-Za-z0-9]+}", ApiUserRequired(getProfilesForDirectMessageList)).Methods("GET")
|
BaseRoutes.NeedChannel.Handle("/users/{offset:[0-9]+}/{limit:[0-9]+}", ApiUserRequired(getProfilesInChannel)).Methods("GET")
|
||||||
|
BaseRoutes.NeedChannel.Handle("/users/not_in_channel/{offset:[0-9]+}/{limit:[0-9]+}", ApiUserRequired(getProfilesNotInChannel)).Methods("GET")
|
||||||
|
BaseRoutes.Users.Handle("/search", ApiUserRequired(searchUsers)).Methods("POST")
|
||||||
|
BaseRoutes.Users.Handle("/ids", ApiUserRequired(getProfilesByIds)).Methods("POST")
|
||||||
|
|
||||||
|
BaseRoutes.NeedTeam.Handle("/users/autocomplete", ApiUserRequired(autocompleteUsersInTeam)).Methods("GET")
|
||||||
|
BaseRoutes.NeedChannel.Handle("/users/autocomplete", ApiUserRequired(autocompleteUsersInChannel)).Methods("GET")
|
||||||
|
|
||||||
BaseRoutes.Users.Handle("/mfa", ApiAppHandler(checkMfa)).Methods("POST")
|
BaseRoutes.Users.Handle("/mfa", ApiAppHandler(checkMfa)).Methods("POST")
|
||||||
BaseRoutes.Users.Handle("/generate_mfa_qr", ApiUserRequiredTrustRequester(generateMfaQrCode)).Methods("GET")
|
BaseRoutes.Users.Handle("/generate_mfa_qr", ApiUserRequiredTrustRequester(generateMfaQrCode)).Methods("GET")
|
||||||
@@ -270,7 +276,9 @@ func CreateUser(user *model.User) (*model.User, *model.AppError) {
|
|||||||
ruser.Sanitize(map[string]bool{})
|
ruser.Sanitize(map[string]bool{})
|
||||||
|
|
||||||
// This message goes to everyone, so the teamId, channelId and userId are irrelevant
|
// This message goes to everyone, so the teamId, channelId and userId are irrelevant
|
||||||
go Publish(model.NewWebSocketEvent(model.WEBSOCKET_EVENT_NEW_USER, "", "", "", nil))
|
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_NEW_USER, "", "", "", nil)
|
||||||
|
message.Add("user_id", ruser.Id)
|
||||||
|
go Publish(message)
|
||||||
|
|
||||||
return ruser, nil
|
return ruser, nil
|
||||||
}
|
}
|
||||||
@@ -379,7 +387,7 @@ func sendWelcomeEmail(c *Context, userId string, email string, siteURL string, v
|
|||||||
|
|
||||||
func addDirectChannels(teamId string, user *model.User) {
|
func addDirectChannels(teamId string, user *model.User) {
|
||||||
var profiles map[string]*model.User
|
var profiles map[string]*model.User
|
||||||
if result := <-Srv.Store.User().GetProfiles(teamId); result.Err != nil {
|
if result := <-Srv.Store.User().GetProfiles(teamId, 0, 100); result.Err != nil {
|
||||||
l4g.Error(utils.T("api.user.add_direct_channels_and_forget.failed.error"), user.Id, teamId, result.Err.Error())
|
l4g.Error(utils.T("api.user.add_direct_channels_and_forget.failed.error"), user.Id, teamId, result.Err.Error())
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
@@ -875,7 +883,6 @@ func getInitialLoad(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
uchan := Srv.Store.User().Get(c.Session.UserId)
|
uchan := Srv.Store.User().Get(c.Session.UserId)
|
||||||
pchan := Srv.Store.Preference().GetAll(c.Session.UserId)
|
pchan := Srv.Store.Preference().GetAll(c.Session.UserId)
|
||||||
tchan := Srv.Store.Team().GetTeamsByUserId(c.Session.UserId)
|
tchan := Srv.Store.Team().GetTeamsByUserId(c.Session.UserId)
|
||||||
dpchan := Srv.Store.User().GetDirectProfiles(c.Session.UserId)
|
|
||||||
|
|
||||||
il.TeamMembers = c.Session.TeamMembers
|
il.TeamMembers = c.Session.TeamMembers
|
||||||
|
|
||||||
@@ -904,19 +911,6 @@ func getInitialLoad(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
team.Sanitize()
|
team.Sanitize()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if dp := <-dpchan; dp.Err != nil {
|
|
||||||
c.Err = dp.Err
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
profiles := dp.Data.(map[string]*model.User)
|
|
||||||
|
|
||||||
for k, p := range profiles {
|
|
||||||
profiles[k] = sanitizeProfile(c, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
il.DirectProfiles = profiles
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if cchan != nil {
|
if cchan != nil {
|
||||||
@@ -960,25 +954,27 @@ func getUser(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getProfilesForDirectMessageList(c *Context, w http.ResponseWriter, r *http.Request) {
|
func getProfiles(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
params := mux.Vars(r)
|
params := mux.Vars(r)
|
||||||
id := params["id"]
|
|
||||||
|
|
||||||
var pchan store.StoreChannel
|
offset, err := strconv.Atoi(params["offset"])
|
||||||
|
if err != nil {
|
||||||
if *utils.Cfg.TeamSettings.RestrictDirectMessage == model.DIRECT_MESSAGE_TEAM {
|
c.SetInvalidParam("getProfiles", "offset")
|
||||||
if c.Session.GetTeamByTeamId(id) == nil {
|
return
|
||||||
if !HasPermissionToContext(c, model.PERMISSION_MANAGE_SYSTEM) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pchan = Srv.Store.User().GetProfiles(id)
|
|
||||||
} else {
|
|
||||||
pchan = Srv.Store.User().GetAllProfiles()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if result := <-pchan; result.Err != nil {
|
limit, err := strconv.Atoi(params["limit"])
|
||||||
|
if err != nil {
|
||||||
|
c.SetInvalidParam("getProfiles", "limit")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
etag := (<-Srv.Store.User().GetEtagForAllProfiles()).Data.(string)
|
||||||
|
if HandleEtag(etag, w, r) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := <-Srv.Store.User().GetAllProfiles(offset, limit); result.Err != nil {
|
||||||
c.Err = result.Err
|
c.Err = result.Err
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
@@ -988,26 +984,39 @@ func getProfilesForDirectMessageList(c *Context, w http.ResponseWriter, r *http.
|
|||||||
profiles[k] = sanitizeProfile(c, p)
|
profiles[k] = sanitizeProfile(c, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
w.Header().Set(model.HEADER_ETAG_SERVER, etag)
|
||||||
w.Write([]byte(model.UserMapToJson(profiles)))
|
w.Write([]byte(model.UserMapToJson(profiles)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getProfiles(c *Context, w http.ResponseWriter, r *http.Request) {
|
func getProfilesInTeam(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
params := mux.Vars(r)
|
params := mux.Vars(r)
|
||||||
id := params["id"]
|
teamId := params["team_id"]
|
||||||
|
|
||||||
if c.Session.GetTeamByTeamId(id) == nil {
|
if c.Session.GetTeamByTeamId(teamId) == nil {
|
||||||
if !HasPermissionToContext(c, model.PERMISSION_MANAGE_SYSTEM) {
|
if !HasPermissionToContext(c, model.PERMISSION_MANAGE_SYSTEM) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
etag := (<-Srv.Store.User().GetEtagForProfiles(id)).Data.(string)
|
offset, err := strconv.Atoi(params["offset"])
|
||||||
|
if err != nil {
|
||||||
|
c.SetInvalidParam("getProfilesInTeam", "offset")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
limit, err := strconv.Atoi(params["limit"])
|
||||||
|
if err != nil {
|
||||||
|
c.SetInvalidParam("getProfilesInTeam", "limit")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
etag := (<-Srv.Store.User().GetEtagForProfiles(teamId)).Data.(string)
|
||||||
if HandleEtag(etag, w, r) {
|
if HandleEtag(etag, w, r) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if result := <-Srv.Store.User().GetProfiles(id); result.Err != nil {
|
if result := <-Srv.Store.User().GetProfiles(teamId, offset, limit); result.Err != nil {
|
||||||
c.Err = result.Err
|
c.Err = result.Err
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
@@ -1022,13 +1031,73 @@ func getProfiles(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDirectProfiles(c *Context, w http.ResponseWriter, r *http.Request) {
|
func getProfilesInChannel(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
etag := (<-Srv.Store.User().GetEtagForDirectProfiles(c.Session.UserId)).Data.(string)
|
params := mux.Vars(r)
|
||||||
if HandleEtag(etag, w, r) {
|
channelId := params["channel_id"]
|
||||||
|
|
||||||
|
if c.Session.GetTeamByTeamId(c.TeamId) == nil {
|
||||||
|
if !HasPermissionToContext(c, model.PERMISSION_MANAGE_SYSTEM) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !HasPermissionToChannelContext(c, channelId, model.PERMISSION_READ_CHANNEL) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if result := <-Srv.Store.User().GetDirectProfiles(c.Session.UserId); result.Err != nil {
|
offset, err := strconv.Atoi(params["offset"])
|
||||||
|
if err != nil {
|
||||||
|
c.SetInvalidParam("getProfiles", "offset")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
limit, err := strconv.Atoi(params["limit"])
|
||||||
|
if err != nil {
|
||||||
|
c.SetInvalidParam("getProfiles", "limit")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := <-Srv.Store.User().GetProfilesInChannel(channelId, offset, limit, false); result.Err != nil {
|
||||||
|
c.Err = result.Err
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
profiles := result.Data.(map[string]*model.User)
|
||||||
|
|
||||||
|
for k, p := range profiles {
|
||||||
|
profiles[k] = sanitizeProfile(c, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write([]byte(model.UserMapToJson(profiles)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getProfilesNotInChannel(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
params := mux.Vars(r)
|
||||||
|
channelId := params["channel_id"]
|
||||||
|
|
||||||
|
if c.Session.GetTeamByTeamId(c.TeamId) == nil {
|
||||||
|
if !HasPermissionToContext(c, model.PERMISSION_MANAGE_SYSTEM) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !HasPermissionToChannelContext(c, channelId, model.PERMISSION_READ_CHANNEL) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
offset, err := strconv.Atoi(params["offset"])
|
||||||
|
if err != nil {
|
||||||
|
c.SetInvalidParam("getProfiles", "offset")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
limit, err := strconv.Atoi(params["limit"])
|
||||||
|
if err != nil {
|
||||||
|
c.SetInvalidParam("getProfiles", "limit")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := <-Srv.Store.User().GetProfilesNotInChannel(c.TeamId, channelId, offset, limit); result.Err != nil {
|
||||||
c.Err = result.Err
|
c.Err = result.Err
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
@@ -1038,7 +1107,6 @@ func getDirectProfiles(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
profiles[k] = sanitizeProfile(c, p)
|
profiles[k] = sanitizeProfile(c, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set(model.HEADER_ETAG_SERVER, etag)
|
|
||||||
w.Write([]byte(model.UserMapToJson(profiles)))
|
w.Write([]byte(model.UserMapToJson(profiles)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2522,3 +2590,152 @@ func sanitizeProfile(c *Context, user *model.User) *model.User {
|
|||||||
|
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func searchUsers(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
props := model.MapFromJson(r.Body)
|
||||||
|
|
||||||
|
term := props["term"]
|
||||||
|
if len(term) == 0 {
|
||||||
|
c.SetInvalidParam("searchUsers", "term")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
teamId := props["team_id"]
|
||||||
|
inChannelId := props["in_channel"]
|
||||||
|
notInChannelId := props["not_in_channel"]
|
||||||
|
|
||||||
|
if inChannelId != "" && !HasPermissionToChannelContext(c, inChannelId, model.PERMISSION_READ_CHANNEL) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if notInChannelId != "" && !HasPermissionToChannelContext(c, notInChannelId, model.PERMISSION_READ_CHANNEL) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var uchan store.StoreChannel
|
||||||
|
if inChannelId != "" {
|
||||||
|
uchan = Srv.Store.User().SearchInChannel(inChannelId, term, store.USER_SEARCH_TYPE_USERNAME)
|
||||||
|
} else if notInChannelId != "" {
|
||||||
|
uchan = Srv.Store.User().SearchNotInChannel(teamId, notInChannelId, term, store.USER_SEARCH_TYPE_USERNAME)
|
||||||
|
} else {
|
||||||
|
uchan = Srv.Store.User().Search(teamId, term, store.USER_SEARCH_TYPE_USERNAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := <-uchan; result.Err != nil {
|
||||||
|
c.Err = result.Err
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
profiles := result.Data.([]*model.User)
|
||||||
|
|
||||||
|
for _, p := range profiles {
|
||||||
|
sanitizeProfile(c, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write([]byte(model.UserListToJson(profiles)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getProfilesByIds(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
userIds := model.ArrayFromJson(r.Body)
|
||||||
|
|
||||||
|
if len(userIds) == 0 {
|
||||||
|
c.SetInvalidParam("getProfilesByIds", "user_ids")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := <-Srv.Store.User().GetProfileByIds(userIds); result.Err != nil {
|
||||||
|
c.Err = result.Err
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
profiles := result.Data.(map[string]*model.User)
|
||||||
|
|
||||||
|
for _, p := range profiles {
|
||||||
|
sanitizeProfile(c, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write([]byte(model.UserMapToJson(profiles)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func autocompleteUsersInChannel(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
params := mux.Vars(r)
|
||||||
|
channelId := params["channel_id"]
|
||||||
|
teamId := params["team_id"]
|
||||||
|
|
||||||
|
term := r.URL.Query().Get("term")
|
||||||
|
|
||||||
|
if c.Session.GetTeamByTeamId(teamId) == nil {
|
||||||
|
if !HasPermissionToContext(c, model.PERMISSION_MANAGE_SYSTEM) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !HasPermissionToChannelContext(c, channelId, model.PERMISSION_READ_CHANNEL) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uchan := Srv.Store.User().SearchInChannel(channelId, term, store.USER_SEARCH_TYPE_ALL)
|
||||||
|
nuchan := Srv.Store.User().SearchNotInChannel(teamId, channelId, term, store.USER_SEARCH_TYPE_ALL)
|
||||||
|
|
||||||
|
autocomplete := &model.UserAutocompleteInChannel{}
|
||||||
|
|
||||||
|
if result := <-uchan; result.Err != nil {
|
||||||
|
c.Err = result.Err
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
profiles := result.Data.([]*model.User)
|
||||||
|
|
||||||
|
for _, p := range profiles {
|
||||||
|
sanitizeProfile(c, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
autocomplete.InChannel = profiles
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := <-nuchan; result.Err != nil {
|
||||||
|
c.Err = result.Err
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
profiles := result.Data.([]*model.User)
|
||||||
|
|
||||||
|
for _, p := range profiles {
|
||||||
|
sanitizeProfile(c, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
autocomplete.OutOfChannel = profiles
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write([]byte(autocomplete.ToJson()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func autocompleteUsersInTeam(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
params := mux.Vars(r)
|
||||||
|
teamId := params["team_id"]
|
||||||
|
|
||||||
|
term := r.URL.Query().Get("term")
|
||||||
|
|
||||||
|
if c.Session.GetTeamByTeamId(teamId) == nil {
|
||||||
|
if !HasPermissionToContext(c, model.PERMISSION_MANAGE_SYSTEM) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uchan := Srv.Store.User().Search(teamId, term, store.USER_SEARCH_TYPE_ALL)
|
||||||
|
|
||||||
|
autocomplete := &model.UserAutocompleteInTeam{}
|
||||||
|
|
||||||
|
if result := <-uchan; result.Err != nil {
|
||||||
|
c.Err = result.Err
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
profiles := result.Data.([]*model.User)
|
||||||
|
|
||||||
|
for _, p := range profiles {
|
||||||
|
sanitizeProfile(c, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
autocomplete.InTeam = profiles
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write([]byte(autocomplete.ToJson()))
|
||||||
|
}
|
||||||
|
|||||||
514
api/user_test.go
514
api/user_test.go
@@ -435,7 +435,7 @@ func TestGetUser(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if userMap, err := Client.GetProfiles(rteam.Data.(*model.Team).Id, ""); err != nil {
|
if userMap, err := Client.GetProfilesInTeam(rteam.Data.(*model.Team).Id, 0, 100, ""); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
} else if len(userMap.Data.(map[string]*model.User)) != 2 {
|
} else if len(userMap.Data.(map[string]*model.User)) != 2 {
|
||||||
t.Fatal("should have been 2")
|
t.Fatal("should have been 2")
|
||||||
@@ -444,7 +444,7 @@ func TestGetUser(t *testing.T) {
|
|||||||
} else {
|
} else {
|
||||||
|
|
||||||
// test etag caching
|
// test etag caching
|
||||||
if cache_result, err := Client.GetProfiles(rteam.Data.(*model.Team).Id, userMap.Etag); err != nil {
|
if cache_result, err := Client.GetProfilesInTeam(rteam.Data.(*model.Team).Id, 0, 100, userMap.Etag); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
} else if cache_result.Data.(map[string]*model.User) != nil {
|
} else if cache_result.Data.(map[string]*model.User) != nil {
|
||||||
t.Log(cache_result.Data)
|
t.Log(cache_result.Data)
|
||||||
@@ -452,7 +452,25 @@ func TestGetUser(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := Client.GetProfiles(rteam2.Data.(*model.Team).Id, ""); err == nil {
|
if userMap, err := Client.GetProfilesInTeam(rteam.Data.(*model.Team).Id, 0, 1, ""); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if len(userMap.Data.(map[string]*model.User)) != 1 {
|
||||||
|
t.Fatal("should have been 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
if userMap, err := Client.GetProfilesInTeam(rteam.Data.(*model.Team).Id, 1, 1, ""); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if len(userMap.Data.(map[string]*model.User)) != 1 {
|
||||||
|
t.Fatal("should have been 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
if userMap, err := Client.GetProfilesInTeam(rteam.Data.(*model.Team).Id, 10, 10, ""); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if len(userMap.Data.(map[string]*model.User)) != 0 {
|
||||||
|
t.Fatal("should have been 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := Client.GetProfilesInTeam(rteam2.Data.(*model.Team).Id, 0, 100, ""); err == nil {
|
||||||
t.Fatal("shouldn't have access")
|
t.Fatal("shouldn't have access")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -468,12 +486,12 @@ func TestGetUser(t *testing.T) {
|
|||||||
|
|
||||||
Client.Login(user.Email, "passwd1")
|
Client.Login(user.Email, "passwd1")
|
||||||
|
|
||||||
if _, err := Client.GetProfiles(rteam2.Data.(*model.Team).Id, ""); err != nil {
|
if _, err := Client.GetProfilesInTeam(rteam2.Data.(*model.Team).Id, 0, 100, ""); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetDirectProfiles(t *testing.T) {
|
func TestGetProfiles(t *testing.T) {
|
||||||
th := Setup().InitBasic()
|
th := Setup().InitBasic()
|
||||||
|
|
||||||
th.BasicClient.Must(th.BasicClient.CreateDirectChannel(th.BasicUser2.Id))
|
th.BasicClient.Must(th.BasicClient.CreateDirectChannel(th.BasicUser2.Id))
|
||||||
@@ -485,62 +503,7 @@ func TestGetDirectProfiles(t *testing.T) {
|
|||||||
|
|
||||||
utils.Cfg.PrivacySettings.ShowEmailAddress = true
|
utils.Cfg.PrivacySettings.ShowEmailAddress = true
|
||||||
|
|
||||||
if result, err := th.BasicClient.GetDirectProfiles(""); err != nil {
|
if result, err := th.BasicClient.GetProfiles(0, 100, ""); err != nil {
|
||||||
t.Fatal(err)
|
|
||||||
} else {
|
|
||||||
users := result.Data.(map[string]*model.User)
|
|
||||||
|
|
||||||
if len(users) != 1 {
|
|
||||||
t.Fatal("map was wrong length")
|
|
||||||
}
|
|
||||||
|
|
||||||
if users[th.BasicUser2.Id] == nil {
|
|
||||||
t.Fatal("missing expected user")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, user := range users {
|
|
||||||
if user.Email == "" {
|
|
||||||
t.Fatal("problem with show email")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
utils.Cfg.PrivacySettings.ShowEmailAddress = false
|
|
||||||
|
|
||||||
if result, err := th.BasicClient.GetDirectProfiles(""); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
} else {
|
|
||||||
users := result.Data.(map[string]*model.User)
|
|
||||||
|
|
||||||
if len(users) != 1 {
|
|
||||||
t.Fatal("map was wrong length")
|
|
||||||
}
|
|
||||||
|
|
||||||
if users[th.BasicUser2.Id] == nil {
|
|
||||||
t.Fatal("missing expected user")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, user := range users {
|
|
||||||
if user.Email != "" {
|
|
||||||
t.Fatal("problem with show email")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetProfilesForDirectMessageList(t *testing.T) {
|
|
||||||
th := Setup().InitBasic()
|
|
||||||
|
|
||||||
th.BasicClient.Must(th.BasicClient.CreateDirectChannel(th.BasicUser2.Id))
|
|
||||||
|
|
||||||
prevShowEmail := utils.Cfg.PrivacySettings.ShowEmailAddress
|
|
||||||
defer func() {
|
|
||||||
utils.Cfg.PrivacySettings.ShowEmailAddress = prevShowEmail
|
|
||||||
}()
|
|
||||||
|
|
||||||
utils.Cfg.PrivacySettings.ShowEmailAddress = true
|
|
||||||
|
|
||||||
if result, err := th.BasicClient.GetProfilesForDirectMessageList(th.BasicTeam.Id); err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
} else {
|
} else {
|
||||||
users := result.Data.(map[string]*model.User)
|
users := result.Data.(map[string]*model.User)
|
||||||
@@ -554,11 +517,20 @@ func TestGetProfilesForDirectMessageList(t *testing.T) {
|
|||||||
t.Fatal("problem with show email")
|
t.Fatal("problem with show email")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// test etag caching
|
||||||
|
if cache_result, err := th.BasicClient.GetProfiles(0, 100, result.Etag); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if cache_result.Data.(map[string]*model.User) != nil {
|
||||||
|
t.Log(cache_result.Etag)
|
||||||
|
t.Log(result.Etag)
|
||||||
|
t.Fatal("cache should be empty")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.Cfg.PrivacySettings.ShowEmailAddress = false
|
utils.Cfg.PrivacySettings.ShowEmailAddress = false
|
||||||
|
|
||||||
if result, err := th.BasicClient.GetProfilesForDirectMessageList(th.BasicTeam.Id); err != nil {
|
if result, err := th.BasicClient.GetProfiles(0, 100, ""); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
} else {
|
} else {
|
||||||
users := result.Data.(map[string]*model.User)
|
users := result.Data.(map[string]*model.User)
|
||||||
@@ -575,6 +547,61 @@ func TestGetProfilesForDirectMessageList(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetProfilesByIds(t *testing.T) {
|
||||||
|
th := Setup().InitBasic()
|
||||||
|
|
||||||
|
prevShowEmail := utils.Cfg.PrivacySettings.ShowEmailAddress
|
||||||
|
defer func() {
|
||||||
|
utils.Cfg.PrivacySettings.ShowEmailAddress = prevShowEmail
|
||||||
|
}()
|
||||||
|
|
||||||
|
utils.Cfg.PrivacySettings.ShowEmailAddress = true
|
||||||
|
|
||||||
|
if result, err := th.BasicClient.GetProfilesByIds([]string{th.BasicUser.Id}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
users := result.Data.(map[string]*model.User)
|
||||||
|
|
||||||
|
if len(users) != 1 {
|
||||||
|
t.Fatal("map was wrong length")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, user := range users {
|
||||||
|
if user.Email == "" {
|
||||||
|
t.Fatal("problem with show email")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.Cfg.PrivacySettings.ShowEmailAddress = false
|
||||||
|
|
||||||
|
if result, err := th.BasicClient.GetProfilesByIds([]string{th.BasicUser.Id}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
users := result.Data.(map[string]*model.User)
|
||||||
|
|
||||||
|
if len(users) != 1 {
|
||||||
|
t.Fatal("map was wrong length")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, user := range users {
|
||||||
|
if user.Email != "" {
|
||||||
|
t.Fatal("problem with show email")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if result, err := th.BasicClient.GetProfilesByIds([]string{th.BasicUser.Id, th.BasicUser2.Id}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
users := result.Data.(map[string]*model.User)
|
||||||
|
|
||||||
|
if len(users) != 2 {
|
||||||
|
t.Fatal("map was wrong length")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetAudits(t *testing.T) {
|
func TestGetAudits(t *testing.T) {
|
||||||
th := Setup()
|
th := Setup()
|
||||||
Client := th.CreateClient()
|
Client := th.CreateClient()
|
||||||
@@ -1837,3 +1864,366 @@ func TestUserTyping(t *testing.T) {
|
|||||||
t.Fatal("did not receive typing event")
|
t.Fatal("did not receive typing event")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetProfilesInChannel(t *testing.T) {
|
||||||
|
th := Setup().InitBasic()
|
||||||
|
Client := th.BasicClient
|
||||||
|
|
||||||
|
prevShowEmail := utils.Cfg.PrivacySettings.ShowEmailAddress
|
||||||
|
defer func() {
|
||||||
|
utils.Cfg.PrivacySettings.ShowEmailAddress = prevShowEmail
|
||||||
|
}()
|
||||||
|
|
||||||
|
utils.Cfg.PrivacySettings.ShowEmailAddress = true
|
||||||
|
|
||||||
|
if result, err := Client.GetProfilesInChannel(th.BasicChannel.Id, 0, 100, ""); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
users := result.Data.(map[string]*model.User)
|
||||||
|
|
||||||
|
if len(users) < 1 {
|
||||||
|
t.Fatal("map was wrong length")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, user := range users {
|
||||||
|
if user.Email == "" {
|
||||||
|
t.Fatal("problem with show email")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
th.LoginBasic2()
|
||||||
|
|
||||||
|
if _, err := Client.GetProfilesInChannel(th.BasicChannel.Id, 0, 100, ""); err == nil {
|
||||||
|
t.Fatal("should not have access")
|
||||||
|
}
|
||||||
|
|
||||||
|
Client.Must(Client.JoinChannel(th.BasicChannel.Id))
|
||||||
|
|
||||||
|
utils.Cfg.PrivacySettings.ShowEmailAddress = false
|
||||||
|
|
||||||
|
if result, err := Client.GetProfilesInChannel(th.BasicChannel.Id, 0, 100, ""); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
users := result.Data.(map[string]*model.User)
|
||||||
|
|
||||||
|
if len(users) < 1 {
|
||||||
|
t.Fatal("map was wrong length")
|
||||||
|
}
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, user := range users {
|
||||||
|
if user.Email != "" {
|
||||||
|
t.Fatal("problem with show email")
|
||||||
|
}
|
||||||
|
if user.Id == th.BasicUser2.Id {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
t.Fatal("should have found profile")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"}
|
||||||
|
Client.Must(Client.CreateUser(&user, ""))
|
||||||
|
|
||||||
|
Client.Login(user.Email, "passwd1")
|
||||||
|
Client.SetTeamId("junk")
|
||||||
|
|
||||||
|
if _, err := Client.GetProfilesInChannel(th.BasicChannel.Id, 0, 100, ""); err == nil {
|
||||||
|
t.Fatal("should not have access")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetProfilesNotInChannel(t *testing.T) {
|
||||||
|
th := Setup().InitBasic()
|
||||||
|
Client := th.BasicClient
|
||||||
|
|
||||||
|
prevShowEmail := utils.Cfg.PrivacySettings.ShowEmailAddress
|
||||||
|
defer func() {
|
||||||
|
utils.Cfg.PrivacySettings.ShowEmailAddress = prevShowEmail
|
||||||
|
}()
|
||||||
|
|
||||||
|
utils.Cfg.PrivacySettings.ShowEmailAddress = true
|
||||||
|
|
||||||
|
if result, err := Client.GetProfilesNotInChannel(th.BasicChannel.Id, 0, 100, ""); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
users := result.Data.(map[string]*model.User)
|
||||||
|
|
||||||
|
if len(users) < 1 {
|
||||||
|
t.Fatal("map was wrong length")
|
||||||
|
}
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, user := range users {
|
||||||
|
if user.Email == "" {
|
||||||
|
t.Fatal("problem with show email")
|
||||||
|
}
|
||||||
|
if user.Id == th.BasicUser2.Id {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
t.Fatal("should have found profile")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
user := &model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"}
|
||||||
|
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
|
||||||
|
LinkUserToTeam(user, th.BasicTeam)
|
||||||
|
|
||||||
|
th.LoginBasic2()
|
||||||
|
|
||||||
|
if _, err := Client.GetProfilesNotInChannel(th.BasicChannel.Id, 0, 100, ""); err == nil {
|
||||||
|
t.Fatal("should not have access")
|
||||||
|
}
|
||||||
|
|
||||||
|
Client.Must(Client.JoinChannel(th.BasicChannel.Id))
|
||||||
|
|
||||||
|
utils.Cfg.PrivacySettings.ShowEmailAddress = false
|
||||||
|
|
||||||
|
if result, err := Client.GetProfilesNotInChannel(th.BasicChannel.Id, 0, 100, ""); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
users := result.Data.(map[string]*model.User)
|
||||||
|
|
||||||
|
if len(users) < 1 {
|
||||||
|
t.Fatal("map was wrong length")
|
||||||
|
}
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, user := range users {
|
||||||
|
if user.Email != "" {
|
||||||
|
t.Fatal("problem with show email")
|
||||||
|
}
|
||||||
|
if user.Id == th.BasicUser2.Id {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if found {
|
||||||
|
t.Fatal("should not have found profile")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"}
|
||||||
|
Client.Must(Client.CreateUser(&user2, ""))
|
||||||
|
|
||||||
|
Client.Login(user2.Email, "passwd1")
|
||||||
|
Client.SetTeamId(th.BasicTeam.Id)
|
||||||
|
|
||||||
|
if _, err := Client.GetProfilesNotInChannel(th.BasicChannel.Id, 0, 100, ""); err == nil {
|
||||||
|
t.Fatal("should not have access")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSearchUsers(t *testing.T) {
|
||||||
|
th := Setup().InitBasic()
|
||||||
|
Client := th.BasicClient
|
||||||
|
|
||||||
|
if result, err := Client.SearchUsers(th.BasicUser.Username, "", map[string]string{}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
users := result.Data.([]*model.User)
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, user := range users {
|
||||||
|
if user.Id == th.BasicUser.Id {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
t.Fatal("should have found profile")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if result, err := Client.SearchUsers(th.BasicUser.Username, "", map[string]string{"in_channel": th.BasicChannel.Id}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
users := result.Data.([]*model.User)
|
||||||
|
|
||||||
|
if len(users) != 1 {
|
||||||
|
t.Fatal("map was wrong length")
|
||||||
|
}
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, user := range users {
|
||||||
|
if user.Id == th.BasicUser.Id {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
t.Fatal("should have found profile")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if result, err := Client.SearchUsers(th.BasicUser2.Username, "", map[string]string{"not_in_channel": th.BasicChannel.Id}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
users := result.Data.([]*model.User)
|
||||||
|
|
||||||
|
if len(users) != 1 {
|
||||||
|
t.Fatal("map was wrong length")
|
||||||
|
}
|
||||||
|
|
||||||
|
found1 := false
|
||||||
|
found2 := false
|
||||||
|
for _, user := range users {
|
||||||
|
if user.Id == th.BasicUser.Id {
|
||||||
|
found1 = true
|
||||||
|
} else if user.Id == th.BasicUser2.Id {
|
||||||
|
found2 = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if found1 {
|
||||||
|
t.Fatal("should not have found profile")
|
||||||
|
}
|
||||||
|
if !found2 {
|
||||||
|
t.Fatal("should have found profile")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if result, err := Client.SearchUsers(th.BasicUser2.Username, th.BasicTeam.Id, map[string]string{"not_in_channel": th.BasicChannel.Id}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
users := result.Data.([]*model.User)
|
||||||
|
|
||||||
|
if len(users) != 1 {
|
||||||
|
t.Fatal("map was wrong length")
|
||||||
|
}
|
||||||
|
|
||||||
|
found1 := false
|
||||||
|
found2 := false
|
||||||
|
for _, user := range users {
|
||||||
|
if user.Id == th.BasicUser.Id {
|
||||||
|
found1 = true
|
||||||
|
} else if user.Id == th.BasicUser2.Id {
|
||||||
|
found2 = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if found1 {
|
||||||
|
t.Fatal("should not have found profile")
|
||||||
|
}
|
||||||
|
if !found2 {
|
||||||
|
t.Fatal("should have found profile")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if result, err := Client.SearchUsers(th.BasicUser.Username, "junk", map[string]string{"not_in_channel": th.BasicChannel.Id}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
users := result.Data.([]*model.User)
|
||||||
|
|
||||||
|
if len(users) != 0 {
|
||||||
|
t.Fatal("map was wrong length")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
th.LoginBasic2()
|
||||||
|
|
||||||
|
if result, err := Client.SearchUsers(th.BasicUser.Username, "", map[string]string{}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
users := result.Data.([]*model.User)
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, user := range users {
|
||||||
|
if user.Id == th.BasicUser.Id {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
t.Fatal("should have found profile")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := Client.SearchUsers("", "", map[string]string{}); err == nil {
|
||||||
|
t.Fatal("should have errored - blank term")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := Client.SearchUsers(th.BasicUser.Username, "", map[string]string{"in_channel": th.BasicChannel.Id}); err == nil {
|
||||||
|
t.Fatal("should not have access")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := Client.SearchUsers(th.BasicUser.Username, "", map[string]string{"not_in_channel": th.BasicChannel.Id}); err == nil {
|
||||||
|
t.Fatal("should not have access")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAutocompleteUsers(t *testing.T) {
|
||||||
|
th := Setup().InitBasic()
|
||||||
|
Client := th.BasicClient
|
||||||
|
|
||||||
|
if result, err := Client.AutocompleteUsersInTeam(th.BasicUser.Username); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
autocomplete := result.Data.(*model.UserAutocompleteInTeam)
|
||||||
|
if len(autocomplete.InTeam) != 1 {
|
||||||
|
t.Fatal("should have returned 1 user in")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if result, err := Client.AutocompleteUsersInTeam(th.BasicUser.Username[0:5]); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
autocomplete := result.Data.(*model.UserAutocompleteInTeam)
|
||||||
|
if len(autocomplete.InTeam) < 1 {
|
||||||
|
t.Fatal("should have returned at least 1 user in")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if result, err := Client.AutocompleteUsersInChannel(th.BasicUser.Username, th.BasicChannel.Id); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
autocomplete := result.Data.(*model.UserAutocompleteInChannel)
|
||||||
|
if len(autocomplete.InChannel) != 1 {
|
||||||
|
t.Fatal("should have returned 1 user in")
|
||||||
|
}
|
||||||
|
if len(autocomplete.OutOfChannel) != 0 {
|
||||||
|
t.Fatal("should have returned no users out")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if result, err := Client.AutocompleteUsersInChannel("", th.BasicChannel.Id); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
autocomplete := result.Data.(*model.UserAutocompleteInChannel)
|
||||||
|
if len(autocomplete.InChannel) != 1 && autocomplete.InChannel[0].Id != th.BasicUser2.Id {
|
||||||
|
t.Fatal("should have returned at 1 user in")
|
||||||
|
}
|
||||||
|
if len(autocomplete.OutOfChannel) != 1 && autocomplete.OutOfChannel[0].Id != th.BasicUser2.Id {
|
||||||
|
t.Fatal("should have returned 1 user out")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if result, err := Client.AutocompleteUsersInTeam(""); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
autocomplete := result.Data.(*model.UserAutocompleteInTeam)
|
||||||
|
if len(autocomplete.InTeam) != 2 {
|
||||||
|
t.Fatal("should have returned 2 users in")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := Client.AutocompleteUsersInChannel("", "junk"); err == nil {
|
||||||
|
t.Fatal("should have errored - bad channel id")
|
||||||
|
}
|
||||||
|
|
||||||
|
Client.SetTeamId("junk")
|
||||||
|
if _, err := Client.AutocompleteUsersInChannel("", th.BasicChannel.Id); err == nil {
|
||||||
|
t.Fatal("should have errored - bad team id")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := Client.AutocompleteUsersInTeam(""); err == nil {
|
||||||
|
t.Fatal("should have errored - bad team id")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
155
api/web_conn.go
155
api/web_conn.go
@@ -4,54 +4,52 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/mattermost/platform/model"
|
"github.com/mattermost/platform/model"
|
||||||
|
|
||||||
|
l4g "github.com/alecthomas/log4go"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
goi18n "github.com/nicksnyder/go-i18n/i18n"
|
goi18n "github.com/nicksnyder/go-i18n/i18n"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
WRITE_WAIT = 10 * time.Second
|
WRITE_WAIT = 30 * time.Second
|
||||||
PONG_WAIT = 60 * time.Second
|
PONG_WAIT = 100 * time.Second
|
||||||
PING_PERIOD = (PONG_WAIT * 9) / 10
|
PING_PERIOD = (PONG_WAIT * 6) / 10
|
||||||
MAX_SIZE = 512
|
|
||||||
REDIS_WAIT = 60 * time.Second
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type WebConn struct {
|
type WebConn struct {
|
||||||
WebSocket *websocket.Conn
|
WebSocket *websocket.Conn
|
||||||
Send chan model.WebSocketMessage
|
Send chan model.WebSocketMessage
|
||||||
SessionToken string
|
SessionToken string
|
||||||
UserId string
|
UserId string
|
||||||
T goi18n.TranslateFunc
|
T goi18n.TranslateFunc
|
||||||
Locale string
|
Locale string
|
||||||
isMemberOfChannel map[string]bool
|
AllChannelMembers map[string]string
|
||||||
isMemberOfTeam map[string]bool
|
LastAllChannelMembersTime int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWebConn(c *Context, ws *websocket.Conn) *WebConn {
|
func NewWebConn(c *Context, ws *websocket.Conn) *WebConn {
|
||||||
go SetStatusOnline(c.Session.UserId, c.Session.Id, false)
|
go SetStatusOnline(c.Session.UserId, c.Session.Id, false)
|
||||||
|
|
||||||
return &WebConn{
|
return &WebConn{
|
||||||
Send: make(chan model.WebSocketMessage, 64),
|
Send: make(chan model.WebSocketMessage, 256),
|
||||||
WebSocket: ws,
|
WebSocket: ws,
|
||||||
UserId: c.Session.UserId,
|
UserId: c.Session.UserId,
|
||||||
SessionToken: c.Session.Token,
|
SessionToken: c.Session.Token,
|
||||||
T: c.T,
|
T: c.T,
|
||||||
Locale: c.Locale,
|
Locale: c.Locale,
|
||||||
isMemberOfChannel: make(map[string]bool),
|
|
||||||
isMemberOfTeam: make(map[string]bool),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *WebConn) readPump() {
|
func (c *WebConn) readPump() {
|
||||||
defer func() {
|
defer func() {
|
||||||
hub.Unregister(c)
|
HubUnregister(c)
|
||||||
c.WebSocket.Close()
|
c.WebSocket.Close()
|
||||||
}()
|
}()
|
||||||
c.WebSocket.SetReadLimit(MAX_SIZE)
|
c.WebSocket.SetReadLimit(SOCKET_MAX_MESSAGE_SIZE_KB)
|
||||||
c.WebSocket.SetReadDeadline(time.Now().Add(PONG_WAIT))
|
c.WebSocket.SetReadDeadline(time.Now().Add(PONG_WAIT))
|
||||||
c.WebSocket.SetPongHandler(func(string) error {
|
c.WebSocket.SetPongHandler(func(string) error {
|
||||||
c.WebSocket.SetReadDeadline(time.Now().Add(PONG_WAIT))
|
c.WebSocket.SetReadDeadline(time.Now().Add(PONG_WAIT))
|
||||||
@@ -62,6 +60,13 @@ func (c *WebConn) readPump() {
|
|||||||
for {
|
for {
|
||||||
var req model.WebSocketRequest
|
var req model.WebSocketRequest
|
||||||
if err := c.WebSocket.ReadJSON(&req); err != nil {
|
if err := c.WebSocket.ReadJSON(&req); err != nil {
|
||||||
|
// browsers will appear as CloseNoStatusReceived
|
||||||
|
if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) {
|
||||||
|
l4g.Debug(fmt.Sprintf("websocket.read: client side closed socket userId=%v", c.UserId))
|
||||||
|
} else {
|
||||||
|
l4g.Debug(fmt.Sprintf("websocket.read: cannot read, closing websocket for userId=%v error=%v", c.UserId, err.Error()))
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
BaseRoutes.WebSocket.ServeWebSocket(c, &req)
|
BaseRoutes.WebSocket.ServeWebSocket(c, &req)
|
||||||
@@ -87,63 +92,97 @@ func (c *WebConn) writePump() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.WebSocket.SetWriteDeadline(time.Now().Add(WRITE_WAIT))
|
c.WebSocket.SetWriteDeadline(time.Now().Add(WRITE_WAIT))
|
||||||
if err := c.WebSocket.WriteJSON(msg); err != nil {
|
if err := c.WebSocket.WriteMessage(websocket.TextMessage, msg.GetPreComputeJson()); err != nil {
|
||||||
|
// browsers will appear as CloseNoStatusReceived
|
||||||
|
if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) {
|
||||||
|
l4g.Debug(fmt.Sprintf("websocket.send: client side closed socket userId=%v", c.UserId))
|
||||||
|
} else {
|
||||||
|
l4g.Debug(fmt.Sprintf("websocket.send: cannot send, closing websocket for userId=%v, error=%v", c.UserId, err.Error()))
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
c.WebSocket.SetWriteDeadline(time.Now().Add(WRITE_WAIT))
|
c.WebSocket.SetWriteDeadline(time.Now().Add(WRITE_WAIT))
|
||||||
if err := c.WebSocket.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
|
if err := c.WebSocket.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
|
||||||
|
// browsers will appear as CloseNoStatusReceived
|
||||||
|
if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) {
|
||||||
|
l4g.Debug(fmt.Sprintf("websocket.ticker: client side closed socket userId=%v", c.UserId))
|
||||||
|
} else {
|
||||||
|
l4g.Debug(fmt.Sprintf("websocket.ticker: cannot read, closing websocket for userId=%v error=%v", c.UserId, err.Error()))
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *WebConn) InvalidateCache() {
|
func (webCon *WebConn) InvalidateCache() {
|
||||||
c.isMemberOfTeam = make(map[string]bool)
|
webCon.AllChannelMembers = nil
|
||||||
c.isMemberOfChannel = make(map[string]bool)
|
webCon.LastAllChannelMembersTime = 0
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *WebConn) InvalidateCacheForChannel(channelId string) {
|
func (webCon *WebConn) ShouldSendEvent(msg *model.WebSocketEvent) bool {
|
||||||
delete(c.isMemberOfChannel, channelId)
|
// If the event is destined to a specific user
|
||||||
}
|
if len(msg.Broadcast.UserId) > 0 && webCon.UserId != msg.Broadcast.UserId {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (c *WebConn) IsMemberOfTeam(teamId string) bool {
|
// if the user is omitted don't send the message
|
||||||
isMember, ok := c.isMemberOfTeam[teamId]
|
if len(msg.Broadcast.OmitUsers) > 0 {
|
||||||
if !ok {
|
if _, ok := msg.Broadcast.OmitUsers[webCon.UserId]; ok {
|
||||||
session := GetSession(c.SessionToken)
|
return false
|
||||||
if session == nil {
|
}
|
||||||
isMember = false
|
}
|
||||||
c.isMemberOfTeam[teamId] = isMember
|
|
||||||
} else {
|
|
||||||
member := session.GetTeamByTeamId(teamId)
|
|
||||||
|
|
||||||
if member != nil {
|
// Only report events to users who are in the channel for the event
|
||||||
isMember = true
|
if len(msg.Broadcast.ChannelId) > 0 {
|
||||||
c.isMemberOfTeam[teamId] = isMember
|
|
||||||
|
if model.GetMillis()-webCon.LastAllChannelMembersTime > 1000*60*15 { // 15 minutes
|
||||||
|
webCon.AllChannelMembers = nil
|
||||||
|
webCon.LastAllChannelMembersTime = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if webCon.AllChannelMembers == nil {
|
||||||
|
if result := <-Srv.Store.Channel().GetAllChannelMembersForUser(webCon.UserId, true); result.Err != nil {
|
||||||
|
l4g.Error("webhub.shouldSendEvent: " + result.Err.Error())
|
||||||
|
return false
|
||||||
} else {
|
} else {
|
||||||
isMember = true
|
webCon.AllChannelMembers = result.Data.(map[string]string)
|
||||||
c.isMemberOfTeam[teamId] = isMember
|
webCon.LastAllChannelMembersTime = model.GetMillis()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return isMember
|
if _, ok := webCon.AllChannelMembers[msg.Broadcast.ChannelId]; ok {
|
||||||
}
|
return true
|
||||||
|
|
||||||
func (c *WebConn) IsMemberOfChannel(channelId string) bool {
|
|
||||||
isMember, ok := c.isMemberOfChannel[channelId]
|
|
||||||
if !ok {
|
|
||||||
if cresult := <-Srv.Store.Channel().GetMember(channelId, c.UserId); cresult.Err != nil {
|
|
||||||
isMember = false
|
|
||||||
c.isMemberOfChannel[channelId] = isMember
|
|
||||||
} else {
|
} else {
|
||||||
isMember = true
|
return false
|
||||||
c.isMemberOfChannel[channelId] = isMember
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return isMember
|
// Only report events to users who are in the team for the event
|
||||||
|
if len(msg.Broadcast.TeamId) > 0 {
|
||||||
|
return webCon.IsMemberOfTeam(msg.Broadcast.TeamId)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (webCon *WebConn) IsMemberOfTeam(teamId string) bool {
|
||||||
|
session := GetSession(webCon.SessionToken)
|
||||||
|
if session == nil {
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
member := session.GetTeamByTeamId(teamId)
|
||||||
|
|
||||||
|
if member != nil {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
152
api/web_hub.go
152
api/web_hub.go
@@ -5,6 +5,8 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"hash/fnv"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
l4g "github.com/alecthomas/log4go"
|
l4g "github.com/alecthomas/log4go"
|
||||||
|
|
||||||
@@ -14,27 +16,77 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Hub struct {
|
type Hub struct {
|
||||||
connections map[*WebConn]bool
|
connections map[*WebConn]bool
|
||||||
register chan *WebConn
|
register chan *WebConn
|
||||||
unregister chan *WebConn
|
unregister chan *WebConn
|
||||||
broadcast chan *model.WebSocketEvent
|
broadcast chan *model.WebSocketEvent
|
||||||
stop chan string
|
stop chan string
|
||||||
invalidateUser chan string
|
invalidateUser chan string
|
||||||
invalidateChannel chan string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var hub = &Hub{
|
var hubs []*Hub = make([]*Hub, 0)
|
||||||
register: make(chan *WebConn),
|
|
||||||
unregister: make(chan *WebConn),
|
func NewWebHub() *Hub {
|
||||||
connections: make(map[*WebConn]bool),
|
return &Hub{
|
||||||
broadcast: make(chan *model.WebSocketEvent),
|
register: make(chan *WebConn),
|
||||||
stop: make(chan string),
|
unregister: make(chan *WebConn),
|
||||||
invalidateUser: make(chan string),
|
connections: make(map[*WebConn]bool, model.SESSION_CACHE_SIZE),
|
||||||
invalidateChannel: make(chan string),
|
broadcast: make(chan *model.WebSocketEvent, 4096),
|
||||||
|
stop: make(chan string),
|
||||||
|
invalidateUser: make(chan string),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TotalWebsocketConnections() int {
|
||||||
|
// XXX TODO FIXME, this is racy and needs to be fixed
|
||||||
|
count := 0
|
||||||
|
for _, hub := range hubs {
|
||||||
|
count = count + len(hub.connections)
|
||||||
|
}
|
||||||
|
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
func HubStart() {
|
||||||
|
l4g.Info(utils.T("api.web_hub.start.starting.debug"), runtime.NumCPU()*2)
|
||||||
|
|
||||||
|
// Total number of hubs is twice the number of CPUs.
|
||||||
|
hubs = make([]*Hub, runtime.NumCPU()*2)
|
||||||
|
|
||||||
|
for i := 0; i < len(hubs); i++ {
|
||||||
|
hubs[i] = NewWebHub()
|
||||||
|
hubs[i].Start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func HubStop() {
|
||||||
|
l4g.Info(utils.T("api.web_hub.start.stopping.debug"))
|
||||||
|
|
||||||
|
for _, hub := range hubs {
|
||||||
|
hub.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
hubs = make([]*Hub, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func HubRegister(webConn *WebConn) {
|
||||||
|
hash := fnv.New32a()
|
||||||
|
hash.Write([]byte(webConn.UserId))
|
||||||
|
index := hash.Sum32() % uint32(len(hubs))
|
||||||
|
hubs[index].Register(webConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func HubUnregister(webConn *WebConn) {
|
||||||
|
for _, hub := range hubs {
|
||||||
|
hub.Unregister(webConn)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Publish(message *model.WebSocketEvent) {
|
func Publish(message *model.WebSocketEvent) {
|
||||||
hub.Broadcast(message)
|
message.DoPreComputeJson()
|
||||||
|
for _, hub := range hubs {
|
||||||
|
hub.Broadcast(message)
|
||||||
|
}
|
||||||
|
|
||||||
if einterfaces.GetClusterInterface() != nil {
|
if einterfaces.GetClusterInterface() != nil {
|
||||||
einterfaces.GetClusterInterface().Publish(message)
|
einterfaces.GetClusterInterface().Publish(message)
|
||||||
@@ -42,11 +94,19 @@ func Publish(message *model.WebSocketEvent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func PublishSkipClusterSend(message *model.WebSocketEvent) {
|
func PublishSkipClusterSend(message *model.WebSocketEvent) {
|
||||||
hub.Broadcast(message)
|
message.DoPreComputeJson()
|
||||||
|
for _, hub := range hubs {
|
||||||
|
hub.Broadcast(message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func InvalidateCacheForUser(userId string) {
|
func InvalidateCacheForUser(userId string) {
|
||||||
hub.invalidateUser <- userId
|
|
||||||
|
Srv.Store.Channel().InvalidateAllChannelMembersForUser(userId)
|
||||||
|
|
||||||
|
for _, hub := range hubs {
|
||||||
|
hub.InvalidateUser(userId)
|
||||||
|
}
|
||||||
|
|
||||||
if einterfaces.GetClusterInterface() != nil {
|
if einterfaces.GetClusterInterface() != nil {
|
||||||
einterfaces.GetClusterInterface().InvalidateCacheForUser(userId)
|
einterfaces.GetClusterInterface().InvalidateCacheForUser(userId)
|
||||||
@@ -54,11 +114,17 @@ func InvalidateCacheForUser(userId string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func InvalidateCacheForChannel(channelId string) {
|
func InvalidateCacheForChannel(channelId string) {
|
||||||
hub.invalidateChannel <- channelId
|
|
||||||
|
|
||||||
if einterfaces.GetClusterInterface() != nil {
|
// XXX TODO FIX ME
|
||||||
einterfaces.GetClusterInterface().InvalidateCacheForChannel(channelId)
|
// This can be removed, but the performance branch
|
||||||
}
|
// needs to be merged into master so it can be removed
|
||||||
|
// from the enterprise repo as well.
|
||||||
|
|
||||||
|
// hub.invalidateChannel <- channelId
|
||||||
|
|
||||||
|
// if einterfaces.GetClusterInterface() != nil {
|
||||||
|
// einterfaces.GetClusterInterface().InvalidateCacheForChannel(channelId)
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Hub) Register(webConn *WebConn) {
|
func (h *Hub) Register(webConn *WebConn) {
|
||||||
@@ -79,6 +145,10 @@ func (h *Hub) Broadcast(message *model.WebSocketEvent) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Hub) InvalidateUser(userId string) {
|
||||||
|
h.invalidateUser <- userId
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Hub) Stop() {
|
func (h *Hub) Stop() {
|
||||||
h.stop <- "all"
|
h.stop <- "all"
|
||||||
}
|
}
|
||||||
@@ -108,6 +178,7 @@ func (h *Hub) Start() {
|
|||||||
if !found {
|
if !found {
|
||||||
go SetStatusOffline(userId, false)
|
go SetStatusOffline(userId, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
case userId := <-h.invalidateUser:
|
case userId := <-h.invalidateUser:
|
||||||
for webCon := range h.connections {
|
for webCon := range h.connections {
|
||||||
if webCon.UserId == userId {
|
if webCon.UserId == userId {
|
||||||
@@ -115,26 +186,20 @@ func (h *Hub) Start() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case channelId := <-h.invalidateChannel:
|
|
||||||
for webCon := range h.connections {
|
|
||||||
webCon.InvalidateCacheForChannel(channelId)
|
|
||||||
}
|
|
||||||
|
|
||||||
case msg := <-h.broadcast:
|
case msg := <-h.broadcast:
|
||||||
for webCon := range h.connections {
|
for webCon := range h.connections {
|
||||||
if shouldSendEvent(webCon, msg) {
|
if webCon.ShouldSendEvent(msg) {
|
||||||
select {
|
select {
|
||||||
case webCon.Send <- msg:
|
case webCon.Send <- msg:
|
||||||
default:
|
default:
|
||||||
|
l4g.Error(fmt.Sprintf("webhub.broadcast: cannot send, closing websocket for userId=%v", webCon.UserId))
|
||||||
close(webCon.Send)
|
close(webCon.Send)
|
||||||
delete(h.connections, webCon)
|
delete(h.connections, webCon)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case s := <-h.stop:
|
case <-h.stop:
|
||||||
l4g.Debug(utils.T("api.web_hub.start.stopping.debug"), s)
|
|
||||||
|
|
||||||
for webCon := range h.connections {
|
for webCon := range h.connections {
|
||||||
webCon.WebSocket.Close()
|
webCon.WebSocket.Close()
|
||||||
}
|
}
|
||||||
@@ -144,28 +209,3 @@ func (h *Hub) Start() {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func shouldSendEvent(webCon *WebConn, msg *model.WebSocketEvent) bool {
|
|
||||||
// If the event is destined to a specific user
|
|
||||||
if len(msg.Broadcast.UserId) > 0 && webCon.UserId != msg.Broadcast.UserId {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the user is omitted don't send the message
|
|
||||||
if _, ok := msg.Broadcast.OmitUsers[webCon.UserId]; ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only report events to users who are in the channel for the event
|
|
||||||
if len(msg.Broadcast.ChannelId) > 0 {
|
|
||||||
return webCon.IsMemberOfChannel(msg.Broadcast.ChannelId)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only report events to users who are in the team for the event
|
|
||||||
if len(msg.Broadcast.TeamId) > 0 {
|
|
||||||
return webCon.IsMemberOfTeam(msg.Broadcast.TeamId)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -11,16 +11,20 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
SOCKET_MAX_MESSAGE_SIZE_KB = 8 * 1024 // 8KB
|
||||||
|
)
|
||||||
|
|
||||||
func InitWebSocket() {
|
func InitWebSocket() {
|
||||||
l4g.Debug(utils.T("api.web_socket.init.debug"))
|
l4g.Debug(utils.T("api.web_socket.init.debug"))
|
||||||
BaseRoutes.Users.Handle("/websocket", ApiUserRequiredTrustRequester(connect)).Methods("GET")
|
BaseRoutes.Users.Handle("/websocket", ApiUserRequiredTrustRequester(connect)).Methods("GET")
|
||||||
hub.Start()
|
HubStart()
|
||||||
}
|
}
|
||||||
|
|
||||||
func connect(c *Context, w http.ResponseWriter, r *http.Request) {
|
func connect(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
upgrader := websocket.Upgrader{
|
upgrader := websocket.Upgrader{
|
||||||
ReadBufferSize: 1024,
|
ReadBufferSize: SOCKET_MAX_MESSAGE_SIZE_KB,
|
||||||
WriteBufferSize: 1024,
|
WriteBufferSize: SOCKET_MAX_MESSAGE_SIZE_KB,
|
||||||
CheckOrigin: func(r *http.Request) bool {
|
CheckOrigin: func(r *http.Request) bool {
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
@@ -34,7 +38,7 @@ func connect(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
wc := NewWebConn(c, ws)
|
wc := NewWebConn(c, ws)
|
||||||
hub.Register(wc)
|
HubRegister(wc)
|
||||||
go wc.writePump()
|
go wc.writePump()
|
||||||
wc.readPump()
|
wc.readPump()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,11 +31,17 @@ func (wh *webSocketHandler) ServeWebSocket(conn *WebConn, r *model.WebSocketRequ
|
|||||||
if data, err = wh.handlerFunc(r); err != nil {
|
if data, err = wh.handlerFunc(r); err != nil {
|
||||||
l4g.Error(utils.T("api.web_socket_handler.log.error"), "/api/v3/users/websocket", r.Action, r.Seq, r.Session.UserId, err.SystemMessage(utils.T), err.DetailedError)
|
l4g.Error(utils.T("api.web_socket_handler.log.error"), "/api/v3/users/websocket", r.Action, r.Seq, r.Session.UserId, err.SystemMessage(utils.T), err.DetailedError)
|
||||||
err.DetailedError = ""
|
err.DetailedError = ""
|
||||||
conn.Send <- model.NewWebSocketError(r.Seq, err)
|
errResp := model.NewWebSocketError(r.Seq, err)
|
||||||
|
errResp.DoPreComputeJson()
|
||||||
|
|
||||||
|
conn.Send <- errResp
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
conn.Send <- model.NewWebSocketResponse(model.STATUS_OK, r.Seq, data)
|
resp := model.NewWebSocketResponse(model.STATUS_OK, r.Seq, data)
|
||||||
|
resp.DoPreComputeJson()
|
||||||
|
|
||||||
|
conn.Send <- resp
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewInvalidWebSocketParamError(action string, name string) *model.AppError {
|
func NewInvalidWebSocketParamError(action string, name string) *model.AppError {
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ func (wr *WebSocketRouter) ReturnWebSocketError(conn *WebConn, r *model.WebSocke
|
|||||||
|
|
||||||
err.DetailedError = ""
|
err.DetailedError = ""
|
||||||
errorResp := model.NewWebSocketError(r.Seq, err)
|
errorResp := model.NewWebSocketError(r.Seq, err)
|
||||||
|
errorResp.DoPreComputeJson()
|
||||||
|
|
||||||
conn.Send <- errorResp
|
conn.Send <- errorResp
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,7 +82,8 @@ func TestWebSocketEvent(t *testing.T) {
|
|||||||
omitUser["somerandomid"] = true
|
omitUser["somerandomid"] = true
|
||||||
evt1 := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_TYPING, "", th.BasicChannel.Id, "", omitUser)
|
evt1 := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_TYPING, "", th.BasicChannel.Id, "", omitUser)
|
||||||
evt1.Add("user_id", "somerandomid")
|
evt1.Add("user_id", "somerandomid")
|
||||||
go Publish(evt1)
|
Publish(evt1)
|
||||||
|
|
||||||
time.Sleep(300 * time.Millisecond)
|
time.Sleep(300 * time.Millisecond)
|
||||||
|
|
||||||
stop := make(chan bool)
|
stop := make(chan bool)
|
||||||
|
|||||||
@@ -64,7 +64,7 @@
|
|||||||
},
|
},
|
||||||
"LogSettings": {
|
"LogSettings": {
|
||||||
"EnableConsole": true,
|
"EnableConsole": true,
|
||||||
"ConsoleLevel": "DEBUG",
|
"ConsoleLevel": "INFO",
|
||||||
"EnableFile": true,
|
"EnableFile": true,
|
||||||
"FileLevel": "INFO",
|
"FileLevel": "INFO",
|
||||||
"FileFormat": "",
|
"FileFormat": "",
|
||||||
@@ -240,4 +240,4 @@
|
|||||||
"TurnUsername": "",
|
"TurnUsername": "",
|
||||||
"TurnSharedKey": ""
|
"TurnSharedKey": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
18
i18n/en.json
18
i18n/en.json
@@ -2403,9 +2403,13 @@
|
|||||||
"id": "api.user.verify_email.bad_link.app_error",
|
"id": "api.user.verify_email.bad_link.app_error",
|
||||||
"translation": "Bad verify email link."
|
"translation": "Bad verify email link."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "api.web_hub.start.starting.debug",
|
||||||
|
"translation": "Starting %v websocket hubs"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "api.web_hub.start.stopping.debug",
|
"id": "api.web_hub.start.stopping.debug",
|
||||||
"translation": "stopping %v connections"
|
"translation": "stopping websocket hub connections"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "api.web_socket.connect.error",
|
"id": "api.web_socket.connect.error",
|
||||||
@@ -4627,6 +4631,10 @@
|
|||||||
"id": "store.sql_team.get_member.app_error",
|
"id": "store.sql_team.get_member.app_error",
|
||||||
"translation": "We couldn't get the team member"
|
"translation": "We couldn't get the team member"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "store.sql_team.get_members_by_ids.app_error",
|
||||||
|
"translation": "We couldn't get the team members"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "store.sql_team.get_member.missing.app_error",
|
"id": "store.sql_team.get_member.missing.app_error",
|
||||||
"translation": "No team member found for that user id and team id"
|
"translation": "No team member found for that user id and team id"
|
||||||
@@ -4635,6 +4643,10 @@
|
|||||||
"id": "store.sql_team.get_members.app_error",
|
"id": "store.sql_team.get_members.app_error",
|
||||||
"translation": "We couldn't get the team members"
|
"translation": "We couldn't get the team members"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "store.sql_team.get_member_count.app_error",
|
||||||
|
"translation": "We couldn't count the team members"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "store.sql_team.get_teams_for_email.app_error",
|
"id": "store.sql_team.get_teams_for_email.app_error",
|
||||||
"translation": "We encountered a problem when looking up teams"
|
"translation": "We encountered a problem when looking up teams"
|
||||||
@@ -4723,6 +4735,10 @@
|
|||||||
"id": "store.sql_user.get_profiles.app_error",
|
"id": "store.sql_user.get_profiles.app_error",
|
||||||
"translation": "We encountered an error while finding user profiles"
|
"translation": "We encountered an error while finding user profiles"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "store.sql_user.get_recently_active_users.app_error",
|
||||||
|
"translation": "We encountered an error while finding the recently active users"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "store.sql_user.get_sysadmin_profiles.app_error",
|
"id": "store.sql_user.get_sysadmin_profiles.app_error",
|
||||||
"translation": "We encountered an error while finding user profiles"
|
"translation": "We encountered an error while finding user profiles"
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"runtime/pprof"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
@@ -83,6 +84,10 @@ var flagChannelHeader string
|
|||||||
var flagChannelPurpose string
|
var flagChannelPurpose string
|
||||||
var flagUserSetInactive bool
|
var flagUserSetInactive bool
|
||||||
var flagImportArchive string
|
var flagImportArchive string
|
||||||
|
var flagCpuProfile bool
|
||||||
|
var flagMemProfile bool
|
||||||
|
var flagBlockProfile bool
|
||||||
|
var flagHttpProfiler bool
|
||||||
|
|
||||||
func doLoadConfig(filename string) (err string) {
|
func doLoadConfig(filename string) (err string) {
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -122,7 +127,26 @@ func main() {
|
|||||||
|
|
||||||
cmdUpdateDb30()
|
cmdUpdateDb30()
|
||||||
|
|
||||||
api.NewServer()
|
if flagCpuProfile {
|
||||||
|
f, err := os.Create(utils.GetLogFileLocation(utils.Cfg.LogSettings.FileLocation) + ".cpu.prof")
|
||||||
|
if err != nil {
|
||||||
|
l4g.Error("Error creating cpu profile log: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
l4g.Info("CPU Profiler is logging to " + utils.GetLogFileLocation(utils.Cfg.LogSettings.FileLocation) + ".cpu.prof")
|
||||||
|
pprof.StartCPUProfile(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
if flagBlockProfile {
|
||||||
|
l4g.Info("Block Profiler is logging to " + utils.GetLogFileLocation(utils.Cfg.LogSettings.FileLocation) + ".blk.prof")
|
||||||
|
runtime.SetBlockProfileRate(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if flagMemProfile {
|
||||||
|
l4g.Info("Memory Profiler is logging to " + utils.GetLogFileLocation(utils.Cfg.LogSettings.FileLocation) + ".mem.prof")
|
||||||
|
}
|
||||||
|
|
||||||
|
api.NewServer(flagHttpProfiler)
|
||||||
api.InitApi()
|
api.InitApi()
|
||||||
web.InitWeb()
|
web.InitWeb()
|
||||||
|
|
||||||
@@ -169,6 +193,37 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
api.StopServer()
|
api.StopServer()
|
||||||
|
|
||||||
|
if flagCpuProfile {
|
||||||
|
l4g.Info("Closing CPU Profiler")
|
||||||
|
pprof.StopCPUProfile()
|
||||||
|
}
|
||||||
|
|
||||||
|
if flagBlockProfile {
|
||||||
|
f, err := os.Create(utils.GetLogFileLocation(utils.Cfg.LogSettings.FileLocation) + ".blk.prof")
|
||||||
|
if err != nil {
|
||||||
|
l4g.Error("Error creating block profile log: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
l4g.Info("Writing Block Profiler to: " + utils.GetLogFileLocation(utils.Cfg.LogSettings.FileLocation) + ".blk.prof")
|
||||||
|
pprof.Lookup("block").WriteTo(f, 0)
|
||||||
|
f.Close()
|
||||||
|
runtime.SetBlockProfileRate(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if flagMemProfile {
|
||||||
|
f, err := os.Create(utils.GetLogFileLocation(utils.Cfg.LogSettings.FileLocation) + ".mem.prof")
|
||||||
|
if err != nil {
|
||||||
|
l4g.Error("Error creating memory profile file: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
l4g.Info("Writing Memory Profiler to: " + utils.GetLogFileLocation(utils.Cfg.LogSettings.FileLocation) + ".mem.prof")
|
||||||
|
runtime.GC()
|
||||||
|
if err := pprof.WriteHeapProfile(f); err != nil {
|
||||||
|
l4g.Error("Error creating memory profile: " + err.Error())
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -380,6 +435,10 @@ func parseCmds() {
|
|||||||
flag.BoolVar(&flagCmdActivateUser, "activate_user", false, "")
|
flag.BoolVar(&flagCmdActivateUser, "activate_user", false, "")
|
||||||
flag.BoolVar(&flagCmdSlackImport, "slack_import", false, "")
|
flag.BoolVar(&flagCmdSlackImport, "slack_import", false, "")
|
||||||
flag.BoolVar(&flagUserSetInactive, "inactive", false, "")
|
flag.BoolVar(&flagUserSetInactive, "inactive", false, "")
|
||||||
|
flag.BoolVar(&flagCpuProfile, "cpuprofile", false, "")
|
||||||
|
flag.BoolVar(&flagMemProfile, "memprofile", false, "")
|
||||||
|
flag.BoolVar(&flagBlockProfile, "blkprofile", false, "")
|
||||||
|
flag.BoolVar(&flagHttpProfiler, "httpprofiler", false, "")
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
|||||||
58
model/autocomplete.go
Обычный файл
58
model/autocomplete.go
Обычный файл
@@ -0,0 +1,58 @@
|
|||||||
|
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserAutocompleteInChannel struct {
|
||||||
|
InChannel []*User `json:"in_channel"`
|
||||||
|
OutOfChannel []*User `json:"out_of_channel"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserAutocompleteInTeam struct {
|
||||||
|
InTeam []*User `json:"in_team"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *UserAutocompleteInChannel) ToJson() string {
|
||||||
|
b, err := json.Marshal(o)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
} else {
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UserAutocompleteInChannelFromJson(data io.Reader) *UserAutocompleteInChannel {
|
||||||
|
decoder := json.NewDecoder(data)
|
||||||
|
var o UserAutocompleteInChannel
|
||||||
|
err := decoder.Decode(&o)
|
||||||
|
if err == nil {
|
||||||
|
return &o
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *UserAutocompleteInTeam) ToJson() string {
|
||||||
|
b, err := json.Marshal(o)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
} else {
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UserAutocompleteInTeamFromJson(data io.Reader) *UserAutocompleteInTeam {
|
||||||
|
decoder := json.NewDecoder(data)
|
||||||
|
var o UserAutocompleteInTeam
|
||||||
|
err := decoder.Decode(&o)
|
||||||
|
if err == nil {
|
||||||
|
return &o
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -57,8 +57,8 @@ func (o *Channel) Etag() string {
|
|||||||
return Etag(o.Id, o.UpdateAt)
|
return Etag(o.Id, o.UpdateAt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Channel) ExtraEtag(memberLimit int) string {
|
func (o *Channel) StatsEtag() string {
|
||||||
return Etag(o.Id, o.ExtraUpdateAt, memberLimit)
|
return Etag(o.Id, o.ExtraUpdateAt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Channel) IsValid() *AppError {
|
func (o *Channel) IsValid() *AppError {
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See License.txt for license information.
|
|
||||||
|
|
||||||
package model
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ExtraMember struct {
|
|
||||||
Id string `json:"id"`
|
|
||||||
Nickname string `json:"nickname"`
|
|
||||||
Email string `json:"email"`
|
|
||||||
Roles string `json:"roles"`
|
|
||||||
Username string `json:"username"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *ExtraMember) Sanitize(options map[string]bool) {
|
|
||||||
if len(options) == 0 || !options["email"] {
|
|
||||||
o.Email = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ChannelExtra struct {
|
|
||||||
Id string `json:"id"`
|
|
||||||
Members []ExtraMember `json:"members"`
|
|
||||||
MemberCount int64 `json:"member_count"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *ChannelExtra) ToJson() string {
|
|
||||||
b, err := json.Marshal(o)
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
} else {
|
|
||||||
return string(b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ChannelExtraFromJson(data io.Reader) *ChannelExtra {
|
|
||||||
decoder := json.NewDecoder(data)
|
|
||||||
var o ChannelExtra
|
|
||||||
err := decoder.Decode(&o)
|
|
||||||
if err == nil {
|
|
||||||
return &o
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
34
model/channel_stats.go
Обычный файл
34
model/channel_stats.go
Обычный файл
@@ -0,0 +1,34 @@
|
|||||||
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ChannelStats struct {
|
||||||
|
ChannelId string `json:"channel_id"`
|
||||||
|
MemberCount int64 `json:"member_count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ChannelStats) ToJson() string {
|
||||||
|
b, err := json.Marshal(o)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
} else {
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ChannelStatsFromJson(data io.Reader) *ChannelStats {
|
||||||
|
decoder := json.NewDecoder(data)
|
||||||
|
var o ChannelStats
|
||||||
|
err := decoder.Decode(&o)
|
||||||
|
if err == nil {
|
||||||
|
return &o
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
164
model/client.go
164
model/client.go
@@ -131,6 +131,7 @@ func (c *Client) GetFileRoute(fileId string) string {
|
|||||||
func (c *Client) DoPost(url, data, contentType string) (*http.Response, *AppError) {
|
func (c *Client) DoPost(url, data, contentType string) (*http.Response, *AppError) {
|
||||||
rq, _ := http.NewRequest("POST", c.Url+url, strings.NewReader(data))
|
rq, _ := http.NewRequest("POST", c.Url+url, strings.NewReader(data))
|
||||||
rq.Header.Set("Content-Type", contentType)
|
rq.Header.Set("Content-Type", contentType)
|
||||||
|
rq.Close = true
|
||||||
|
|
||||||
if rp, err := c.HttpClient.Do(rq); err != nil {
|
if rp, err := c.HttpClient.Do(rq); err != nil {
|
||||||
return nil, NewLocAppError(url, "model.client.connecting.app_error", nil, err.Error())
|
return nil, NewLocAppError(url, "model.client.connecting.app_error", nil, err.Error())
|
||||||
@@ -144,6 +145,7 @@ func (c *Client) DoPost(url, data, contentType string) (*http.Response, *AppErro
|
|||||||
|
|
||||||
func (c *Client) DoApiPost(url string, data string) (*http.Response, *AppError) {
|
func (c *Client) DoApiPost(url string, data string) (*http.Response, *AppError) {
|
||||||
rq, _ := http.NewRequest("POST", c.ApiUrl+url, strings.NewReader(data))
|
rq, _ := http.NewRequest("POST", c.ApiUrl+url, strings.NewReader(data))
|
||||||
|
rq.Close = true
|
||||||
|
|
||||||
if len(c.AuthToken) > 0 {
|
if len(c.AuthToken) > 0 {
|
||||||
rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken)
|
rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken)
|
||||||
@@ -161,6 +163,7 @@ func (c *Client) DoApiPost(url string, data string) (*http.Response, *AppError)
|
|||||||
|
|
||||||
func (c *Client) DoApiGet(url string, data string, etag string) (*http.Response, *AppError) {
|
func (c *Client) DoApiGet(url string, data string, etag string) (*http.Response, *AppError) {
|
||||||
rq, _ := http.NewRequest("GET", c.ApiUrl+url, strings.NewReader(data))
|
rq, _ := http.NewRequest("GET", c.ApiUrl+url, strings.NewReader(data))
|
||||||
|
rq.Close = true
|
||||||
|
|
||||||
if len(etag) > 0 {
|
if len(etag) > 0 {
|
||||||
rq.Header.Set(HEADER_ETAG_CLIENT, etag)
|
rq.Header.Set(HEADER_ETAG_CLIENT, etag)
|
||||||
@@ -508,10 +511,9 @@ func (c *Client) GetMe(etag string) (*Result, *AppError) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetProfilesForDirectMessageList returns a map of users for a team that can be direct
|
// GetProfiles returns a map of users using user id as the key. Must be authenticated.
|
||||||
// messaged, using user id as the key. Must be authenticated.
|
func (c *Client) GetProfiles(offset int, limit int, etag string) (*Result, *AppError) {
|
||||||
func (c *Client) GetProfilesForDirectMessageList(teamId string) (*Result, *AppError) {
|
if r, err := c.DoApiGet(fmt.Sprintf("/users/%v/%v", offset, limit), "", etag); err != nil {
|
||||||
if r, err := c.DoApiGet("/users/profiles_for_dm_list/"+teamId, "", ""); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
defer closeBody(r)
|
defer closeBody(r)
|
||||||
@@ -520,10 +522,10 @@ func (c *Client) GetProfilesForDirectMessageList(teamId string) (*Result, *AppEr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetProfiles returns a map of users for a team using user id as the key. Must
|
// GetProfilesInTeam returns a map of users for a team using user id as the key. Must
|
||||||
// be authenticated.
|
// be authenticated.
|
||||||
func (c *Client) GetProfiles(teamId string, etag string) (*Result, *AppError) {
|
func (c *Client) GetProfilesInTeam(teamId string, offset int, limit int, etag string) (*Result, *AppError) {
|
||||||
if r, err := c.DoApiGet("/users/profiles/"+teamId, "", etag); err != nil {
|
if r, err := c.DoApiGet(fmt.Sprintf("/teams/%v/users/%v/%v", teamId, offset, limit), "", etag); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
defer closeBody(r)
|
defer closeBody(r)
|
||||||
@@ -532,10 +534,10 @@ func (c *Client) GetProfiles(teamId string, etag string) (*Result, *AppError) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDirectProfiles gets a map of users that are currently shown in the sidebar,
|
// GetProfilesInChannel returns a map of users for a channel using user id as the key. Must
|
||||||
// using user id as the key. Must be authenticated.
|
// be authenticated.
|
||||||
func (c *Client) GetDirectProfiles(etag string) (*Result, *AppError) {
|
func (c *Client) GetProfilesInChannel(channelId string, offset int, limit int, etag string) (*Result, *AppError) {
|
||||||
if r, err := c.DoApiGet("/users/direct_profiles", "", etag); err != nil {
|
if r, err := c.DoApiGet(fmt.Sprintf(c.GetChannelRoute(channelId)+"/users/%v/%v", offset, limit), "", etag); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
defer closeBody(r)
|
defer closeBody(r)
|
||||||
@@ -544,6 +546,72 @@ func (c *Client) GetDirectProfiles(etag string) (*Result, *AppError) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetProfilesNotInChannel returns a map of users not in a channel but on the team using user id as the key. Must
|
||||||
|
// be authenticated.
|
||||||
|
func (c *Client) GetProfilesNotInChannel(channelId string, offset int, limit int, etag string) (*Result, *AppError) {
|
||||||
|
if r, err := c.DoApiGet(fmt.Sprintf(c.GetChannelRoute(channelId)+"/users/not_in_channel/%v/%v", offset, limit), "", etag); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
defer closeBody(r)
|
||||||
|
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||||
|
r.Header.Get(HEADER_ETAG_SERVER), UserMapFromJson(r.Body)}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProfilesByIds returns a map of users based on the user ids provided. Must
|
||||||
|
// be authenticated.
|
||||||
|
func (c *Client) GetProfilesByIds(userIds []string) (*Result, *AppError) {
|
||||||
|
if r, err := c.DoApiPost("/users/ids", ArrayToJson(userIds)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
defer closeBody(r)
|
||||||
|
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||||
|
r.Header.Get(HEADER_ETAG_SERVER), UserMapFromJson(r.Body)}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchUsers returns a list of users that have a username matching or similar to the search term. Must
|
||||||
|
// be authenticated.
|
||||||
|
func (c *Client) SearchUsers(term string, teamId string, options map[string]string) (*Result, *AppError) {
|
||||||
|
options["term"] = term
|
||||||
|
options["team_id"] = teamId
|
||||||
|
if r, err := c.DoApiPost("/users/search", MapToJson(options)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
defer closeBody(r)
|
||||||
|
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||||
|
r.Header.Get(HEADER_ETAG_SERVER), UserListFromJson(r.Body)}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AutocompleteUsersInChannel returns two lists for autocompletion of users in a channel. The first list "in_channel",
|
||||||
|
// specifies users in the channel. The second list "out_of_channel" specifies users outside of the
|
||||||
|
// channel. Term, the string to search against, is required, channel id is also required. Must be authenticated.
|
||||||
|
func (c *Client) AutocompleteUsersInChannel(term string, channelId string) (*Result, *AppError) {
|
||||||
|
url := fmt.Sprintf("%s/users/autocomplete?term=%s", c.GetChannelRoute(channelId), url.QueryEscape(term))
|
||||||
|
if r, err := c.DoApiGet(url, "", ""); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
defer closeBody(r)
|
||||||
|
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||||
|
r.Header.Get(HEADER_ETAG_SERVER), UserAutocompleteInChannelFromJson(r.Body)}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AutocompleteUsersInTeam returns a list for autocompletion of users in a team. The list "in_team" specifies
|
||||||
|
// the users in the team that match the provided term, matching against username, full name and
|
||||||
|
// nickname. Must be authenticated.
|
||||||
|
func (c *Client) AutocompleteUsersInTeam(term string) (*Result, *AppError) {
|
||||||
|
url := fmt.Sprintf("%s/users/autocomplete?term=%s", c.GetTeamRoute(), url.QueryEscape(term))
|
||||||
|
if r, err := c.DoApiGet(url, "", ""); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
defer closeBody(r)
|
||||||
|
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||||
|
r.Header.Get(HEADER_ETAG_SERVER), UserAutocompleteInTeamFromJson(r.Body)}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// LoginById authenticates a user by user id and password.
|
// LoginById authenticates a user by user id and password.
|
||||||
func (c *Client) LoginById(id string, password string) (*Result, *AppError) {
|
func (c *Client) LoginById(id string, password string) (*Result, *AppError) {
|
||||||
m := make(map[string]string)
|
m := make(map[string]string)
|
||||||
@@ -942,6 +1010,7 @@ func (c *Client) SaveComplianceReport(job *Compliance) (*Result, *AppError) {
|
|||||||
func (c *Client) DownloadComplianceReport(id string) (*Result, *AppError) {
|
func (c *Client) DownloadComplianceReport(id string) (*Result, *AppError) {
|
||||||
var rq *http.Request
|
var rq *http.Request
|
||||||
rq, _ = http.NewRequest("GET", c.ApiUrl+"/admin/download_compliance_report/"+id, nil)
|
rq, _ = http.NewRequest("GET", c.ApiUrl+"/admin/download_compliance_report/"+id, nil)
|
||||||
|
rq.Close = true
|
||||||
|
|
||||||
if len(c.AuthToken) > 0 {
|
if len(c.AuthToken) > 0 {
|
||||||
rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken)
|
rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken)
|
||||||
@@ -1174,13 +1243,23 @@ func (c *Client) UpdateLastViewedAt(channelId string, active bool) (*Result, *Ap
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) GetChannelExtraInfo(id string, memberLimit int, etag string) (*Result, *AppError) {
|
func (c *Client) GetChannelStats(id string, etag string) (*Result, *AppError) {
|
||||||
if r, err := c.DoApiGet(c.GetChannelRoute(id)+"/extra_info/"+strconv.FormatInt(int64(memberLimit), 10), "", etag); err != nil {
|
if r, err := c.DoApiGet(c.GetChannelRoute(id)+"/stats", "", etag); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
defer closeBody(r)
|
defer closeBody(r)
|
||||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||||
r.Header.Get(HEADER_ETAG_SERVER), ChannelExtraFromJson(r.Body)}, nil
|
r.Header.Get(HEADER_ETAG_SERVER), ChannelStatsFromJson(r.Body)}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetChannelMember(channelId string, userId string) (*Result, *AppError) {
|
||||||
|
if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+"/members/"+userId, "", ""); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
defer closeBody(r)
|
||||||
|
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||||
|
r.Header.Get(HEADER_ETAG_SERVER), ChannelMemberFromJson(r.Body)}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1325,6 +1404,7 @@ func (c *Client) UploadPostAttachment(data []byte, channelId string, filename st
|
|||||||
func (c *Client) uploadFile(url string, data []byte, contentType string) (*Result, *AppError) {
|
func (c *Client) uploadFile(url string, data []byte, contentType string) (*Result, *AppError) {
|
||||||
rq, _ := http.NewRequest("POST", url, bytes.NewReader(data))
|
rq, _ := http.NewRequest("POST", url, bytes.NewReader(data))
|
||||||
rq.Header.Set("Content-Type", contentType)
|
rq.Header.Set("Content-Type", contentType)
|
||||||
|
rq.Close = true
|
||||||
|
|
||||||
if len(c.AuthToken) > 0 {
|
if len(c.AuthToken) > 0 {
|
||||||
rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken)
|
rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken)
|
||||||
@@ -1525,6 +1605,18 @@ func (c *Client) GetStatuses() (*Result, *AppError) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetStatusesByIds returns a map of string statuses using user id as the key,
|
||||||
|
// based on the provided user ids
|
||||||
|
func (c *Client) GetStatusesByIds(userIds []string) (*Result, *AppError) {
|
||||||
|
if r, err := c.DoApiPost("/users/status/ids", ArrayToJson(userIds)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
defer closeBody(r)
|
||||||
|
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||||
|
r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SetActiveChannel sets the the channel id the user is currently viewing.
|
// SetActiveChannel sets the the channel id the user is currently viewing.
|
||||||
// The channelId key is required but the value can be blank. Returns standard
|
// The channelId key is required but the value can be blank. Returns standard
|
||||||
// response.
|
// response.
|
||||||
@@ -1550,8 +1642,46 @@ func (c *Client) GetMyTeam(etag string) (*Result, *AppError) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) GetTeamMembers(teamId string) (*Result, *AppError) {
|
// GetTeamMembers will return a page of team member objects as an array paged based on the
|
||||||
if r, err := c.DoApiGet("/teams/members/"+teamId, "", ""); err != nil {
|
// team id, offset and limit provided. Must be authenticated.
|
||||||
|
func (c *Client) GetTeamMembers(teamId string, offset int, limit int) (*Result, *AppError) {
|
||||||
|
if r, err := c.DoApiGet(fmt.Sprintf("/teams/%v/members/%v/%v", teamId, offset, limit), "", ""); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
defer closeBody(r)
|
||||||
|
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||||
|
r.Header.Get(HEADER_ETAG_SERVER), TeamMembersFromJson(r.Body)}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTeamMember will return a team member object based on the team id and user id provided.
|
||||||
|
// Must be authenticated.
|
||||||
|
func (c *Client) GetTeamMember(teamId string, userId string) (*Result, *AppError) {
|
||||||
|
if r, err := c.DoApiGet(fmt.Sprintf("/teams/%v/members/%v", teamId, userId), "", ""); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
defer closeBody(r)
|
||||||
|
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||||
|
r.Header.Get(HEADER_ETAG_SERVER), TeamMemberFromJson(r.Body)}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTeamStats will return a team stats object containing the number of users on the team
|
||||||
|
// based on the team id provided. Must be authenticated.
|
||||||
|
func (c *Client) GetTeamStats(teamId string) (*Result, *AppError) {
|
||||||
|
if r, err := c.DoApiGet(fmt.Sprintf("/teams/%v/stats", teamId), "", ""); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
defer closeBody(r)
|
||||||
|
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||||
|
r.Header.Get(HEADER_ETAG_SERVER), TeamStatsFromJson(r.Body)}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTeamMembersByIds will return team member objects as an array based on the
|
||||||
|
// team id and a list of user ids provided. Must be authenticated.
|
||||||
|
func (c *Client) GetTeamMembersByIds(teamId string, userIds []string) (*Result, *AppError) {
|
||||||
|
if r, err := c.DoApiPost(fmt.Sprintf("/teams/%v/members/ids", teamId), ArrayToJson(userIds)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
defer closeBody(r)
|
defer closeBody(r)
|
||||||
@@ -1866,6 +1996,7 @@ func (c *Client) CreateEmoji(emoji *Emoji, image []byte, filename string) (*Emoj
|
|||||||
|
|
||||||
rq, _ := http.NewRequest("POST", c.ApiUrl+c.GetEmojiRoute()+"/create", body)
|
rq, _ := http.NewRequest("POST", c.ApiUrl+c.GetEmojiRoute()+"/create", body)
|
||||||
rq.Header.Set("Content-Type", writer.FormDataContentType())
|
rq.Header.Set("Content-Type", writer.FormDataContentType())
|
||||||
|
rq.Close = true
|
||||||
|
|
||||||
if len(c.AuthToken) > 0 {
|
if len(c.AuthToken) > 0 {
|
||||||
rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken)
|
rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken)
|
||||||
@@ -1908,6 +2039,7 @@ func (c *Client) UploadCertificateFile(data []byte, contentType string) *AppErro
|
|||||||
url := c.ApiUrl + "/admin/add_certificate"
|
url := c.ApiUrl + "/admin/add_certificate"
|
||||||
rq, _ := http.NewRequest("POST", url, bytes.NewReader(data))
|
rq, _ := http.NewRequest("POST", url, bytes.NewReader(data))
|
||||||
rq.Header.Set("Content-Type", contentType)
|
rq.Header.Set("Content-Type", contentType)
|
||||||
|
rq.Close = true
|
||||||
|
|
||||||
if len(c.AuthToken) > 0 {
|
if len(c.AuthToken) > 0 {
|
||||||
rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken)
|
rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken)
|
||||||
|
|||||||
@@ -9,14 +9,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type InitialLoad struct {
|
type InitialLoad struct {
|
||||||
User *User `json:"user"`
|
User *User `json:"user"`
|
||||||
TeamMembers []*TeamMember `json:"team_members"`
|
TeamMembers []*TeamMember `json:"team_members"`
|
||||||
Teams []*Team `json:"teams"`
|
Teams []*Team `json:"teams"`
|
||||||
DirectProfiles map[string]*User `json:"direct_profiles"`
|
Preferences Preferences `json:"preferences"`
|
||||||
Preferences Preferences `json:"preferences"`
|
ClientCfg map[string]string `json:"client_cfg"`
|
||||||
ClientCfg map[string]string `json:"client_cfg"`
|
LicenseCfg map[string]string `json:"license_cfg"`
|
||||||
LicenseCfg map[string]string `json:"license_cfg"`
|
NoAccounts bool `json:"no_accounts"`
|
||||||
NoAccounts bool `json:"no_accounts"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (me *InitialLoad) ToJson() string {
|
func (me *InitialLoad) ToJson() string {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
SESSION_COOKIE_TOKEN = "MMAUTHTOKEN"
|
SESSION_COOKIE_TOKEN = "MMAUTHTOKEN"
|
||||||
SESSION_CACHE_SIZE = 10000
|
SESSION_CACHE_SIZE = 25000
|
||||||
SESSION_PROP_PLATFORM = "platform"
|
SESSION_PROP_PLATFORM = "platform"
|
||||||
SESSION_PROP_OS = "os"
|
SESSION_PROP_OS = "os"
|
||||||
SESSION_PROP_BROWSER = "browser"
|
SESSION_PROP_BROWSER = "browser"
|
||||||
|
|||||||
@@ -12,8 +12,9 @@ const (
|
|||||||
STATUS_OFFLINE = "offline"
|
STATUS_OFFLINE = "offline"
|
||||||
STATUS_AWAY = "away"
|
STATUS_AWAY = "away"
|
||||||
STATUS_ONLINE = "online"
|
STATUS_ONLINE = "online"
|
||||||
STATUS_CACHE_SIZE = 10000
|
STATUS_CACHE_SIZE = 25000
|
||||||
STATUS_CHANNEL_TIMEOUT = 20000 // 20 seconds
|
STATUS_CHANNEL_TIMEOUT = 20000 // 20 seconds
|
||||||
|
STATUS_MIN_UPDATE_TIME = 120000 // 2 minutes
|
||||||
)
|
)
|
||||||
|
|
||||||
type Status struct {
|
type Status struct {
|
||||||
|
|||||||
34
model/team_stats.go
Обычный файл
34
model/team_stats.go
Обычный файл
@@ -0,0 +1,34 @@
|
|||||||
|
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TeamStats struct {
|
||||||
|
TeamId string `json:"team_id"`
|
||||||
|
MemberCount int64 `json:"member_count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *TeamStats) ToJson() string {
|
||||||
|
b, err := json.Marshal(o)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
} else {
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TeamStatsFromJson(data io.Reader) *TeamStats {
|
||||||
|
decoder := json.NewDecoder(data)
|
||||||
|
var o TeamStats
|
||||||
|
err := decoder.Decode(&o)
|
||||||
|
if err == nil {
|
||||||
|
return &o
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -413,6 +413,26 @@ func UserMapFromJson(data io.Reader) map[string]*User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UserListToJson(u []*User) string {
|
||||||
|
b, err := json.Marshal(u)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
} else {
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UserListFromJson(data io.Reader) []*User {
|
||||||
|
decoder := json.NewDecoder(data)
|
||||||
|
var users []*User
|
||||||
|
err := decoder.Decode(&users)
|
||||||
|
if err == nil {
|
||||||
|
return users
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// HashPassword generates a hash using the bcrypt.GenerateFromPassword
|
// HashPassword generates a hash using the bcrypt.GenerateFromPassword
|
||||||
func HashPassword(password string) string {
|
func HashPassword(password string) string {
|
||||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), 10)
|
hash, err := bcrypt.GenerateFromPassword([]byte(password), 10)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net/mail"
|
"net/mail"
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -74,13 +75,21 @@ func (er *AppError) ToJson() string {
|
|||||||
|
|
||||||
// AppErrorFromJson will decode the input and return an AppError
|
// AppErrorFromJson will decode the input and return an AppError
|
||||||
func AppErrorFromJson(data io.Reader) *AppError {
|
func AppErrorFromJson(data io.Reader) *AppError {
|
||||||
decoder := json.NewDecoder(data)
|
str := ""
|
||||||
|
bytes, rerr := ioutil.ReadAll(data)
|
||||||
|
if rerr != nil {
|
||||||
|
str = rerr.Error()
|
||||||
|
} else {
|
||||||
|
str = string(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder := json.NewDecoder(strings.NewReader(str))
|
||||||
var er AppError
|
var er AppError
|
||||||
err := decoder.Decode(&er)
|
err := decoder.Decode(&er)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return &er
|
return &er
|
||||||
} else {
|
} else {
|
||||||
return NewLocAppError("AppErrorFromJson", "model.utils.decode_json.app_error", nil, err.Error())
|
return NewLocAppError("AppErrorFromJson", "model.utils.decode_json.app_error", nil, "body: "+str)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,6 +175,23 @@ func ArrayFromJson(data io.Reader) []string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ArrayFromInterface(data interface{}) []string {
|
||||||
|
stringArray := []string{}
|
||||||
|
|
||||||
|
dataArray, ok := data.([]interface{})
|
||||||
|
if !ok {
|
||||||
|
return stringArray
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range dataArray {
|
||||||
|
if str, ok := v.(string); ok {
|
||||||
|
stringArray = append(stringArray, str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stringArray
|
||||||
|
}
|
||||||
|
|
||||||
func StringInterfaceToJson(objmap map[string]interface{}) string {
|
func StringInterfaceToJson(objmap map[string]interface{}) string {
|
||||||
if b, err := json.Marshal(objmap); err != nil {
|
if b, err := json.Marshal(objmap); err != nil {
|
||||||
return ""
|
return ""
|
||||||
|
|||||||
@@ -37,6 +37,13 @@ func TestAppError(t *testing.T) {
|
|||||||
err.Error()
|
err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAppErrorJunk(t *testing.T) {
|
||||||
|
rerr := AppErrorFromJson(strings.NewReader("<html><body>This is a broken test</body></html>"))
|
||||||
|
if "body: <html><body>This is a broken test</body></html>" != rerr.DetailedError {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMapJson(t *testing.T) {
|
func TestMapJson(t *testing.T) {
|
||||||
|
|
||||||
m := make(map[string]string)
|
m := make(map[string]string)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ type WebSocketClient struct {
|
|||||||
Sequence int64 // The ever-incrementing sequence attached to each WebSocket action
|
Sequence int64 // The ever-incrementing sequence attached to each WebSocket action
|
||||||
EventChannel chan *WebSocketEvent
|
EventChannel chan *WebSocketEvent
|
||||||
ResponseChannel chan *WebSocketResponse
|
ResponseChannel chan *WebSocketResponse
|
||||||
|
ListenError *AppError
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewWebSocketClient constructs a new WebSocket client with convienence
|
// NewWebSocketClient constructs a new WebSocket client with convienence
|
||||||
@@ -37,6 +38,7 @@ func NewWebSocketClient(url, authToken string) (*WebSocketClient, *AppError) {
|
|||||||
1,
|
1,
|
||||||
make(chan *WebSocketEvent, 100),
|
make(chan *WebSocketEvent, 100),
|
||||||
make(chan *WebSocketResponse, 100),
|
make(chan *WebSocketResponse, 100),
|
||||||
|
nil,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,10 +61,20 @@ func (wsc *WebSocketClient) Close() {
|
|||||||
|
|
||||||
func (wsc *WebSocketClient) Listen() {
|
func (wsc *WebSocketClient) Listen() {
|
||||||
go func() {
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
wsc.Conn.Close()
|
||||||
|
close(wsc.EventChannel)
|
||||||
|
close(wsc.ResponseChannel)
|
||||||
|
}()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
var rawMsg json.RawMessage
|
var rawMsg json.RawMessage
|
||||||
var err error
|
var err error
|
||||||
if _, rawMsg, err = wsc.Conn.ReadMessage(); err != nil {
|
if _, rawMsg, err = wsc.Conn.ReadMessage(); err != nil {
|
||||||
|
if !websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) {
|
||||||
|
wsc.ListenError = NewLocAppError("NewWebSocketClient", "model.websocket_client.connect_fail.app_error", nil, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,3 +119,12 @@ func (wsc *WebSocketClient) UserTyping(channelId, parentId string) {
|
|||||||
func (wsc *WebSocketClient) GetStatuses() {
|
func (wsc *WebSocketClient) GetStatuses() {
|
||||||
wsc.SendMessage("get_statuses", nil)
|
wsc.SendMessage("get_statuses", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetStatusesByIds will fetch certain user statuses based on ids and return
|
||||||
|
// a map of string statuses using user id as the key
|
||||||
|
func (wsc *WebSocketClient) GetStatusesByIds(userIds []string) {
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"user_ids": userIds,
|
||||||
|
}
|
||||||
|
wsc.SendMessage("get_statuses_by_ids", data)
|
||||||
|
}
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ const (
|
|||||||
type WebSocketMessage interface {
|
type WebSocketMessage interface {
|
||||||
ToJson() string
|
ToJson() string
|
||||||
IsValid() bool
|
IsValid() bool
|
||||||
|
DoPreComputeJson()
|
||||||
|
GetPreComputeJson() []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
type WebsocketBroadcast struct {
|
type WebsocketBroadcast struct {
|
||||||
@@ -41,9 +43,10 @@ type WebsocketBroadcast struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type WebSocketEvent struct {
|
type WebSocketEvent struct {
|
||||||
Event string `json:"event"`
|
Event string `json:"event"`
|
||||||
Data map[string]interface{} `json:"data"`
|
Data map[string]interface{} `json:"data"`
|
||||||
Broadcast *WebsocketBroadcast `json:"broadcast"`
|
Broadcast *WebsocketBroadcast `json:"broadcast"`
|
||||||
|
PreComputeJson []byte `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *WebSocketEvent) Add(key string, value interface{}) {
|
func (m *WebSocketEvent) Add(key string, value interface{}) {
|
||||||
@@ -59,6 +62,19 @@ func (o *WebSocketEvent) IsValid() bool {
|
|||||||
return o.Event != ""
|
return o.Event != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *WebSocketEvent) DoPreComputeJson() {
|
||||||
|
b, err := json.Marshal(o)
|
||||||
|
if err != nil {
|
||||||
|
o.PreComputeJson = []byte("")
|
||||||
|
} else {
|
||||||
|
o.PreComputeJson = b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *WebSocketEvent) GetPreComputeJson() []byte {
|
||||||
|
return o.PreComputeJson
|
||||||
|
}
|
||||||
|
|
||||||
func (o *WebSocketEvent) ToJson() string {
|
func (o *WebSocketEvent) ToJson() string {
|
||||||
b, err := json.Marshal(o)
|
b, err := json.Marshal(o)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -80,10 +96,11 @@ func WebSocketEventFromJson(data io.Reader) *WebSocketEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type WebSocketResponse struct {
|
type WebSocketResponse struct {
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
SeqReply int64 `json:"seq_reply,omitempty"`
|
SeqReply int64 `json:"seq_reply,omitempty"`
|
||||||
Data map[string]interface{} `json:"data,omitempty"`
|
Data map[string]interface{} `json:"data,omitempty"`
|
||||||
Error *AppError `json:"error,omitempty"`
|
Error *AppError `json:"error,omitempty"`
|
||||||
|
PreComputeJson []byte `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *WebSocketResponse) Add(key string, value interface{}) {
|
func (m *WebSocketResponse) Add(key string, value interface{}) {
|
||||||
@@ -111,6 +128,19 @@ func (o *WebSocketResponse) ToJson() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *WebSocketResponse) DoPreComputeJson() {
|
||||||
|
b, err := json.Marshal(o)
|
||||||
|
if err != nil {
|
||||||
|
o.PreComputeJson = []byte("")
|
||||||
|
} else {
|
||||||
|
o.PreComputeJson = b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *WebSocketResponse) GetPreComputeJson() []byte {
|
||||||
|
return o.PreComputeJson
|
||||||
|
}
|
||||||
|
|
||||||
func WebSocketResponseFromJson(data io.Reader) *WebSocketResponse {
|
func WebSocketResponseFromJson(data io.Reader) *WebSocketResponse {
|
||||||
decoder := json.NewDecoder(data)
|
decoder := json.NewDecoder(data)
|
||||||
var o WebSocketResponse
|
var o WebSocketResponse
|
||||||
|
|||||||
@@ -6,21 +6,26 @@ package store
|
|||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
|
||||||
|
l4g "github.com/alecthomas/log4go"
|
||||||
"github.com/go-gorp/gorp"
|
"github.com/go-gorp/gorp"
|
||||||
"github.com/mattermost/platform/model"
|
"github.com/mattermost/platform/model"
|
||||||
"github.com/mattermost/platform/utils"
|
"github.com/mattermost/platform/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MISSING_CHANNEL_ERROR = "store.sql_channel.get_by_name.missing.app_error"
|
MISSING_CHANNEL_ERROR = "store.sql_channel.get_by_name.missing.app_error"
|
||||||
MISSING_CHANNEL_MEMBER_ERROR = "store.sql_channel.get_member.missing.app_error"
|
MISSING_CHANNEL_MEMBER_ERROR = "store.sql_channel.get_member.missing.app_error"
|
||||||
CHANNEL_EXISTS_ERROR = "store.sql_channel.save_channel.exists.app_error"
|
CHANNEL_EXISTS_ERROR = "store.sql_channel.save_channel.exists.app_error"
|
||||||
|
ALL_CHANNEL_MEMBERS_FOR_USER_CACHE_SIZE = model.SESSION_CACHE_SIZE
|
||||||
|
ALL_CHANNEL_MEMBERS_FOR_USER_CACHE_SEC = 900 // 15 mins
|
||||||
)
|
)
|
||||||
|
|
||||||
type SqlChannelStore struct {
|
type SqlChannelStore struct {
|
||||||
*SqlStore
|
*SqlStore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var allChannelMembersForUserCache *utils.Cache = utils.NewLru(ALL_CHANNEL_MEMBERS_FOR_USER_CACHE_SIZE)
|
||||||
|
|
||||||
func NewSqlChannelStore(sqlStore *SqlStore) ChannelStore {
|
func NewSqlChannelStore(sqlStore *SqlStore) ChannelStore {
|
||||||
s := &SqlChannelStore{sqlStore}
|
s := &SqlChannelStore{sqlStore}
|
||||||
|
|
||||||
@@ -517,6 +522,8 @@ func (s SqlChannelStore) SaveMember(member *model.ChannelMember) StoreChannel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.InvalidateAllChannelMembersForUser(member.UserId)
|
||||||
|
|
||||||
storeChannel <- result
|
storeChannel <- result
|
||||||
close(storeChannel)
|
close(storeChannel)
|
||||||
}()
|
}()
|
||||||
@@ -619,6 +626,33 @@ func (s SqlChannelStore) GetMember(channelId string, userId string) StoreChannel
|
|||||||
return storeChannel
|
return storeChannel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (us SqlChannelStore) InvalidateAllChannelMembersForUser(userId string) {
|
||||||
|
allChannelMembersForUserCache.Remove(userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (us SqlChannelStore) IsUserInChannelUseCache(userId string, channelId string) bool {
|
||||||
|
if cacheItem, ok := allChannelMembersForUserCache.Get(userId); ok {
|
||||||
|
ids := cacheItem.(map[string]string)
|
||||||
|
if _, ok := ids[channelId]; ok {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := <-us.GetAllChannelMembersForUser(userId, true); result.Err != nil {
|
||||||
|
l4g.Error("SqlChannelStore.IsUserInChannelUseCache: " + result.Err.Error())
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
ids := result.Data.(map[string]string)
|
||||||
|
if _, ok := ids[channelId]; ok {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s SqlChannelStore) GetMemberForPost(postId string, userId string) StoreChannel {
|
func (s SqlChannelStore) GetMemberForPost(postId string, userId string) StoreChannel {
|
||||||
storeChannel := make(StoreChannel, 1)
|
storeChannel := make(StoreChannel, 1)
|
||||||
|
|
||||||
@@ -649,6 +683,52 @@ func (s SqlChannelStore) GetMemberForPost(postId string, userId string) StoreCha
|
|||||||
return storeChannel
|
return storeChannel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type allChannelMember struct {
|
||||||
|
ChannelId string
|
||||||
|
Roles string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SqlChannelStore) GetAllChannelMembersForUser(userId string, allowFromCache bool) StoreChannel {
|
||||||
|
storeChannel := make(StoreChannel, 1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
result := StoreResult{}
|
||||||
|
|
||||||
|
if allowFromCache {
|
||||||
|
if cacheItem, ok := allChannelMembersForUserCache.Get(userId); ok {
|
||||||
|
result.Data = cacheItem.(map[string]string)
|
||||||
|
storeChannel <- result
|
||||||
|
close(storeChannel)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var data []allChannelMember
|
||||||
|
_, err := s.GetReplica().Select(&data, "SELECT ChannelId, Roles FROM Channels, ChannelMembers WHERE Channels.Id = ChannelMembers.ChannelId AND ChannelMembers.UserId = :UserId AND Channels.DeleteAt = 0", map[string]interface{}{"UserId": userId})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
result.Err = model.NewLocAppError("SqlChannelStore.GetAllChannelMembersForUser", "store.sql_channel.get_channels.get.app_error", nil, "userId="+userId+", err="+err.Error())
|
||||||
|
} else {
|
||||||
|
|
||||||
|
ids := make(map[string]string)
|
||||||
|
for i := range data {
|
||||||
|
ids[data[i].ChannelId] = data[i].Roles
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Data = ids
|
||||||
|
|
||||||
|
if allowFromCache {
|
||||||
|
allChannelMembersForUserCache.AddWithExpiresInSecs(userId, ids, ALL_CHANNEL_MEMBERS_FOR_USER_CACHE_SEC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
storeChannel <- result
|
||||||
|
close(storeChannel)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return storeChannel
|
||||||
|
}
|
||||||
|
|
||||||
func (s SqlChannelStore) GetMemberCount(channelId string) StoreChannel {
|
func (s SqlChannelStore) GetMemberCount(channelId string) StoreChannel {
|
||||||
storeChannel := make(StoreChannel, 1)
|
storeChannel := make(StoreChannel, 1)
|
||||||
|
|
||||||
@@ -678,64 +758,6 @@ func (s SqlChannelStore) GetMemberCount(channelId string) StoreChannel {
|
|||||||
return storeChannel
|
return storeChannel
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s SqlChannelStore) GetExtraMembers(channelId string, limit int) StoreChannel {
|
|
||||||
storeChannel := make(StoreChannel, 1)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
result := StoreResult{}
|
|
||||||
|
|
||||||
var members []model.ExtraMember
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if limit != -1 {
|
|
||||||
_, err = s.GetReplica().Select(&members, `
|
|
||||||
SELECT
|
|
||||||
Id,
|
|
||||||
Nickname,
|
|
||||||
Email,
|
|
||||||
ChannelMembers.Roles,
|
|
||||||
Username
|
|
||||||
FROM
|
|
||||||
ChannelMembers,
|
|
||||||
Users
|
|
||||||
WHERE
|
|
||||||
ChannelMembers.UserId = Users.Id
|
|
||||||
AND Users.DeleteAt = 0
|
|
||||||
AND ChannelId = :ChannelId
|
|
||||||
LIMIT :Limit`, map[string]interface{}{"ChannelId": channelId, "Limit": limit})
|
|
||||||
} else {
|
|
||||||
_, err = s.GetReplica().Select(&members, `
|
|
||||||
SELECT
|
|
||||||
Id,
|
|
||||||
Nickname,
|
|
||||||
Email,
|
|
||||||
ChannelMembers.Roles,
|
|
||||||
Username
|
|
||||||
FROM
|
|
||||||
ChannelMembers,
|
|
||||||
Users
|
|
||||||
WHERE
|
|
||||||
ChannelMembers.UserId = Users.Id
|
|
||||||
AND Users.DeleteAt = 0
|
|
||||||
AND ChannelId = :ChannelId`, map[string]interface{}{"ChannelId": channelId})
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
result.Err = model.NewLocAppError("SqlChannelStore.GetExtraMembers", "store.sql_channel.get_extra_members.app_error", nil, "channel_id="+channelId+", "+err.Error())
|
|
||||||
} else {
|
|
||||||
for i := range members {
|
|
||||||
members[i].Sanitize(utils.Cfg.GetSanitizeOptions())
|
|
||||||
}
|
|
||||||
result.Data = members
|
|
||||||
}
|
|
||||||
|
|
||||||
storeChannel <- result
|
|
||||||
close(storeChannel)
|
|
||||||
}()
|
|
||||||
|
|
||||||
return storeChannel
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s SqlChannelStore) RemoveMember(channelId string, userId string) StoreChannel {
|
func (s SqlChannelStore) RemoveMember(channelId string, userId string) StoreChannel {
|
||||||
storeChannel := make(StoreChannel, 1)
|
storeChannel := make(StoreChannel, 1)
|
||||||
|
|
||||||
|
|||||||
@@ -408,11 +408,6 @@ func TestChannelMemberStore(t *testing.T) {
|
|||||||
t.Fatal("should have go member")
|
t.Fatal("should have go member")
|
||||||
}
|
}
|
||||||
|
|
||||||
extraMembers := (<-store.Channel().GetExtraMembers(o1.ChannelId, 20)).Data.([]model.ExtraMember)
|
|
||||||
if len(extraMembers) != 1 {
|
|
||||||
t.Fatal("should have 1 extra members")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := (<-store.Channel().SaveMember(&o1)).Err; err == nil {
|
if err := (<-store.Channel().SaveMember(&o1)).Err; err == nil {
|
||||||
t.Fatal("Should have been a duplicate")
|
t.Fatal("Should have been a duplicate")
|
||||||
}
|
}
|
||||||
@@ -422,18 +417,6 @@ func TestChannelMemberStore(t *testing.T) {
|
|||||||
if t4 != t3 {
|
if t4 != t3 {
|
||||||
t.Fatal("Should not update time upon failure")
|
t.Fatal("Should not update time upon failure")
|
||||||
}
|
}
|
||||||
|
|
||||||
// rejoin the channel and make sure that an inactive user isn't returned by GetExtraMambers
|
|
||||||
Must(store.Channel().SaveMember(&o2))
|
|
||||||
|
|
||||||
u2.DeleteAt = 1000
|
|
||||||
Must(store.User().Update(&u2, true))
|
|
||||||
|
|
||||||
if result := <-store.Channel().GetExtraMembers(o1.ChannelId, 20); result.Err != nil {
|
|
||||||
t.Fatal(result.Err)
|
|
||||||
} else if extraMembers := result.Data.([]model.ExtraMember); len(extraMembers) != 1 {
|
|
||||||
t.Fatal("should have 1 extra members")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestChannelDeleteMemberStore(t *testing.T) {
|
func TestChannelDeleteMemberStore(t *testing.T) {
|
||||||
@@ -534,6 +517,42 @@ func TestChannelStoreGetChannels(t *testing.T) {
|
|||||||
if list.Channels[0].Id != o1.Id {
|
if list.Channels[0].Id != o1.Id {
|
||||||
t.Fatal("missing channel")
|
t.Fatal("missing channel")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
acresult := <-store.Channel().GetAllChannelMembersForUser(m1.UserId, false)
|
||||||
|
ids := acresult.Data.(map[string]string)
|
||||||
|
if _, ok := ids[o1.Id]; !ok {
|
||||||
|
t.Fatal("missing channel")
|
||||||
|
}
|
||||||
|
|
||||||
|
acresult2 := <-store.Channel().GetAllChannelMembersForUser(m1.UserId, true)
|
||||||
|
ids2 := acresult2.Data.(map[string]string)
|
||||||
|
if _, ok := ids2[o1.Id]; !ok {
|
||||||
|
t.Fatal("missing channel")
|
||||||
|
}
|
||||||
|
|
||||||
|
acresult3 := <-store.Channel().GetAllChannelMembersForUser(m1.UserId, true)
|
||||||
|
ids3 := acresult3.Data.(map[string]string)
|
||||||
|
if _, ok := ids3[o1.Id]; !ok {
|
||||||
|
t.Fatal("missing channel")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !store.Channel().IsUserInChannelUseCache(m1.UserId, o1.Id) {
|
||||||
|
t.Fatal("missing channel")
|
||||||
|
}
|
||||||
|
|
||||||
|
if store.Channel().IsUserInChannelUseCache(m1.UserId, o2.Id) {
|
||||||
|
t.Fatal("missing channel")
|
||||||
|
}
|
||||||
|
|
||||||
|
if store.Channel().IsUserInChannelUseCache(m1.UserId, "blahblah") {
|
||||||
|
t.Fatal("missing channel")
|
||||||
|
}
|
||||||
|
|
||||||
|
if store.Channel().IsUserInChannelUseCache("blahblah", "blahblah") {
|
||||||
|
t.Fatal("missing channel")
|
||||||
|
}
|
||||||
|
|
||||||
|
store.Channel().InvalidateAllChannelMembersForUser(m1.UserId)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestChannelStoreGetMoreChannels(t *testing.T) {
|
func TestChannelStoreGetMoreChannels(t *testing.T) {
|
||||||
@@ -974,22 +993,10 @@ func TestUpdateExtrasByUser(t *testing.T) {
|
|||||||
t.Fatal("failed to update extras by user: %v", result.Err)
|
t.Fatal("failed to update extras by user: %v", result.Err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if result := <-store.Channel().GetExtraMembers(c1.Id, -1); result.Err != nil {
|
|
||||||
t.Fatal("failed to get extras: %v", result.Err)
|
|
||||||
} else if len(result.Data.([]model.ExtraMember)) != 0 {
|
|
||||||
t.Fatal("got incorrect member count %v", len(result.Data.([]model.ExtraMember)))
|
|
||||||
}
|
|
||||||
|
|
||||||
u1.DeleteAt = 0
|
u1.DeleteAt = 0
|
||||||
Must(store.User().Update(u1, true))
|
Must(store.User().Update(u1, true))
|
||||||
|
|
||||||
if result := <-store.Channel().ExtraUpdateByUser(u1.Id, u1.DeleteAt); result.Err != nil {
|
if result := <-store.Channel().ExtraUpdateByUser(u1.Id, u1.DeleteAt); result.Err != nil {
|
||||||
t.Fatal("failed to update extras by user: %v", result.Err)
|
t.Fatal("failed to update extras by user: %v", result.Err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if result := <-store.Channel().GetExtraMembers(c1.Id, -1); result.Err != nil {
|
|
||||||
t.Fatal("failed to get extras: %v", result.Err)
|
|
||||||
} else if len(result.Data.([]model.ExtraMember)) != 1 {
|
|
||||||
t.Fatal("got incorrect member count %v", len(result.Data.([]model.ExtraMember)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ func (s SqlPostStore) CreateIndexesIfNotExists() {
|
|||||||
s.CreateIndexIfNotExists("idx_posts_create_at", "Posts", "CreateAt")
|
s.CreateIndexIfNotExists("idx_posts_create_at", "Posts", "CreateAt")
|
||||||
s.CreateIndexIfNotExists("idx_posts_channel_id", "Posts", "ChannelId")
|
s.CreateIndexIfNotExists("idx_posts_channel_id", "Posts", "ChannelId")
|
||||||
s.CreateIndexIfNotExists("idx_posts_root_id", "Posts", "RootId")
|
s.CreateIndexIfNotExists("idx_posts_root_id", "Posts", "RootId")
|
||||||
|
s.CreateIndexIfNotExists("idx_posts_user_id", "Posts", "UserId")
|
||||||
|
|
||||||
s.CreateFullTextIndexIfNotExists("idx_posts_message_txt", "Posts", "Message")
|
s.CreateFullTextIndexIfNotExists("idx_posts_message_txt", "Posts", "Message")
|
||||||
s.CreateFullTextIndexIfNotExists("idx_posts_hashtags_txt", "Posts", "Hashtags")
|
s.CreateFullTextIndexIfNotExists("idx_posts_hashtags_txt", "Posts", "Hashtags")
|
||||||
@@ -811,47 +812,36 @@ func (s SqlPostStore) AnalyticsUserCountsWithPostsByDay(teamId string) StoreChan
|
|||||||
result := StoreResult{}
|
result := StoreResult{}
|
||||||
|
|
||||||
query :=
|
query :=
|
||||||
`SELECT
|
`SELECT DISTINCT
|
||||||
t1.Name, COUNT(t1.UserId) AS Value
|
|
||||||
FROM
|
|
||||||
(SELECT DISTINCT
|
|
||||||
DATE(FROM_UNIXTIME(Posts.CreateAt / 1000)) AS Name,
|
DATE(FROM_UNIXTIME(Posts.CreateAt / 1000)) AS Name,
|
||||||
Posts.UserId
|
COUNT(DISTINCT Posts.UserId) AS Value
|
||||||
FROM
|
FROM Posts
|
||||||
Posts, Channels
|
INNER JOIN Channels
|
||||||
WHERE
|
ON Posts.ChannelId = Channels.Id`
|
||||||
Posts.ChannelId = Channels.Id`
|
|
||||||
|
|
||||||
if len(teamId) > 0 {
|
if len(teamId) > 0 {
|
||||||
query += " AND Channels.TeamId = :TeamId"
|
query += " AND Channels.TeamId = :TeamId"
|
||||||
}
|
}
|
||||||
|
|
||||||
query += ` AND Posts.CreateAt >= :StartTime AND Posts.CreateAt <= :EndTime
|
query += ` AND Posts.CreateAt >= :StartTime AND Posts.CreateAt <= :EndTime
|
||||||
ORDER BY Name DESC) AS t1
|
GROUP BY DATE(FROM_UNIXTIME(Posts.CreateAt / 1000))
|
||||||
GROUP BY Name
|
|
||||||
ORDER BY Name DESC
|
ORDER BY Name DESC
|
||||||
LIMIT 30`
|
LIMIT 30`
|
||||||
|
|
||||||
if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
|
if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
|
||||||
query =
|
query =
|
||||||
`SELECT
|
`SELECT
|
||||||
TO_CHAR(t1.Name, 'YYYY-MM-DD') AS Name, COUNT(t1.UserId) AS Value
|
TO_CHAR(DATE(TO_TIMESTAMP(Posts.CreateAt / 1000)), 'YYYY-MM-DD') AS Name, COUNT(DISTINCT Posts.UserId) AS Value
|
||||||
FROM
|
FROM Posts
|
||||||
(SELECT DISTINCT
|
INNER JOIN Channels
|
||||||
DATE(TO_TIMESTAMP(Posts.CreateAt / 1000)) AS Name,
|
ON Posts.ChannelId = Channels.Id`
|
||||||
Posts.UserId
|
|
||||||
FROM
|
|
||||||
Posts, Channels
|
|
||||||
WHERE
|
|
||||||
Posts.ChannelId = Channels.Id`
|
|
||||||
|
|
||||||
if len(teamId) > 0 {
|
if len(teamId) > 0 {
|
||||||
query += " AND Channels.TeamId = :TeamId"
|
query += " AND Channels.TeamId = :TeamId"
|
||||||
}
|
}
|
||||||
|
|
||||||
query += ` AND Posts.CreateAt >= :StartTime AND Posts.CreateAt <= :EndTime
|
query += ` AND Posts.CreateAt >= :StartTime AND Posts.CreateAt <= :EndTime
|
||||||
ORDER BY Name DESC) AS t1
|
GROUP BY DATE(TO_TIMESTAMP(Posts.CreateAt / 1000))
|
||||||
GROUP BY Name
|
|
||||||
ORDER BY Name DESC
|
ORDER BY Name DESC
|
||||||
LIMIT 30`
|
LIMIT 30`
|
||||||
}
|
}
|
||||||
@@ -884,15 +874,12 @@ func (s SqlPostStore) AnalyticsPostCountsByDay(teamId string) StoreChannel {
|
|||||||
result := StoreResult{}
|
result := StoreResult{}
|
||||||
|
|
||||||
query :=
|
query :=
|
||||||
`SELECT
|
`SELECT
|
||||||
Name, COUNT(Value) AS Value
|
|
||||||
FROM
|
|
||||||
(SELECT
|
|
||||||
DATE(FROM_UNIXTIME(Posts.CreateAt / 1000)) AS Name,
|
DATE(FROM_UNIXTIME(Posts.CreateAt / 1000)) AS Name,
|
||||||
'1' AS Value
|
COUNT(Posts.Id) AS Value
|
||||||
FROM
|
FROM Posts
|
||||||
Posts, Channels
|
INNER JOIN Channels
|
||||||
WHERE
|
ON
|
||||||
Posts.ChannelId = Channels.Id`
|
Posts.ChannelId = Channels.Id`
|
||||||
|
|
||||||
if len(teamId) > 0 {
|
if len(teamId) > 0 {
|
||||||
@@ -900,31 +887,26 @@ func (s SqlPostStore) AnalyticsPostCountsByDay(teamId string) StoreChannel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
query += ` AND Posts.CreateAt <= :EndTime
|
query += ` AND Posts.CreateAt <= :EndTime
|
||||||
AND Posts.CreateAt >= :StartTime) AS t1
|
AND Posts.CreateAt >= :StartTime
|
||||||
GROUP BY Name
|
GROUP BY DATE(FROM_UNIXTIME(Posts.CreateAt / 1000))
|
||||||
ORDER BY Name DESC
|
ORDER BY Name DESC
|
||||||
LIMIT 30`
|
LIMIT 30`
|
||||||
|
|
||||||
if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
|
if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
|
||||||
query =
|
query =
|
||||||
`SELECT
|
`SELECT
|
||||||
Name, COUNT(Value) AS Value
|
TO_CHAR(DATE(TO_TIMESTAMP(Posts.CreateAt / 1000)), 'YYYY-MM-DD') AS Name, Count(Posts.Id) AS Value
|
||||||
FROM
|
FROM Posts
|
||||||
(SELECT
|
INNER JOIN Channels
|
||||||
TO_CHAR(DATE(TO_TIMESTAMP(Posts.CreateAt / 1000)), 'YYYY-MM-DD') AS Name,
|
ON Posts.ChannelId = Channels.Id`
|
||||||
'1' AS Value
|
|
||||||
FROM
|
|
||||||
Posts, Channels
|
|
||||||
WHERE
|
|
||||||
Posts.ChannelId = Channels.Id`
|
|
||||||
|
|
||||||
if len(teamId) > 0 {
|
if len(teamId) > 0 {
|
||||||
query += " AND Channels.TeamId = :TeamId"
|
query += " AND Channels.TeamId = :TeamId"
|
||||||
}
|
}
|
||||||
|
|
||||||
query += ` AND Posts.CreateAt <= :EndTime
|
query += ` AND Posts.CreateAt <= :EndTime
|
||||||
AND Posts.CreateAt >= :StartTime) AS t1
|
AND Posts.CreateAt >= :StartTime
|
||||||
GROUP BY Name
|
GROUP BY DATE(TO_TIMESTAMP(Posts.CreateAt / 1000))
|
||||||
ORDER BY Name DESC
|
ORDER BY Name DESC
|
||||||
LIMIT 30`
|
LIMIT 30`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package store
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/mattermost/platform/model"
|
"github.com/mattermost/platform/model"
|
||||||
)
|
)
|
||||||
@@ -43,11 +44,11 @@ func (s SqlStatusStore) SaveOrUpdate(status *model.Status) StoreChannel {
|
|||||||
|
|
||||||
if err := s.GetReplica().SelectOne(&model.Status{}, "SELECT * FROM Status WHERE UserId = :UserId", map[string]interface{}{"UserId": status.UserId}); err == nil {
|
if err := s.GetReplica().SelectOne(&model.Status{}, "SELECT * FROM Status WHERE UserId = :UserId", map[string]interface{}{"UserId": status.UserId}); err == nil {
|
||||||
if _, err := s.GetMaster().Update(status); err != nil {
|
if _, err := s.GetMaster().Update(status); err != nil {
|
||||||
result.Err = model.NewLocAppError("SqlStatusStore.SaveOrUpdate", "store.sql_status.update.app_error", nil, "")
|
result.Err = model.NewLocAppError("SqlStatusStore.SaveOrUpdate", "store.sql_status.update.app_error", nil, err.Error())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err := s.GetMaster().Insert(status); err != nil {
|
if err := s.GetMaster().Insert(status); err != nil {
|
||||||
result.Err = model.NewLocAppError("SqlStatusStore.SaveOrUpdate", "store.sql_status.save.app_error", nil, "")
|
result.Err = model.NewLocAppError("SqlStatusStore.SaveOrUpdate", "store.sql_status.save.app_error", nil, err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,6 +90,38 @@ func (s SqlStatusStore) Get(userId string) StoreChannel {
|
|||||||
return storeChannel
|
return storeChannel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s SqlStatusStore) GetByIds(userIds []string) StoreChannel {
|
||||||
|
storeChannel := make(StoreChannel, 1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
result := StoreResult{}
|
||||||
|
|
||||||
|
props := make(map[string]interface{})
|
||||||
|
idQuery := ""
|
||||||
|
|
||||||
|
for index, userId := range userIds {
|
||||||
|
if len(idQuery) > 0 {
|
||||||
|
idQuery += ", "
|
||||||
|
}
|
||||||
|
|
||||||
|
props["userId"+strconv.Itoa(index)] = userId
|
||||||
|
idQuery += ":userId" + strconv.Itoa(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
var statuses []*model.Status
|
||||||
|
if _, err := s.GetReplica().Select(&statuses, "SELECT * FROM Status WHERE UserId IN ("+idQuery+")", props); err != nil {
|
||||||
|
result.Err = model.NewLocAppError("SqlStatusStore.GetByIds", "store.sql_status.get.app_error", nil, err.Error())
|
||||||
|
} else {
|
||||||
|
result.Data = statuses
|
||||||
|
}
|
||||||
|
|
||||||
|
storeChannel <- result
|
||||||
|
close(storeChannel)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return storeChannel
|
||||||
|
}
|
||||||
|
|
||||||
func (s SqlStatusStore) GetOnlineAway() StoreChannel {
|
func (s SqlStatusStore) GetOnlineAway() StoreChannel {
|
||||||
storeChannel := make(StoreChannel, 1)
|
storeChannel := make(StoreChannel, 1)
|
||||||
|
|
||||||
@@ -96,7 +129,7 @@ func (s SqlStatusStore) GetOnlineAway() StoreChannel {
|
|||||||
result := StoreResult{}
|
result := StoreResult{}
|
||||||
|
|
||||||
var statuses []*model.Status
|
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 {
|
if _, err := s.GetReplica().Select(&statuses, "SELECT * FROM Status WHERE Status = :Online OR Status = :Away LIMIT 300", map[string]interface{}{"Online": model.STATUS_ONLINE, "Away": model.STATUS_AWAY}); err != nil {
|
||||||
result.Err = model.NewLocAppError("SqlStatusStore.GetOnlineAway", "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 {
|
} else {
|
||||||
result.Data = statuses
|
result.Data = statuses
|
||||||
@@ -157,7 +190,7 @@ func (s SqlStatusStore) ResetAll() StoreChannel {
|
|||||||
go func() {
|
go func() {
|
||||||
result := StoreResult{}
|
result := StoreResult{}
|
||||||
|
|
||||||
if _, err := s.GetMaster().Exec("UPDATE Status SET Status = :Status", map[string]interface{}{"Status": model.STATUS_OFFLINE}); err != nil {
|
if _, err := s.GetMaster().Exec("UPDATE Status SET Status = :Status WHERE Manual = 0", map[string]interface{}{"Status": model.STATUS_OFFLINE}); err != nil {
|
||||||
result.Err = model.NewLocAppError("SqlStatusStore.ResetAll", "store.sql_status.reset_all.app_error", nil, "")
|
result.Err = model.NewLocAppError("SqlStatusStore.ResetAll", "store.sql_status.reset_all.app_error", nil, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,15 @@ func TestSqlStatusStore(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if result := <-store.Status().GetByIds([]string{status.UserId, "junk"}); result.Err != nil {
|
||||||
|
t.Fatal(result.Err)
|
||||||
|
} else {
|
||||||
|
statuses := result.Data.([]*model.Status)
|
||||||
|
if len(statuses) != 1 {
|
||||||
|
t.Fatal("should only have 1 status")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := (<-store.Status().ResetAll()).Err; err != nil {
|
if err := (<-store.Status().ResetAll()).Err; err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import (
|
|||||||
const (
|
const (
|
||||||
INDEX_TYPE_FULL_TEXT = "full_text"
|
INDEX_TYPE_FULL_TEXT = "full_text"
|
||||||
INDEX_TYPE_DEFAULT = "default"
|
INDEX_TYPE_DEFAULT = "default"
|
||||||
|
MAX_DB_CONN_LIFETIME = 15
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -94,9 +95,7 @@ func initConnection() *SqlStore {
|
|||||||
|
|
||||||
if len(utils.Cfg.SqlSettings.DataSourceReplicas) == 0 {
|
if len(utils.Cfg.SqlSettings.DataSourceReplicas) == 0 {
|
||||||
sqlStore.replicas = make([]*gorp.DbMap, 1)
|
sqlStore.replicas = make([]*gorp.DbMap, 1)
|
||||||
sqlStore.replicas[0] = setupConnection(fmt.Sprintf("replica-%v", 0), utils.Cfg.SqlSettings.DriverName, utils.Cfg.SqlSettings.DataSource,
|
sqlStore.replicas[0] = sqlStore.master
|
||||||
utils.Cfg.SqlSettings.MaxIdleConns, utils.Cfg.SqlSettings.MaxOpenConns,
|
|
||||||
utils.Cfg.SqlSettings.Trace)
|
|
||||||
} else {
|
} else {
|
||||||
sqlStore.replicas = make([]*gorp.DbMap, len(utils.Cfg.SqlSettings.DataSourceReplicas))
|
sqlStore.replicas = make([]*gorp.DbMap, len(utils.Cfg.SqlSettings.DataSourceReplicas))
|
||||||
for i, replica := range utils.Cfg.SqlSettings.DataSourceReplicas {
|
for i, replica := range utils.Cfg.SqlSettings.DataSourceReplicas {
|
||||||
@@ -183,6 +182,7 @@ func setupConnection(con_type string, driver string, dataSource string, maxIdle
|
|||||||
|
|
||||||
db.SetMaxIdleConns(maxIdle)
|
db.SetMaxIdleConns(maxIdle)
|
||||||
db.SetMaxOpenConns(maxOpen)
|
db.SetMaxOpenConns(maxOpen)
|
||||||
|
db.SetConnMaxLifetime(time.Duration(MAX_DB_CONN_LIFETIME) * time.Minute)
|
||||||
|
|
||||||
var dbmap *gorp.DbMap
|
var dbmap *gorp.DbMap
|
||||||
|
|
||||||
@@ -205,6 +205,26 @@ func setupConnection(con_type string, driver string, dataSource string, maxIdle
|
|||||||
return dbmap
|
return dbmap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ss SqlStore) TotalMasterDbConnections() int {
|
||||||
|
return ss.GetMaster().Db.Stats().OpenConnections
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss SqlStore) TotalReadDbConnections() int {
|
||||||
|
|
||||||
|
if len(utils.Cfg.SqlSettings.DataSourceReplicas) == 0 {
|
||||||
|
return 0
|
||||||
|
} else {
|
||||||
|
count := 0
|
||||||
|
for _, db := range ss.replicas {
|
||||||
|
count = count + db.Db.Stats().OpenConnections
|
||||||
|
}
|
||||||
|
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
func (ss SqlStore) GetCurrentSchemaVersion() string {
|
func (ss SqlStore) GetCurrentSchemaVersion() string {
|
||||||
version, _ := ss.GetMaster().SelectStr("SELECT Value FROM Systems WHERE Name='Version'")
|
version, _ := ss.GetMaster().SelectStr("SELECT Value FROM Systems WHERE Name='Version'")
|
||||||
return version
|
return version
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package store
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/mattermost/platform/model"
|
"github.com/mattermost/platform/model"
|
||||||
"github.com/mattermost/platform/utils"
|
"github.com/mattermost/platform/utils"
|
||||||
@@ -441,14 +442,14 @@ func (s SqlTeamStore) GetMember(teamId string, userId string) StoreChannel {
|
|||||||
return storeChannel
|
return storeChannel
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s SqlTeamStore) GetMembers(teamId string) StoreChannel {
|
func (s SqlTeamStore) GetMembers(teamId string, offset int, limit int) StoreChannel {
|
||||||
storeChannel := make(StoreChannel, 1)
|
storeChannel := make(StoreChannel, 1)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
result := StoreResult{}
|
result := StoreResult{}
|
||||||
|
|
||||||
var members []*model.TeamMember
|
var members []*model.TeamMember
|
||||||
_, err := s.GetReplica().Select(&members, "SELECT * FROM TeamMembers WHERE TeamId = :TeamId", map[string]interface{}{"TeamId": teamId})
|
_, err := s.GetReplica().Select(&members, "SELECT * FROM TeamMembers WHERE TeamId = :TeamId AND DeleteAt = 0 LIMIT :Limit OFFSET :Offset", map[string]interface{}{"TeamId": teamId, "Offset": offset, "Limit": limit})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
result.Err = model.NewLocAppError("SqlTeamStore.GetMembers", "store.sql_team.get_members.app_error", nil, "teamId="+teamId+" "+err.Error())
|
result.Err = model.NewLocAppError("SqlTeamStore.GetMembers", "store.sql_team.get_members.app_error", nil, "teamId="+teamId+" "+err.Error())
|
||||||
} else {
|
} else {
|
||||||
@@ -462,6 +463,70 @@ func (s SqlTeamStore) GetMembers(teamId string) StoreChannel {
|
|||||||
return storeChannel
|
return storeChannel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s SqlTeamStore) GetMemberCount(teamId string) StoreChannel {
|
||||||
|
storeChannel := make(StoreChannel, 1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
result := StoreResult{}
|
||||||
|
|
||||||
|
count, err := s.GetReplica().SelectInt(`
|
||||||
|
SELECT
|
||||||
|
count(*)
|
||||||
|
FROM
|
||||||
|
TeamMembers,
|
||||||
|
Users
|
||||||
|
WHERE
|
||||||
|
TeamMembers.UserId = Users.Id
|
||||||
|
AND TeamMembers.TeamId = :TeamId
|
||||||
|
AND TeamMembers.DeleteAt = 0
|
||||||
|
AND Users.DeleteAt = 0`, map[string]interface{}{"TeamId": teamId})
|
||||||
|
if err != nil {
|
||||||
|
result.Err = model.NewLocAppError("SqlTeamStore.GetMemberCount", "store.sql_team.get_member_count.app_error", nil, "teamId="+teamId+" "+err.Error())
|
||||||
|
} else {
|
||||||
|
result.Data = count
|
||||||
|
}
|
||||||
|
|
||||||
|
storeChannel <- result
|
||||||
|
close(storeChannel)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return storeChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SqlTeamStore) GetMembersByIds(teamId string, userIds []string) StoreChannel {
|
||||||
|
storeChannel := make(StoreChannel, 1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
result := StoreResult{}
|
||||||
|
|
||||||
|
var members []*model.TeamMember
|
||||||
|
props := make(map[string]interface{})
|
||||||
|
idQuery := ""
|
||||||
|
|
||||||
|
for index, userId := range userIds {
|
||||||
|
if len(idQuery) > 0 {
|
||||||
|
idQuery += ", "
|
||||||
|
}
|
||||||
|
|
||||||
|
props["userId"+strconv.Itoa(index)] = userId
|
||||||
|
idQuery += ":userId" + strconv.Itoa(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
props["TeamId"] = teamId
|
||||||
|
|
||||||
|
if _, err := s.GetReplica().Select(&members, "SELECT * FROM TeamMembers WHERE TeamId = :TeamId AND UserId IN ("+idQuery+") AND DeleteAt = 0", props); err != nil {
|
||||||
|
result.Err = model.NewLocAppError("SqlTeamStore.GetMembersByIds", "store.sql_team.get_members_by_ids.app_error", nil, "teamId="+teamId+" "+err.Error())
|
||||||
|
} else {
|
||||||
|
result.Data = members
|
||||||
|
}
|
||||||
|
|
||||||
|
storeChannel <- result
|
||||||
|
close(storeChannel)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return storeChannel
|
||||||
|
}
|
||||||
|
|
||||||
func (s SqlTeamStore) GetTeamsForUser(userId string) StoreChannel {
|
func (s SqlTeamStore) GetTeamsForUser(userId string) StoreChannel {
|
||||||
storeChannel := make(StoreChannel, 1)
|
storeChannel := make(StoreChannel, 1)
|
||||||
|
|
||||||
|
|||||||
@@ -298,7 +298,7 @@ func TestTeamMembers(t *testing.T) {
|
|||||||
Must(store.Team().SaveMember(m2))
|
Must(store.Team().SaveMember(m2))
|
||||||
Must(store.Team().SaveMember(m3))
|
Must(store.Team().SaveMember(m3))
|
||||||
|
|
||||||
if r1 := <-store.Team().GetMembers(teamId1); r1.Err != nil {
|
if r1 := <-store.Team().GetMembers(teamId1, 0, 100); r1.Err != nil {
|
||||||
t.Fatal(r1.Err)
|
t.Fatal(r1.Err)
|
||||||
} else {
|
} else {
|
||||||
ms := r1.Data.([]*model.TeamMember)
|
ms := r1.Data.([]*model.TeamMember)
|
||||||
@@ -308,7 +308,7 @@ func TestTeamMembers(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if r1 := <-store.Team().GetMembers(teamId2); r1.Err != nil {
|
if r1 := <-store.Team().GetMembers(teamId2, 0, 100); r1.Err != nil {
|
||||||
t.Fatal(r1.Err)
|
t.Fatal(r1.Err)
|
||||||
} else {
|
} else {
|
||||||
ms := r1.Data.([]*model.TeamMember)
|
ms := r1.Data.([]*model.TeamMember)
|
||||||
@@ -342,7 +342,7 @@ func TestTeamMembers(t *testing.T) {
|
|||||||
t.Fatal(r1.Err)
|
t.Fatal(r1.Err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if r1 := <-store.Team().GetMembers(teamId1); r1.Err != nil {
|
if r1 := <-store.Team().GetMembers(teamId1, 0, 100); r1.Err != nil {
|
||||||
t.Fatal(r1.Err)
|
t.Fatal(r1.Err)
|
||||||
} else {
|
} else {
|
||||||
ms := r1.Data.([]*model.TeamMember)
|
ms := r1.Data.([]*model.TeamMember)
|
||||||
@@ -363,7 +363,7 @@ func TestTeamMembers(t *testing.T) {
|
|||||||
t.Fatal(r1.Err)
|
t.Fatal(r1.Err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if r1 := <-store.Team().GetMembers(teamId1); r1.Err != nil {
|
if r1 := <-store.Team().GetMembers(teamId1, 0, 100); r1.Err != nil {
|
||||||
t.Fatal(r1.Err)
|
t.Fatal(r1.Err)
|
||||||
} else {
|
} else {
|
||||||
ms := r1.Data.([]*model.TeamMember)
|
ms := r1.Data.([]*model.TeamMember)
|
||||||
@@ -434,3 +434,74 @@ func TestGetTeamMember(t *testing.T) {
|
|||||||
t.Fatal("empty team id - should have failed")
|
t.Fatal("empty team id - should have failed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetTeamMembersByIds(t *testing.T) {
|
||||||
|
Setup()
|
||||||
|
|
||||||
|
teamId1 := model.NewId()
|
||||||
|
|
||||||
|
m1 := &model.TeamMember{TeamId: teamId1, UserId: model.NewId()}
|
||||||
|
Must(store.Team().SaveMember(m1))
|
||||||
|
|
||||||
|
if r := <-store.Team().GetMembersByIds(m1.TeamId, []string{m1.UserId}); r.Err != nil {
|
||||||
|
t.Fatal(r.Err)
|
||||||
|
} else {
|
||||||
|
rm1 := r.Data.([]*model.TeamMember)[0]
|
||||||
|
|
||||||
|
if rm1.TeamId != m1.TeamId {
|
||||||
|
t.Fatal("bad team id")
|
||||||
|
}
|
||||||
|
|
||||||
|
if rm1.UserId != m1.UserId {
|
||||||
|
t.Fatal("bad user id")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m2 := &model.TeamMember{TeamId: teamId1, UserId: model.NewId()}
|
||||||
|
Must(store.Team().SaveMember(m2))
|
||||||
|
|
||||||
|
if r := <-store.Team().GetMembersByIds(m1.TeamId, []string{m1.UserId, m2.UserId, model.NewId()}); r.Err != nil {
|
||||||
|
t.Fatal(r.Err)
|
||||||
|
} else {
|
||||||
|
rm := r.Data.([]*model.TeamMember)
|
||||||
|
|
||||||
|
if len(rm) != 2 {
|
||||||
|
t.Fatal("return wrong number of results")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r := <-store.Team().GetMembersByIds(m1.TeamId, []string{}); r.Err == nil {
|
||||||
|
t.Fatal("empty user ids - should have failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTeamStoreMemberCount(t *testing.T) {
|
||||||
|
Setup()
|
||||||
|
|
||||||
|
u1 := &model.User{}
|
||||||
|
u1.Email = model.NewId()
|
||||||
|
Must(store.User().Save(u1))
|
||||||
|
|
||||||
|
teamId1 := model.NewId()
|
||||||
|
m1 := &model.TeamMember{TeamId: teamId1, UserId: u1.Id}
|
||||||
|
Must(store.Team().SaveMember(m1))
|
||||||
|
|
||||||
|
if result := <-store.Team().GetMemberCount(teamId1); result.Err != nil {
|
||||||
|
t.Fatal(result.Err)
|
||||||
|
} else {
|
||||||
|
if result.Data.(int64) != 1 {
|
||||||
|
t.Fatal("wrong count")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m2 := &model.TeamMember{TeamId: teamId1, UserId: model.NewId()}
|
||||||
|
Must(store.Team().SaveMember(m2))
|
||||||
|
|
||||||
|
if result := <-store.Team().GetMemberCount(teamId1); result.Err != nil {
|
||||||
|
t.Fatal(result.Err)
|
||||||
|
} else {
|
||||||
|
if result.Data.(int64) != 1 {
|
||||||
|
t.Fatal("wrong count")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,14 +15,20 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MISSING_ACCOUNT_ERROR = "store.sql_user.missing_account.const"
|
MISSING_ACCOUNT_ERROR = "store.sql_user.missing_account.const"
|
||||||
MISSING_AUTH_ACCOUNT_ERROR = "store.sql_user.get_by_auth.missing_account.app_error"
|
MISSING_AUTH_ACCOUNT_ERROR = "store.sql_user.get_by_auth.missing_account.app_error"
|
||||||
|
PROFILES_IN_CHANNEL_CACHE_SIZE = 5000
|
||||||
|
PROFILES_IN_CHANNEL_CACHE_SEC = 900 // 15 mins
|
||||||
|
USER_SEARCH_TYPE_ALL = "Username, FirstName, LastName, Nickname"
|
||||||
|
USER_SEARCH_TYPE_USERNAME = "Username"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SqlUserStore struct {
|
type SqlUserStore struct {
|
||||||
*SqlStore
|
*SqlStore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var profilesInChannelCache *utils.Cache = utils.NewLru(PROFILES_IN_CHANNEL_CACHE_SIZE)
|
||||||
|
|
||||||
func NewSqlUserStore(sqlStore *SqlStore) UserStore {
|
func NewSqlUserStore(sqlStore *SqlStore) UserStore {
|
||||||
us := &SqlUserStore{sqlStore}
|
us := &SqlUserStore{sqlStore}
|
||||||
|
|
||||||
@@ -49,6 +55,9 @@ func NewSqlUserStore(sqlStore *SqlStore) UserStore {
|
|||||||
|
|
||||||
func (us SqlUserStore) CreateIndexesIfNotExists() {
|
func (us SqlUserStore) CreateIndexesIfNotExists() {
|
||||||
us.CreateIndexIfNotExists("idx_users_email", "Users", "Email")
|
us.CreateIndexIfNotExists("idx_users_email", "Users", "Email")
|
||||||
|
|
||||||
|
us.CreateFullTextIndexIfNotExists("idx_users_username_txt", "Users", USER_SEARCH_TYPE_USERNAME)
|
||||||
|
us.CreateFullTextIndexIfNotExists("idx_users_all_names_txt", "Users", USER_SEARCH_TYPE_ALL)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (us SqlUserStore) Save(user *model.User) StoreChannel {
|
func (us SqlUserStore) Save(user *model.User) StoreChannel {
|
||||||
@@ -457,7 +466,7 @@ func (s SqlUserStore) GetEtagForAllProfiles() StoreChannel {
|
|||||||
return storeChannel
|
return storeChannel
|
||||||
}
|
}
|
||||||
|
|
||||||
func (us SqlUserStore) GetAllProfiles() StoreChannel {
|
func (us SqlUserStore) GetAllProfiles(offset int, limit int) StoreChannel {
|
||||||
|
|
||||||
storeChannel := make(StoreChannel, 1)
|
storeChannel := make(StoreChannel, 1)
|
||||||
|
|
||||||
@@ -466,8 +475,8 @@ func (us SqlUserStore) GetAllProfiles() StoreChannel {
|
|||||||
|
|
||||||
var users []*model.User
|
var users []*model.User
|
||||||
|
|
||||||
if _, err := us.GetReplica().Select(&users, "SELECT * FROM Users"); err != nil {
|
if _, err := us.GetReplica().Select(&users, "SELECT * FROM Users ORDER BY Username ASC LIMIT :Limit OFFSET :Offset", map[string]interface{}{"Offset": offset, "Limit": limit}); err != nil {
|
||||||
result.Err = model.NewLocAppError("SqlUserStore.GetProfiles", "store.sql_user.get_profiles.app_error", nil, err.Error())
|
result.Err = model.NewLocAppError("SqlUserStore.GetAllProfiles", "store.sql_user.get_profiles.app_error", nil, err.Error())
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
userMap := make(map[string]*model.User)
|
userMap := make(map[string]*model.User)
|
||||||
@@ -509,7 +518,7 @@ func (s SqlUserStore) GetEtagForProfiles(teamId string) StoreChannel {
|
|||||||
return storeChannel
|
return storeChannel
|
||||||
}
|
}
|
||||||
|
|
||||||
func (us SqlUserStore) GetProfiles(teamId string) StoreChannel {
|
func (us SqlUserStore) GetProfiles(teamId string, offset int, limit int) StoreChannel {
|
||||||
|
|
||||||
storeChannel := make(StoreChannel, 1)
|
storeChannel := make(StoreChannel, 1)
|
||||||
|
|
||||||
@@ -518,7 +527,7 @@ func (us SqlUserStore) GetProfiles(teamId string) StoreChannel {
|
|||||||
|
|
||||||
var users []*model.User
|
var users []*model.User
|
||||||
|
|
||||||
if _, err := us.GetReplica().Select(&users, "SELECT Users.* FROM Users, TeamMembers WHERE TeamMembers.TeamId = :TeamId AND Users.Id = TeamMembers.UserId", map[string]interface{}{"TeamId": teamId}); err != nil {
|
if _, err := us.GetReplica().Select(&users, "SELECT Users.* FROM Users, TeamMembers WHERE TeamMembers.TeamId = :TeamId AND Users.Id = TeamMembers.UserId AND TeamMembers.DeleteAt = 0 ORDER BY Users.Username ASC LIMIT :Limit OFFSET :Offset", map[string]interface{}{"TeamId": teamId, "Offset": offset, "Limit": limit}); err != nil {
|
||||||
result.Err = model.NewLocAppError("SqlUserStore.GetProfiles", "store.sql_user.get_profiles.app_error", nil, err.Error())
|
result.Err = model.NewLocAppError("SqlUserStore.GetProfiles", "store.sql_user.get_profiles.app_error", nil, err.Error())
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
@@ -541,45 +550,36 @@ func (us SqlUserStore) GetProfiles(teamId string) StoreChannel {
|
|||||||
return storeChannel
|
return storeChannel
|
||||||
}
|
}
|
||||||
|
|
||||||
func (us SqlUserStore) GetDirectProfiles(userId string) StoreChannel {
|
func (us SqlUserStore) InvalidateProfilesInChannelCache(channelId string) {
|
||||||
|
profilesInChannelCache.Remove(channelId)
|
||||||
|
}
|
||||||
|
|
||||||
storeChannel := make(StoreChannel, 1)
|
func (us SqlUserStore) GetProfilesInChannel(channelId string, offset int, limit int, allowFromCache bool) StoreChannel {
|
||||||
|
|
||||||
|
storeChannel := make(StoreChannel)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
result := StoreResult{}
|
result := StoreResult{}
|
||||||
|
|
||||||
|
if allowFromCache && offset == -1 && limit == -1 {
|
||||||
|
if cacheItem, ok := profilesInChannelCache.Get(channelId); ok {
|
||||||
|
result.Data = cacheItem.(map[string]*model.User)
|
||||||
|
storeChannel <- result
|
||||||
|
close(storeChannel)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var users []*model.User
|
var users []*model.User
|
||||||
|
|
||||||
if _, err := us.GetReplica().Select(&users, `
|
query := "SELECT Users.* FROM Users, ChannelMembers WHERE ChannelMembers.ChannelId = :ChannelId AND Users.Id = ChannelMembers.UserId AND Users.DeleteAt = 0"
|
||||||
SELECT
|
|
||||||
Users.*
|
if limit >= 0 && offset >= 0 {
|
||||||
FROM
|
query += " ORDER BY Users.Username ASC LIMIT :Limit OFFSET :Offset"
|
||||||
Users
|
}
|
||||||
WHERE
|
|
||||||
Id IN (SELECT DISTINCT
|
if _, err := us.GetReplica().Select(&users, query, map[string]interface{}{"ChannelId": channelId, "Offset": offset, "Limit": limit}); err != nil {
|
||||||
UserId
|
result.Err = model.NewLocAppError("SqlUserStore.GetProfilesInChannel", "store.sql_user.get_profiles.app_error", nil, err.Error())
|
||||||
FROM
|
|
||||||
ChannelMembers
|
|
||||||
WHERE
|
|
||||||
ChannelMembers.UserId != :UserId
|
|
||||||
AND ChannelMembers.ChannelId IN (SELECT
|
|
||||||
Channels.Id
|
|
||||||
FROM
|
|
||||||
Channels,
|
|
||||||
ChannelMembers
|
|
||||||
WHERE
|
|
||||||
Channels.Type = 'D'
|
|
||||||
AND Channels.Id = ChannelMembers.ChannelId
|
|
||||||
AND ChannelMembers.UserId = :UserId))
|
|
||||||
OR Id IN (SELECT
|
|
||||||
Name
|
|
||||||
FROM
|
|
||||||
Preferences
|
|
||||||
WHERE
|
|
||||||
UserId = :UserId
|
|
||||||
AND Category = 'direct_channel_show')
|
|
||||||
`, map[string]interface{}{"UserId": userId}); err != nil {
|
|
||||||
result.Err = model.NewLocAppError("SqlUserStore.GetDirectProfiles", "store.sql_user.get_profiles.app_error", nil, err.Error())
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
userMap := make(map[string]*model.User)
|
userMap := make(map[string]*model.User)
|
||||||
@@ -592,6 +592,148 @@ func (us SqlUserStore) GetDirectProfiles(userId string) StoreChannel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
result.Data = userMap
|
result.Data = userMap
|
||||||
|
|
||||||
|
if allowFromCache && offset == -1 && limit == -1 {
|
||||||
|
profilesInChannelCache.AddWithExpiresInSecs(channelId, userMap, PROFILES_IN_CHANNEL_CACHE_SEC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
storeChannel <- result
|
||||||
|
close(storeChannel)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return storeChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (us SqlUserStore) GetProfilesNotInChannel(teamId string, channelId string, offset int, limit int) StoreChannel {
|
||||||
|
|
||||||
|
storeChannel := make(StoreChannel)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
result := StoreResult{}
|
||||||
|
|
||||||
|
var users []*model.User
|
||||||
|
|
||||||
|
if _, err := us.GetReplica().Select(&users, `
|
||||||
|
SELECT
|
||||||
|
u.*
|
||||||
|
FROM Users u
|
||||||
|
INNER JOIN TeamMembers tm
|
||||||
|
ON tm.UserId = u.Id
|
||||||
|
AND tm.TeamId = :TeamId
|
||||||
|
LEFT JOIN ChannelMembers cm
|
||||||
|
ON cm.UserId = u.Id
|
||||||
|
AND cm.ChannelId = :ChannelId
|
||||||
|
WHERE cm.UserId IS NULL
|
||||||
|
ORDER BY u.Username ASC
|
||||||
|
LIMIT :Limit OFFSET :Offset
|
||||||
|
`, map[string]interface{}{"TeamId": teamId, "ChannelId": channelId, "Offset": offset, "Limit": limit}); err != nil {
|
||||||
|
result.Err = model.NewLocAppError("SqlUserStore.GetProfilesNotInChannel", "store.sql_user.get_profiles.app_error", nil, err.Error())
|
||||||
|
} else {
|
||||||
|
|
||||||
|
userMap := make(map[string]*model.User)
|
||||||
|
|
||||||
|
for _, u := range users {
|
||||||
|
u.Password = ""
|
||||||
|
u.AuthData = new(string)
|
||||||
|
*u.AuthData = ""
|
||||||
|
userMap[u.Id] = u
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Data = userMap
|
||||||
|
}
|
||||||
|
|
||||||
|
storeChannel <- result
|
||||||
|
close(storeChannel)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return storeChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (us SqlUserStore) GetProfilesByUsernames(usernames []string, teamId string) StoreChannel {
|
||||||
|
storeChannel := make(StoreChannel)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
result := StoreResult{}
|
||||||
|
|
||||||
|
var users []*model.User
|
||||||
|
props := make(map[string]interface{})
|
||||||
|
idQuery := ""
|
||||||
|
|
||||||
|
for index, usernames := range usernames {
|
||||||
|
if len(idQuery) > 0 {
|
||||||
|
idQuery += ", "
|
||||||
|
}
|
||||||
|
|
||||||
|
props["username"+strconv.Itoa(index)] = usernames
|
||||||
|
idQuery += ":username" + strconv.Itoa(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
props["TeamId"] = teamId
|
||||||
|
|
||||||
|
if _, err := us.GetReplica().Select(&users, `SELECT Users.* FROM Users INNER JOIN TeamMembers ON
|
||||||
|
Users.Id = TeamMembers.UserId AND Users.Username IN (`+idQuery+`) AND TeamMembers.TeamId = :TeamId `, props); err != nil {
|
||||||
|
result.Err = model.NewLocAppError("SqlUserStore.GetProfilesByUsernames", "store.sql_user.get_profiles.app_error", nil, err.Error())
|
||||||
|
} else {
|
||||||
|
userMap := make(map[string]*model.User)
|
||||||
|
|
||||||
|
for _, u := range users {
|
||||||
|
u.Password = ""
|
||||||
|
u.AuthData = new(string)
|
||||||
|
*u.AuthData = ""
|
||||||
|
userMap[u.Id] = u
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Data = userMap
|
||||||
|
}
|
||||||
|
|
||||||
|
storeChannel <- result
|
||||||
|
close(storeChannel)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return storeChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserWithLastActivityAt struct {
|
||||||
|
model.User
|
||||||
|
LastActivityAt int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (us SqlUserStore) GetRecentlyActiveUsersForTeam(teamId string) StoreChannel {
|
||||||
|
|
||||||
|
storeChannel := make(StoreChannel)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
result := StoreResult{}
|
||||||
|
|
||||||
|
var users []*UserWithLastActivityAt
|
||||||
|
|
||||||
|
if _, err := us.GetReplica().Select(&users, `
|
||||||
|
SELECT
|
||||||
|
u.*,
|
||||||
|
s.LastActivityAt
|
||||||
|
FROM Users AS u
|
||||||
|
INNER JOIN TeamMembers AS t ON u.Id = t.UserId
|
||||||
|
INNER JOIN Status AS s ON s.UserId = t.UserId
|
||||||
|
WHERE t.TeamId = :TeamId
|
||||||
|
ORDER BY s.LastActivityAt DESC
|
||||||
|
LIMIT 100
|
||||||
|
`, map[string]interface{}{"TeamId": teamId}); err != nil {
|
||||||
|
result.Err = model.NewLocAppError("SqlUserStore.GetRecentlyActiveUsers", "store.sql_user.get_recently_active_users.app_error", nil, err.Error())
|
||||||
|
} else {
|
||||||
|
|
||||||
|
userMap := make(map[string]*model.User)
|
||||||
|
|
||||||
|
for _, userWithLastActivityAt := range users {
|
||||||
|
u := userWithLastActivityAt.User
|
||||||
|
u.Password = ""
|
||||||
|
u.AuthData = new(string)
|
||||||
|
*u.AuthData = ""
|
||||||
|
u.LastActivityAt = userWithLastActivityAt.LastActivityAt
|
||||||
|
userMap[u.Id] = &u
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Data = userMap
|
||||||
}
|
}
|
||||||
|
|
||||||
storeChannel <- result
|
storeChannel <- result
|
||||||
@@ -938,3 +1080,144 @@ func (us SqlUserStore) GetUnreadCountForChannel(userId string, channelId string)
|
|||||||
|
|
||||||
return storeChannel
|
return storeChannel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (us SqlUserStore) Search(teamId string, term string, searchType string) StoreChannel {
|
||||||
|
storeChannel := make(StoreChannel, 1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
searchQuery := ""
|
||||||
|
if teamId == "" {
|
||||||
|
searchQuery = `
|
||||||
|
SELECT
|
||||||
|
*
|
||||||
|
FROM
|
||||||
|
Users
|
||||||
|
WHERE
|
||||||
|
DeleteAt = 0
|
||||||
|
SEARCH_CLAUSE
|
||||||
|
ORDER BY Username ASC
|
||||||
|
LIMIT 50`
|
||||||
|
} else {
|
||||||
|
searchQuery = `
|
||||||
|
SELECT
|
||||||
|
Users.*
|
||||||
|
FROM
|
||||||
|
Users, TeamMembers
|
||||||
|
WHERE
|
||||||
|
TeamMembers.TeamId = :TeamId
|
||||||
|
AND Users.Id = TeamMembers.UserId
|
||||||
|
AND Users.DeleteAt = 0
|
||||||
|
AND TeamMembers.DeleteAt = 0
|
||||||
|
SEARCH_CLAUSE
|
||||||
|
ORDER BY Users.Username ASC
|
||||||
|
LIMIT 100`
|
||||||
|
}
|
||||||
|
|
||||||
|
storeChannel <- us.performSearch(searchQuery, term, searchType, map[string]interface{}{"TeamId": teamId})
|
||||||
|
close(storeChannel)
|
||||||
|
|
||||||
|
}()
|
||||||
|
|
||||||
|
return storeChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (us SqlUserStore) SearchNotInChannel(teamId string, channelId string, term string, searchType string) StoreChannel {
|
||||||
|
storeChannel := make(StoreChannel, 1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
searchQuery := ""
|
||||||
|
if teamId == "" {
|
||||||
|
searchQuery = `
|
||||||
|
SELECT
|
||||||
|
u.*
|
||||||
|
FROM Users u
|
||||||
|
LEFT JOIN ChannelMembers cm
|
||||||
|
ON cm.UserId = u.Id
|
||||||
|
AND cm.ChannelId = :ChannelId
|
||||||
|
WHERE cm.UserId IS NULL
|
||||||
|
SEARCH_CLAUSE
|
||||||
|
ORDER BY u.Username ASC
|
||||||
|
LIMIT 100`
|
||||||
|
} else {
|
||||||
|
searchQuery = `
|
||||||
|
SELECT
|
||||||
|
u.*
|
||||||
|
FROM Users u
|
||||||
|
INNER JOIN TeamMembers tm
|
||||||
|
ON tm.UserId = u.Id
|
||||||
|
AND tm.TeamId = :TeamId
|
||||||
|
LEFT JOIN ChannelMembers cm
|
||||||
|
ON cm.UserId = u.Id
|
||||||
|
AND cm.ChannelId = :ChannelId
|
||||||
|
WHERE cm.UserId IS NULL
|
||||||
|
SEARCH_CLAUSE
|
||||||
|
ORDER BY u.Username ASC
|
||||||
|
LIMIT 100`
|
||||||
|
}
|
||||||
|
|
||||||
|
storeChannel <- us.performSearch(searchQuery, term, searchType, map[string]interface{}{"TeamId": teamId, "ChannelId": channelId})
|
||||||
|
close(storeChannel)
|
||||||
|
|
||||||
|
}()
|
||||||
|
|
||||||
|
return storeChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (us SqlUserStore) SearchInChannel(channelId string, term string, searchType string) StoreChannel {
|
||||||
|
storeChannel := make(StoreChannel, 1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
searchQuery := `
|
||||||
|
SELECT
|
||||||
|
Users.*
|
||||||
|
FROM
|
||||||
|
Users, ChannelMembers
|
||||||
|
WHERE
|
||||||
|
ChannelMembers.ChannelId = :ChannelId
|
||||||
|
AND ChannelMembers.UserId = Users.Id
|
||||||
|
AND Users.DeleteAt = 0
|
||||||
|
SEARCH_CLAUSE
|
||||||
|
ORDER BY Users.Username ASC
|
||||||
|
LIMIT 100`
|
||||||
|
|
||||||
|
storeChannel <- us.performSearch(searchQuery, term, searchType, map[string]interface{}{"ChannelId": channelId})
|
||||||
|
close(storeChannel)
|
||||||
|
|
||||||
|
}()
|
||||||
|
|
||||||
|
return storeChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (us SqlUserStore) performSearch(searchQuery string, term string, searchType string, parameters map[string]interface{}) StoreResult {
|
||||||
|
result := StoreResult{}
|
||||||
|
|
||||||
|
if term == "" {
|
||||||
|
searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", "", 1)
|
||||||
|
} else if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
|
||||||
|
term = term + ":*"
|
||||||
|
searchClause := fmt.Sprintf("AND (%s) @@ to_tsquery(:Term)", searchType)
|
||||||
|
searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", searchClause, 1)
|
||||||
|
} else if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL {
|
||||||
|
term = term + "*"
|
||||||
|
searchClause := fmt.Sprintf("AND MATCH(%s) AGAINST (:Term IN BOOLEAN MODE)", searchType)
|
||||||
|
searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", searchClause, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var users []*model.User
|
||||||
|
|
||||||
|
parameters["Term"] = term
|
||||||
|
|
||||||
|
if _, err := us.GetReplica().Select(&users, searchQuery, parameters); err != nil {
|
||||||
|
result.Err = model.NewLocAppError("SqlUserStore.Search", "store.sql_user.search.app_error", nil, "term="+term+", "+"search_type="+searchType+", "+err.Error())
|
||||||
|
} else {
|
||||||
|
for _, u := range users {
|
||||||
|
u.Password = ""
|
||||||
|
u.AuthData = new(string)
|
||||||
|
*u.AuthData = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Data = users
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ func TestUserStoreGetAllProfiles(t *testing.T) {
|
|||||||
Must(store.User().Save(u2))
|
Must(store.User().Save(u2))
|
||||||
Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u2.Id}))
|
Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u2.Id}))
|
||||||
|
|
||||||
if r1 := <-store.User().GetAllProfiles(); r1.Err != nil {
|
if r1 := <-store.User().GetAllProfiles(0, 100); r1.Err != nil {
|
||||||
t.Fatal(r1.Err)
|
t.Fatal(r1.Err)
|
||||||
} else {
|
} else {
|
||||||
users := r1.Data.(map[string]*model.User)
|
users := r1.Data.(map[string]*model.User)
|
||||||
@@ -213,6 +213,15 @@ func TestUserStoreGetAllProfiles(t *testing.T) {
|
|||||||
t.Fatal("invalid returned users")
|
t.Fatal("invalid returned users")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if r2 := <-store.User().GetAllProfiles(0, 1); r2.Err != nil {
|
||||||
|
t.Fatal(r2.Err)
|
||||||
|
} else {
|
||||||
|
users := r2.Data.(map[string]*model.User)
|
||||||
|
if len(users) != 1 {
|
||||||
|
t.Fatal("invalid returned users, limit did not work")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUserStoreGetProfiles(t *testing.T) {
|
func TestUserStoreGetProfiles(t *testing.T) {
|
||||||
@@ -230,7 +239,7 @@ func TestUserStoreGetProfiles(t *testing.T) {
|
|||||||
Must(store.User().Save(u2))
|
Must(store.User().Save(u2))
|
||||||
Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u2.Id}))
|
Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u2.Id}))
|
||||||
|
|
||||||
if r1 := <-store.User().GetProfiles(teamId); r1.Err != nil {
|
if r1 := <-store.User().GetProfiles(teamId, 0, 100); r1.Err != nil {
|
||||||
t.Fatal(r1.Err)
|
t.Fatal(r1.Err)
|
||||||
} else {
|
} else {
|
||||||
users := r1.Data.(map[string]*model.User)
|
users := r1.Data.(map[string]*model.User)
|
||||||
@@ -243,7 +252,7 @@ func TestUserStoreGetProfiles(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if r2 := <-store.User().GetProfiles("123"); r2.Err != nil {
|
if r2 := <-store.User().GetProfiles("123", 0, 100); r2.Err != nil {
|
||||||
t.Fatal(r2.Err)
|
t.Fatal(r2.Err)
|
||||||
} else {
|
} else {
|
||||||
if len(r2.Data.(map[string]*model.User)) != 0 {
|
if len(r2.Data.(map[string]*model.User)) != 0 {
|
||||||
@@ -252,7 +261,7 @@ func TestUserStoreGetProfiles(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUserStoreGetDirectProfiles(t *testing.T) {
|
func TestUserStoreGetProfilesInChannel(t *testing.T) {
|
||||||
Setup()
|
Setup()
|
||||||
|
|
||||||
teamId := model.NewId()
|
teamId := model.NewId()
|
||||||
@@ -267,7 +276,151 @@ func TestUserStoreGetDirectProfiles(t *testing.T) {
|
|||||||
Must(store.User().Save(u2))
|
Must(store.User().Save(u2))
|
||||||
Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u2.Id}))
|
Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u2.Id}))
|
||||||
|
|
||||||
if r1 := <-store.User().GetDirectProfiles(u1.Id); r1.Err != nil {
|
c1 := model.Channel{}
|
||||||
|
c1.TeamId = teamId
|
||||||
|
c1.DisplayName = "Profiles in channel"
|
||||||
|
c1.Name = "profiles-" + model.NewId()
|
||||||
|
c1.Type = model.CHANNEL_OPEN
|
||||||
|
|
||||||
|
c2 := model.Channel{}
|
||||||
|
c2.TeamId = teamId
|
||||||
|
c2.DisplayName = "Profiles in private"
|
||||||
|
c2.Name = "profiles-" + model.NewId()
|
||||||
|
c2.Type = model.CHANNEL_PRIVATE
|
||||||
|
|
||||||
|
Must(store.Channel().Save(&c1))
|
||||||
|
Must(store.Channel().Save(&c2))
|
||||||
|
|
||||||
|
m1 := model.ChannelMember{}
|
||||||
|
m1.ChannelId = c1.Id
|
||||||
|
m1.UserId = u1.Id
|
||||||
|
m1.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||||
|
|
||||||
|
m2 := model.ChannelMember{}
|
||||||
|
m2.ChannelId = c1.Id
|
||||||
|
m2.UserId = u2.Id
|
||||||
|
m2.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||||
|
|
||||||
|
m3 := model.ChannelMember{}
|
||||||
|
m3.ChannelId = c2.Id
|
||||||
|
m3.UserId = u1.Id
|
||||||
|
m3.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||||
|
|
||||||
|
Must(store.Channel().SaveMember(&m1))
|
||||||
|
Must(store.Channel().SaveMember(&m2))
|
||||||
|
Must(store.Channel().SaveMember(&m3))
|
||||||
|
|
||||||
|
if r1 := <-store.User().GetProfilesInChannel(c1.Id, -1, -1, false); r1.Err != nil {
|
||||||
|
t.Fatal(r1.Err)
|
||||||
|
} else {
|
||||||
|
users := r1.Data.(map[string]*model.User)
|
||||||
|
if len(users) != 2 {
|
||||||
|
t.Fatal("invalid returned users")
|
||||||
|
}
|
||||||
|
|
||||||
|
if users[u1.Id].Id != u1.Id {
|
||||||
|
t.Fatal("invalid returned user")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r2 := <-store.User().GetProfilesInChannel(c2.Id, -1, -1, false); r2.Err != nil {
|
||||||
|
t.Fatal(r2.Err)
|
||||||
|
} else {
|
||||||
|
if len(r2.Data.(map[string]*model.User)) != 1 {
|
||||||
|
t.Fatal("should have returned empty map")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r2 := <-store.User().GetProfilesInChannel(c2.Id, -1, -1, true); r2.Err != nil {
|
||||||
|
t.Fatal(r2.Err)
|
||||||
|
} else {
|
||||||
|
if len(r2.Data.(map[string]*model.User)) != 1 {
|
||||||
|
t.Fatal("should have returned empty map")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r2 := <-store.User().GetProfilesInChannel(c2.Id, -1, -1, true); r2.Err != nil {
|
||||||
|
t.Fatal(r2.Err)
|
||||||
|
} else {
|
||||||
|
if len(r2.Data.(map[string]*model.User)) != 1 {
|
||||||
|
t.Fatal("should have returned empty map")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
store.User().InvalidateProfilesInChannelCache(c2.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserStoreGetProfilesNotInChannel(t *testing.T) {
|
||||||
|
Setup()
|
||||||
|
|
||||||
|
teamId := model.NewId()
|
||||||
|
|
||||||
|
u1 := &model.User{}
|
||||||
|
u1.Email = model.NewId()
|
||||||
|
Must(store.User().Save(u1))
|
||||||
|
Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}))
|
||||||
|
|
||||||
|
u2 := &model.User{}
|
||||||
|
u2.Email = model.NewId()
|
||||||
|
Must(store.User().Save(u2))
|
||||||
|
Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u2.Id}))
|
||||||
|
|
||||||
|
c1 := model.Channel{}
|
||||||
|
c1.TeamId = teamId
|
||||||
|
c1.DisplayName = "Profiles in channel"
|
||||||
|
c1.Name = "profiles-" + model.NewId()
|
||||||
|
c1.Type = model.CHANNEL_OPEN
|
||||||
|
|
||||||
|
c2 := model.Channel{}
|
||||||
|
c2.TeamId = teamId
|
||||||
|
c2.DisplayName = "Profiles in private"
|
||||||
|
c2.Name = "profiles-" + model.NewId()
|
||||||
|
c2.Type = model.CHANNEL_PRIVATE
|
||||||
|
|
||||||
|
Must(store.Channel().Save(&c1))
|
||||||
|
Must(store.Channel().Save(&c2))
|
||||||
|
|
||||||
|
if r1 := <-store.User().GetProfilesNotInChannel(teamId, c1.Id, 0, 100); r1.Err != nil {
|
||||||
|
t.Fatal(r1.Err)
|
||||||
|
} else {
|
||||||
|
users := r1.Data.(map[string]*model.User)
|
||||||
|
if len(users) != 2 {
|
||||||
|
t.Fatal("invalid returned users")
|
||||||
|
}
|
||||||
|
|
||||||
|
if users[u1.Id].Id != u1.Id {
|
||||||
|
t.Fatal("invalid returned user")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r2 := <-store.User().GetProfilesNotInChannel(teamId, c2.Id, 0, 100); r2.Err != nil {
|
||||||
|
t.Fatal(r2.Err)
|
||||||
|
} else {
|
||||||
|
if len(r2.Data.(map[string]*model.User)) != 2 {
|
||||||
|
t.Fatal("invalid returned users")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m1 := model.ChannelMember{}
|
||||||
|
m1.ChannelId = c1.Id
|
||||||
|
m1.UserId = u1.Id
|
||||||
|
m1.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||||
|
|
||||||
|
m2 := model.ChannelMember{}
|
||||||
|
m2.ChannelId = c1.Id
|
||||||
|
m2.UserId = u2.Id
|
||||||
|
m2.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||||
|
|
||||||
|
m3 := model.ChannelMember{}
|
||||||
|
m3.ChannelId = c2.Id
|
||||||
|
m3.UserId = u1.Id
|
||||||
|
m3.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||||
|
|
||||||
|
Must(store.Channel().SaveMember(&m1))
|
||||||
|
Must(store.Channel().SaveMember(&m2))
|
||||||
|
Must(store.Channel().SaveMember(&m3))
|
||||||
|
|
||||||
|
if r1 := <-store.User().GetProfilesNotInChannel(teamId, c1.Id, 0, 100); r1.Err != nil {
|
||||||
t.Fatal(r1.Err)
|
t.Fatal(r1.Err)
|
||||||
} else {
|
} else {
|
||||||
users := r1.Data.(map[string]*model.User)
|
users := r1.Data.(map[string]*model.User)
|
||||||
@@ -276,11 +429,11 @@ func TestUserStoreGetDirectProfiles(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if r2 := <-store.User().GetDirectProfiles("123"); r2.Err != nil {
|
if r2 := <-store.User().GetProfilesNotInChannel(teamId, c2.Id, 0, 100); r2.Err != nil {
|
||||||
t.Fatal(r2.Err)
|
t.Fatal(r2.Err)
|
||||||
} else {
|
} else {
|
||||||
if len(r2.Data.(map[string]*model.User)) != 0 {
|
if len(r2.Data.(map[string]*model.User)) != 1 {
|
||||||
t.Fatal("should have returned empty map")
|
t.Fatal("should have had 1 user not in channel")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -326,7 +479,7 @@ func TestUserStoreGetProfilesByIds(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if r2 := <-store.User().GetProfiles("123"); r2.Err != nil {
|
if r2 := <-store.User().GetProfiles("123", 0, 100); r2.Err != nil {
|
||||||
t.Fatal(r2.Err)
|
t.Fatal(r2.Err)
|
||||||
} else {
|
} else {
|
||||||
if len(r2.Data.(map[string]*model.User)) != 0 {
|
if len(r2.Data.(map[string]*model.User)) != 0 {
|
||||||
@@ -335,6 +488,50 @@ func TestUserStoreGetProfilesByIds(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUserStoreGetProfilesByUsernames(t *testing.T) {
|
||||||
|
Setup()
|
||||||
|
|
||||||
|
teamId := model.NewId()
|
||||||
|
|
||||||
|
u1 := &model.User{}
|
||||||
|
u1.Email = model.NewId()
|
||||||
|
u1.Username = "username1" + model.NewId()
|
||||||
|
Must(store.User().Save(u1))
|
||||||
|
Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u1.Id}))
|
||||||
|
|
||||||
|
u2 := &model.User{}
|
||||||
|
u2.Email = model.NewId()
|
||||||
|
u2.Username = "username2" + model.NewId()
|
||||||
|
Must(store.User().Save(u2))
|
||||||
|
Must(store.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u2.Id}))
|
||||||
|
|
||||||
|
if r1 := <-store.User().GetProfilesByUsernames([]string{u1.Username, u2.Username}, teamId); r1.Err != nil {
|
||||||
|
t.Fatal(r1.Err)
|
||||||
|
} else {
|
||||||
|
users := r1.Data.(map[string]*model.User)
|
||||||
|
if len(users) != 2 {
|
||||||
|
t.Fatal("invalid returned users")
|
||||||
|
}
|
||||||
|
|
||||||
|
if users[u1.Id].Id != u1.Id {
|
||||||
|
t.Fatal("invalid returned user")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r1 := <-store.User().GetProfilesByUsernames([]string{u1.Username}, teamId); r1.Err != nil {
|
||||||
|
t.Fatal(r1.Err)
|
||||||
|
} else {
|
||||||
|
users := r1.Data.(map[string]*model.User)
|
||||||
|
if len(users) != 1 {
|
||||||
|
t.Fatal("invalid returned users")
|
||||||
|
}
|
||||||
|
|
||||||
|
if users[u1.Id].Id != u1.Id {
|
||||||
|
t.Fatal("invalid returned user")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestUserStoreGetSystemAdminProfiles(t *testing.T) {
|
func TestUserStoreGetSystemAdminProfiles(t *testing.T) {
|
||||||
Setup()
|
Setup()
|
||||||
|
|
||||||
@@ -713,3 +910,216 @@ func TestUserStoreUpdateMfaActive(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUserStoreGetRecentlyActiveUsersForTeam(t *testing.T) {
|
||||||
|
Setup()
|
||||||
|
|
||||||
|
u1 := &model.User{}
|
||||||
|
u1.Email = model.NewId()
|
||||||
|
Must(store.User().Save(u1))
|
||||||
|
Must(store.Status().SaveOrUpdate(&model.Status{u1.Id, model.STATUS_ONLINE, false, model.GetMillis(), ""}))
|
||||||
|
tid := model.NewId()
|
||||||
|
Must(store.Team().SaveMember(&model.TeamMember{TeamId: tid, UserId: u1.Id}))
|
||||||
|
|
||||||
|
if r1 := <-store.User().GetRecentlyActiveUsersForTeam(tid); r1.Err != nil {
|
||||||
|
t.Fatal(r1.Err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserStoreSearch(t *testing.T) {
|
||||||
|
Setup()
|
||||||
|
|
||||||
|
u1 := &model.User{}
|
||||||
|
u1.Username = "jimbo" + model.NewId()
|
||||||
|
u1.FirstName = "Tim"
|
||||||
|
u1.LastName = "Bill"
|
||||||
|
u1.Nickname = "Rob"
|
||||||
|
u1.Email = model.NewId()
|
||||||
|
Must(store.User().Save(u1))
|
||||||
|
|
||||||
|
tid := model.NewId()
|
||||||
|
Must(store.Team().SaveMember(&model.TeamMember{TeamId: tid, UserId: u1.Id}))
|
||||||
|
|
||||||
|
if r1 := <-store.User().Search(tid, "jimb", USER_SEARCH_TYPE_USERNAME); r1.Err != nil {
|
||||||
|
t.Fatal(r1.Err)
|
||||||
|
} else {
|
||||||
|
profiles := r1.Data.([]*model.User)
|
||||||
|
found := false
|
||||||
|
for _, profile := range profiles {
|
||||||
|
if profile.Id == u1.Id {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
t.Fatal("should have found user")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r1 := <-store.User().Search("", "jimb", USER_SEARCH_TYPE_USERNAME); r1.Err != nil {
|
||||||
|
t.Fatal(r1.Err)
|
||||||
|
} else {
|
||||||
|
profiles := r1.Data.([]*model.User)
|
||||||
|
found := false
|
||||||
|
for _, profile := range profiles {
|
||||||
|
if profile.Id == u1.Id {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
t.Fatal("should have found user")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r1 := <-store.User().Search(tid, "", USER_SEARCH_TYPE_USERNAME); r1.Err != nil {
|
||||||
|
t.Fatal(r1.Err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c1 := model.Channel{}
|
||||||
|
c1.TeamId = tid
|
||||||
|
c1.DisplayName = "NameName"
|
||||||
|
c1.Name = "a" + model.NewId() + "b"
|
||||||
|
c1.Type = model.CHANNEL_OPEN
|
||||||
|
c1 = *Must(store.Channel().Save(&c1)).(*model.Channel)
|
||||||
|
|
||||||
|
if r1 := <-store.User().SearchNotInChannel(tid, c1.Id, "jimb", USER_SEARCH_TYPE_USERNAME); r1.Err != nil {
|
||||||
|
t.Fatal(r1.Err)
|
||||||
|
} else {
|
||||||
|
profiles := r1.Data.([]*model.User)
|
||||||
|
found := false
|
||||||
|
for _, profile := range profiles {
|
||||||
|
if profile.Id == u1.Id {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
t.Fatal("should have found user")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r1 := <-store.User().SearchNotInChannel("", c1.Id, "jimb", USER_SEARCH_TYPE_USERNAME); r1.Err != nil {
|
||||||
|
t.Fatal(r1.Err)
|
||||||
|
} else {
|
||||||
|
profiles := r1.Data.([]*model.User)
|
||||||
|
found := false
|
||||||
|
for _, profile := range profiles {
|
||||||
|
if profile.Id == u1.Id {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
t.Fatal("should have found user")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r1 := <-store.User().SearchNotInChannel("junk", c1.Id, "jimb", USER_SEARCH_TYPE_USERNAME); r1.Err != nil {
|
||||||
|
t.Fatal(r1.Err)
|
||||||
|
} else {
|
||||||
|
profiles := r1.Data.([]*model.User)
|
||||||
|
found := false
|
||||||
|
for _, profile := range profiles {
|
||||||
|
if profile.Id == u1.Id {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if found {
|
||||||
|
t.Fatal("should not have found user")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r1 := <-store.User().SearchInChannel(c1.Id, "jimb", USER_SEARCH_TYPE_USERNAME); r1.Err != nil {
|
||||||
|
t.Fatal(r1.Err)
|
||||||
|
} else {
|
||||||
|
profiles := r1.Data.([]*model.User)
|
||||||
|
found := false
|
||||||
|
for _, profile := range profiles {
|
||||||
|
if profile.Id == u1.Id {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if found {
|
||||||
|
t.Fatal("should not have found user")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Must(store.Channel().SaveMember(&model.ChannelMember{ChannelId: c1.Id, UserId: u1.Id, NotifyProps: model.GetDefaultChannelNotifyProps()}))
|
||||||
|
|
||||||
|
if r1 := <-store.User().SearchInChannel(c1.Id, "jimb", USER_SEARCH_TYPE_USERNAME); r1.Err != nil {
|
||||||
|
t.Fatal(r1.Err)
|
||||||
|
} else {
|
||||||
|
profiles := r1.Data.([]*model.User)
|
||||||
|
found := false
|
||||||
|
for _, profile := range profiles {
|
||||||
|
if profile.Id == u1.Id {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
t.Fatal("should have found user")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r1 := <-store.User().Search(tid, "Tim", USER_SEARCH_TYPE_ALL); r1.Err != nil {
|
||||||
|
t.Fatal(r1.Err)
|
||||||
|
} else {
|
||||||
|
profiles := r1.Data.([]*model.User)
|
||||||
|
found := false
|
||||||
|
for _, profile := range profiles {
|
||||||
|
if profile.Id == u1.Id {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
t.Fatal("should have found user")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r1 := <-store.User().Search(tid, "Bill", USER_SEARCH_TYPE_ALL); r1.Err != nil {
|
||||||
|
t.Fatal(r1.Err)
|
||||||
|
} else {
|
||||||
|
profiles := r1.Data.([]*model.User)
|
||||||
|
found := false
|
||||||
|
for _, profile := range profiles {
|
||||||
|
if profile.Id == u1.Id {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
t.Fatal("should have found user")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r1 := <-store.User().Search(tid, "Rob", USER_SEARCH_TYPE_ALL); r1.Err != nil {
|
||||||
|
t.Fatal(r1.Err)
|
||||||
|
} else {
|
||||||
|
profiles := r1.Data.([]*model.User)
|
||||||
|
found := false
|
||||||
|
for _, profile := range profiles {
|
||||||
|
if profile.Id == u1.Id {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
t.Fatal("should have found user")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ type Store interface {
|
|||||||
MarkSystemRanUnitTests()
|
MarkSystemRanUnitTests()
|
||||||
Close()
|
Close()
|
||||||
DropAllTables()
|
DropAllTables()
|
||||||
|
TotalMasterDbConnections() int
|
||||||
|
TotalReadDbConnections() int
|
||||||
}
|
}
|
||||||
|
|
||||||
type TeamStore interface {
|
type TeamStore interface {
|
||||||
@@ -66,7 +68,9 @@ type TeamStore interface {
|
|||||||
SaveMember(member *model.TeamMember) StoreChannel
|
SaveMember(member *model.TeamMember) StoreChannel
|
||||||
UpdateMember(member *model.TeamMember) StoreChannel
|
UpdateMember(member *model.TeamMember) StoreChannel
|
||||||
GetMember(teamId string, userId string) StoreChannel
|
GetMember(teamId string, userId string) StoreChannel
|
||||||
GetMembers(teamId string) StoreChannel
|
GetMembers(teamId string, offset int, limit int) StoreChannel
|
||||||
|
GetMembersByIds(teamId string, userIds []string) StoreChannel
|
||||||
|
GetMemberCount(teamId string) StoreChannel
|
||||||
GetTeamsForUser(userId string) StoreChannel
|
GetTeamsForUser(userId string) StoreChannel
|
||||||
RemoveMember(teamId string, userId string) StoreChannel
|
RemoveMember(teamId string, userId string) StoreChannel
|
||||||
RemoveAllMembersByTeam(teamId string) StoreChannel
|
RemoveAllMembersByTeam(teamId string) StoreChannel
|
||||||
@@ -89,16 +93,17 @@ type ChannelStore interface {
|
|||||||
GetChannelCounts(teamId string, userId string) StoreChannel
|
GetChannelCounts(teamId string, userId string) StoreChannel
|
||||||
GetAll(teamId string) StoreChannel
|
GetAll(teamId string) StoreChannel
|
||||||
GetForPost(postId string) StoreChannel
|
GetForPost(postId string) StoreChannel
|
||||||
|
|
||||||
SaveMember(member *model.ChannelMember) StoreChannel
|
SaveMember(member *model.ChannelMember) StoreChannel
|
||||||
UpdateMember(member *model.ChannelMember) StoreChannel
|
UpdateMember(member *model.ChannelMember) StoreChannel
|
||||||
GetMembers(channelId string) StoreChannel
|
GetMembers(channelId string) StoreChannel
|
||||||
GetMember(channelId string, userId string) StoreChannel
|
GetMember(channelId string, userId string) StoreChannel
|
||||||
|
GetAllChannelMembersForUser(userId string, allowFromCache bool) StoreChannel
|
||||||
|
InvalidateAllChannelMembersForUser(userId string)
|
||||||
|
IsUserInChannelUseCache(userId string, channelId string) bool
|
||||||
GetMemberForPost(postId string, userId string) StoreChannel
|
GetMemberForPost(postId string, userId string) StoreChannel
|
||||||
GetMemberCount(channelId string) StoreChannel
|
GetMemberCount(channelId string) StoreChannel
|
||||||
RemoveMember(channelId string, userId string) StoreChannel
|
RemoveMember(channelId string, userId string) StoreChannel
|
||||||
PermanentDeleteMembersByUser(userId string) StoreChannel
|
PermanentDeleteMembersByUser(userId string) StoreChannel
|
||||||
GetExtraMembers(channelId string, limit int) StoreChannel
|
|
||||||
UpdateLastViewedAt(channelId string, userId string) StoreChannel
|
UpdateLastViewedAt(channelId string, userId string) StoreChannel
|
||||||
SetLastViewedAt(channelId string, userId string, newLastViewedAt int64) StoreChannel
|
SetLastViewedAt(channelId string, userId string, newLastViewedAt int64) StoreChannel
|
||||||
IncrementMentionCount(channelId string, userId string) StoreChannel
|
IncrementMentionCount(channelId string, userId string) StoreChannel
|
||||||
@@ -135,9 +140,12 @@ type UserStore interface {
|
|||||||
UpdateMfaActive(userId string, active bool) StoreChannel
|
UpdateMfaActive(userId string, active bool) StoreChannel
|
||||||
Get(id string) StoreChannel
|
Get(id string) StoreChannel
|
||||||
GetAll() StoreChannel
|
GetAll() StoreChannel
|
||||||
GetAllProfiles() StoreChannel
|
InvalidateProfilesInChannelCache(channelId string)
|
||||||
GetProfiles(teamId string) StoreChannel
|
GetProfilesInChannel(channelId string, offset int, limit int, allowFromCache bool) StoreChannel
|
||||||
GetDirectProfiles(userId string) StoreChannel
|
GetProfilesNotInChannel(teamId string, channelId string, offset int, limit int) StoreChannel
|
||||||
|
GetProfilesByUsernames(usernames []string, teamId string) StoreChannel
|
||||||
|
GetAllProfiles(offset int, limit int) StoreChannel
|
||||||
|
GetProfiles(teamId string, offset int, limit int) StoreChannel
|
||||||
GetProfileByIds(userId []string) StoreChannel
|
GetProfileByIds(userId []string) StoreChannel
|
||||||
GetByEmail(email string) StoreChannel
|
GetByEmail(email string) StoreChannel
|
||||||
GetByAuth(authData *string, authService string) StoreChannel
|
GetByAuth(authData *string, authService string) StoreChannel
|
||||||
@@ -147,7 +155,6 @@ type UserStore interface {
|
|||||||
VerifyEmail(userId string) StoreChannel
|
VerifyEmail(userId string) StoreChannel
|
||||||
GetEtagForAllProfiles() StoreChannel
|
GetEtagForAllProfiles() StoreChannel
|
||||||
GetEtagForProfiles(teamId string) StoreChannel
|
GetEtagForProfiles(teamId string) StoreChannel
|
||||||
GetEtagForDirectProfiles(userId string) StoreChannel
|
|
||||||
UpdateFailedPasswordAttempts(userId string, attempts int) StoreChannel
|
UpdateFailedPasswordAttempts(userId string, attempts int) StoreChannel
|
||||||
GetTotalUsersCount() StoreChannel
|
GetTotalUsersCount() StoreChannel
|
||||||
GetSystemAdminProfiles() StoreChannel
|
GetSystemAdminProfiles() StoreChannel
|
||||||
@@ -155,6 +162,10 @@ type UserStore interface {
|
|||||||
AnalyticsUniqueUserCount(teamId string) StoreChannel
|
AnalyticsUniqueUserCount(teamId string) StoreChannel
|
||||||
GetUnreadCount(userId string) StoreChannel
|
GetUnreadCount(userId string) StoreChannel
|
||||||
GetUnreadCountForChannel(userId string, channelId string) StoreChannel
|
GetUnreadCountForChannel(userId string, channelId string) StoreChannel
|
||||||
|
GetRecentlyActiveUsersForTeam(teamId string) StoreChannel
|
||||||
|
Search(teamId string, term string, searchType string) StoreChannel
|
||||||
|
SearchInChannel(channelId string, term string, searchType string) StoreChannel
|
||||||
|
SearchNotInChannel(teamId string, channelId string, term string, searchType string) StoreChannel
|
||||||
}
|
}
|
||||||
|
|
||||||
type SessionStore interface {
|
type SessionStore interface {
|
||||||
@@ -274,6 +285,7 @@ type EmojiStore interface {
|
|||||||
type StatusStore interface {
|
type StatusStore interface {
|
||||||
SaveOrUpdate(status *model.Status) StoreChannel
|
SaveOrUpdate(status *model.Status) StoreChannel
|
||||||
Get(userId string) StoreChannel
|
Get(userId string) StoreChannel
|
||||||
|
GetByIds(userIds []string) StoreChannel
|
||||||
GetOnlineAway() StoreChannel
|
GetOnlineAway() StoreChannel
|
||||||
GetOnline() StoreChannel
|
GetOnline() StoreChannel
|
||||||
GetAllFromTeam(teamId string) StoreChannel
|
GetAllFromTeam(teamId string) StoreChannel
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ func Setup() {
|
|||||||
utils.TranslationsPreInit()
|
utils.TranslationsPreInit()
|
||||||
utils.LoadConfig("config.json")
|
utils.LoadConfig("config.json")
|
||||||
utils.InitTranslations(utils.Cfg.LocalizationSettings)
|
utils.InitTranslations(utils.Cfg.LocalizationSettings)
|
||||||
api.NewServer()
|
api.NewServer(false)
|
||||||
api.StartServer()
|
api.StartServer()
|
||||||
api.InitApi()
|
api.InitApi()
|
||||||
InitWeb()
|
InitWeb()
|
||||||
@@ -161,7 +161,7 @@ func TestGetAccessToken(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if result, err := ApiClient.DoApiGet("/users/profiles/"+teamId+"?access_token="+token, "", ""); err != nil {
|
if result, err := ApiClient.DoApiGet("/teams/"+teamId+"/users/0/100?access_token="+token, "", ""); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
} else {
|
} else {
|
||||||
userMap := model.UserMapFromJson(result.Body)
|
userMap := model.UserMapFromJson(result.Body)
|
||||||
@@ -170,16 +170,16 @@ func TestGetAccessToken(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := ApiClient.DoApiGet("/users/profiles/"+teamId, "", ""); err == nil {
|
if _, err := ApiClient.DoApiGet("/teams/"+teamId+"/users/0/100", "", ""); err == nil {
|
||||||
t.Fatal("should have failed - no access token provided")
|
t.Fatal("should have failed - no access token provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := ApiClient.DoApiGet("/users/profiles/"+teamId+"?access_token=junk", "", ""); err == nil {
|
if _, err := ApiClient.DoApiGet("/teams/"+teamId+"/users/0/100?access_token=junk", "", ""); err == nil {
|
||||||
t.Fatal("should have failed - bad access token provided")
|
t.Fatal("should have failed - bad access token provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
ApiClient.SetOAuthToken(token)
|
ApiClient.SetOAuthToken(token)
|
||||||
if result, err := ApiClient.DoApiGet("/users/profiles/"+teamId, "", ""); err != nil {
|
if result, err := ApiClient.DoApiGet("/teams/"+teamId+"/users/0/100", "", ""); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
} else {
|
} else {
|
||||||
userMap := model.UserMapFromJson(result.Body)
|
userMap := model.UserMapFromJson(result.Body)
|
||||||
|
|||||||
@@ -1,18 +1,26 @@
|
|||||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
import {browserHistory} from 'react-router/es6';
|
import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
|
||||||
import * as Utils from 'utils/utils.jsx';
|
|
||||||
import TeamStore from 'stores/team_store.jsx';
|
import TeamStore from 'stores/team_store.jsx';
|
||||||
import UserStore from 'stores/user_store.jsx';
|
import UserStore from 'stores/user_store.jsx';
|
||||||
import ChannelStore from 'stores/channel_store.jsx';
|
import ChannelStore from 'stores/channel_store.jsx';
|
||||||
|
import PreferenceStore from 'stores/preference_store.jsx';
|
||||||
|
|
||||||
|
import {loadProfilesAndTeamMembersForDMSidebar} from 'actions/user_actions.jsx';
|
||||||
|
|
||||||
|
import Client from 'client/web_client.jsx';
|
||||||
import * as AsyncClient from 'utils/async_client.jsx';
|
import * as AsyncClient from 'utils/async_client.jsx';
|
||||||
import * as UserAgent from 'utils/user_agent.jsx';
|
import * as UserAgent from 'utils/user_agent.jsx';
|
||||||
import Client from 'client/web_client.jsx';
|
import * as Utils from 'utils/utils.jsx';
|
||||||
|
import {Preferences, ActionTypes} from 'utils/constants.jsx';
|
||||||
|
|
||||||
|
import {browserHistory} from 'react-router/es6';
|
||||||
|
|
||||||
export function goToChannel(channel) {
|
export function goToChannel(channel) {
|
||||||
if (channel.fake) {
|
if (channel.fake) {
|
||||||
Utils.openDirectChannelToUser(
|
openDirectChannelToUser(
|
||||||
UserStore.getProfileByUsername(channel.display_name),
|
UserStore.getProfileByUsername(channel.display_name),
|
||||||
() => {
|
() => {
|
||||||
browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/channels/' + channel.name);
|
browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/channels/' + channel.name);
|
||||||
@@ -53,3 +61,124 @@ export function setChannelAsRead(channelIdParam) {
|
|||||||
ChannelStore.emitLastViewed(Number.MAX_VALUE, false);
|
ChannelStore.emitLastViewed(Number.MAX_VALUE, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function addUserToChannel(channelId, userId, success, error) {
|
||||||
|
Client.addChannelMember(
|
||||||
|
channelId,
|
||||||
|
userId,
|
||||||
|
(data) => {
|
||||||
|
UserStore.removeProfileNotInChannel(channelId, userId);
|
||||||
|
const profile = UserStore.getProfile(userId);
|
||||||
|
if (profile) {
|
||||||
|
UserStore.saveProfileInChannel(channelId, profile);
|
||||||
|
UserStore.emitInChannelChange();
|
||||||
|
}
|
||||||
|
UserStore.emitNotInChannelChange();
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
success(data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
AsyncClient.dispatchError(err, 'addChannelMember');
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeUserFromChannel(channelId, userId, success, error) {
|
||||||
|
Client.removeChannelMember(
|
||||||
|
channelId,
|
||||||
|
userId,
|
||||||
|
(data) => {
|
||||||
|
UserStore.removeProfileInChannel(channelId, userId);
|
||||||
|
const profile = UserStore.getProfile(userId);
|
||||||
|
if (profile) {
|
||||||
|
UserStore.saveProfileNotInChannel(channelId, profile);
|
||||||
|
UserStore.emitNotInChannelChange();
|
||||||
|
}
|
||||||
|
UserStore.emitInChannelChange();
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
success(data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
AsyncClient.dispatchError(err, 'removeChannelMember');
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function openDirectChannelToUser(user, success, error) {
|
||||||
|
const channelName = Utils.getDirectChannelName(UserStore.getCurrentId(), user.id);
|
||||||
|
let channel = ChannelStore.getByName(channelName);
|
||||||
|
|
||||||
|
if (channel) {
|
||||||
|
PreferenceStore.setPreference(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, user.id, 'true');
|
||||||
|
loadProfilesAndTeamMembersForDMSidebar();
|
||||||
|
|
||||||
|
AsyncClient.savePreference(
|
||||||
|
Preferences.CATEGORY_DIRECT_CHANNEL_SHOW,
|
||||||
|
user.id,
|
||||||
|
'true'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
success(channel, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
channel = {
|
||||||
|
name: channelName,
|
||||||
|
last_post_at: 0,
|
||||||
|
total_msg_count: 0,
|
||||||
|
type: 'D',
|
||||||
|
display_name: user.username,
|
||||||
|
teammate_id: user.id,
|
||||||
|
status: UserStore.getStatus(user.id)
|
||||||
|
};
|
||||||
|
|
||||||
|
Client.createDirectChannel(
|
||||||
|
user.id,
|
||||||
|
(data) => {
|
||||||
|
Client.getChannel(
|
||||||
|
data.id,
|
||||||
|
(data2) => {
|
||||||
|
AppDispatcher.handleServerAction({
|
||||||
|
type: ActionTypes.RECEIVED_CHANNEL,
|
||||||
|
channel: data2.channel,
|
||||||
|
member: data2.member
|
||||||
|
});
|
||||||
|
|
||||||
|
PreferenceStore.setPreference(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, user.id, 'true');
|
||||||
|
loadProfilesAndTeamMembersForDMSidebar();
|
||||||
|
|
||||||
|
AsyncClient.savePreference(
|
||||||
|
Preferences.CATEGORY_DIRECT_CHANNEL_SHOW,
|
||||||
|
user.id,
|
||||||
|
'true'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
success(data2.channel, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
browserHistory.push(TeamStore.getCurrentTeamUrl() + '/channels/' + channelName);
|
||||||
|
if (error) {
|
||||||
|
error();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
46
webapp/actions/emoji_actions.jsx
Обычный файл
46
webapp/actions/emoji_actions.jsx
Обычный файл
@@ -0,0 +1,46 @@
|
|||||||
|
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
|
||||||
|
|
||||||
|
import UserStore from 'stores/user_store.jsx';
|
||||||
|
|
||||||
|
import * as AsyncClient from 'utils/async_client.jsx';
|
||||||
|
import Client from 'client/web_client.jsx';
|
||||||
|
|
||||||
|
import {ActionTypes} from 'utils/constants.jsx';
|
||||||
|
|
||||||
|
export function loadEmoji(getProfiles = true) {
|
||||||
|
Client.listEmoji(
|
||||||
|
(data) => {
|
||||||
|
AppDispatcher.handleServerAction({
|
||||||
|
type: ActionTypes.RECEIVED_CUSTOM_EMOJIS,
|
||||||
|
emojis: data
|
||||||
|
});
|
||||||
|
|
||||||
|
if (getProfiles) {
|
||||||
|
loadProfilesForEmoji(data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
AsyncClient.dispatchError(err, 'listEmoji');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadProfilesForEmoji(emojiList) {
|
||||||
|
const profilesToLoad = {};
|
||||||
|
for (let i = 0; i < emojiList.length; i++) {
|
||||||
|
const emoji = emojiList[i];
|
||||||
|
if (!UserStore.hasProfile(emoji.creator_id)) {
|
||||||
|
profilesToLoad[emoji.creator_id] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const list = Object.keys(profilesToLoad);
|
||||||
|
if (list.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncClient.getProfilesByIds(list);
|
||||||
|
}
|
||||||
@@ -12,7 +12,8 @@ import TeamStore from 'stores/team_store.jsx';
|
|||||||
import PreferenceStore from 'stores/preference_store.jsx';
|
import PreferenceStore from 'stores/preference_store.jsx';
|
||||||
import SearchStore from 'stores/search_store.jsx';
|
import SearchStore from 'stores/search_store.jsx';
|
||||||
|
|
||||||
import {handleNewPost} from 'actions/post_actions.jsx';
|
import {handleNewPost, loadPosts, loadPostsBefore, loadPostsAfter} from 'actions/post_actions.jsx';
|
||||||
|
import {loadProfilesAndTeamMembersForDMSidebar} from 'actions/user_actions.jsx';
|
||||||
|
|
||||||
import Constants from 'utils/constants.jsx';
|
import Constants from 'utils/constants.jsx';
|
||||||
const ActionTypes = Constants.ActionTypes;
|
const ActionTypes = Constants.ActionTypes;
|
||||||
@@ -43,9 +44,9 @@ export function emitChannelClickEvent(channel) {
|
|||||||
function switchToChannel(chan) {
|
function switchToChannel(chan) {
|
||||||
AsyncClient.getChannels(true);
|
AsyncClient.getChannels(true);
|
||||||
AsyncClient.getMoreChannels(true);
|
AsyncClient.getMoreChannels(true);
|
||||||
AsyncClient.getChannelExtraInfo(chan.id);
|
AsyncClient.getChannelStats(chan.id);
|
||||||
AsyncClient.updateLastViewedAt(chan.id);
|
AsyncClient.updateLastViewedAt(chan.id);
|
||||||
AsyncClient.getPosts(chan.id);
|
loadPosts(chan.id);
|
||||||
trackPage();
|
trackPage();
|
||||||
|
|
||||||
AppDispatcher.handleViewAction({
|
AppDispatcher.handleViewAction({
|
||||||
@@ -108,7 +109,7 @@ export function emitInitialLoad(callback) {
|
|||||||
|
|
||||||
if (data.team_members) {
|
if (data.team_members) {
|
||||||
AppDispatcher.handleServerAction({
|
AppDispatcher.handleServerAction({
|
||||||
type: ActionTypes.RECEIVED_TEAM_MEMBERS,
|
type: ActionTypes.RECEIVED_MY_TEAM_MEMBERS,
|
||||||
team_members: data.team_members
|
team_members: data.team_members
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -143,9 +144,9 @@ export function doFocusPost(channelId, postId, data) {
|
|||||||
});
|
});
|
||||||
AsyncClient.getChannels(true);
|
AsyncClient.getChannels(true);
|
||||||
AsyncClient.getMoreChannels(true);
|
AsyncClient.getMoreChannels(true);
|
||||||
AsyncClient.getChannelExtraInfo(channelId);
|
AsyncClient.getChannelStats(channelId);
|
||||||
AsyncClient.getPostsBefore(postId, 0, Constants.POST_FOCUS_CONTEXT_RADIUS, true);
|
loadPostsBefore(postId, 0, Constants.POST_FOCUS_CONTEXT_RADIUS, true);
|
||||||
AsyncClient.getPostsAfter(postId, 0, Constants.POST_FOCUS_CONTEXT_RADIUS, true);
|
loadPostsAfter(postId, 0, Constants.POST_FOCUS_CONTEXT_RADIUS, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function emitPostFocusEvent(postId, onSuccess) {
|
export function emitPostFocusEvent(postId, onSuccess) {
|
||||||
@@ -246,14 +247,14 @@ export function emitLoadMorePostsFocusedTopEvent() {
|
|||||||
export function loadMorePostsTop(id, isFocusPost) {
|
export function loadMorePostsTop(id, isFocusPost) {
|
||||||
const earliestPostId = PostStore.getEarliestPost(id).id;
|
const earliestPostId = PostStore.getEarliestPost(id).id;
|
||||||
if (PostStore.requestVisibilityIncrease(id, Constants.POST_CHUNK_SIZE)) {
|
if (PostStore.requestVisibilityIncrease(id, Constants.POST_CHUNK_SIZE)) {
|
||||||
AsyncClient.getPostsBefore(earliestPostId, 0, Constants.POST_CHUNK_SIZE, isFocusPost);
|
loadPostsBefore(earliestPostId, 0, Constants.POST_CHUNK_SIZE, isFocusPost);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function emitLoadMorePostsFocusedBottomEvent() {
|
export function emitLoadMorePostsFocusedBottomEvent() {
|
||||||
const id = PostStore.getFocusedPostId();
|
const id = PostStore.getFocusedPostId();
|
||||||
const latestPostId = PostStore.getLatestPost(id).id;
|
const latestPostId = PostStore.getLatestPost(id).id;
|
||||||
AsyncClient.getPostsAfter(latestPostId, 0, Constants.POST_CHUNK_SIZE, Boolean(id));
|
loadPostsAfter(latestPostId, 0, Constants.POST_CHUNK_SIZE, Boolean(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function emitUserPostedEvent(post) {
|
export function emitUserPostedEvent(post) {
|
||||||
@@ -362,7 +363,7 @@ export function emitClearSuggestions(suggestionId) {
|
|||||||
|
|
||||||
export function emitPreferenceChangedEvent(preference) {
|
export function emitPreferenceChangedEvent(preference) {
|
||||||
if (preference.category === Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW) {
|
if (preference.category === Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW) {
|
||||||
AsyncClient.getDirectProfiles();
|
loadProfilesAndTeamMembersForDMSidebar();
|
||||||
}
|
}
|
||||||
|
|
||||||
AppDispatcher.handleServerAction({
|
AppDispatcher.handleServerAction({
|
||||||
@@ -437,7 +438,7 @@ export function loadDefaultLocale() {
|
|||||||
export function viewLoggedIn() {
|
export function viewLoggedIn() {
|
||||||
AsyncClient.getChannels();
|
AsyncClient.getChannels();
|
||||||
AsyncClient.getMoreChannels();
|
AsyncClient.getMoreChannels();
|
||||||
AsyncClient.getChannelExtraInfo();
|
AsyncClient.getChannelStats();
|
||||||
|
|
||||||
// Clear pending posts (shouldn't have pending posts if we are loading)
|
// Clear pending posts (shouldn't have pending posts if we are loading)
|
||||||
PostStore.clearPendingPosts();
|
PostStore.clearPendingPosts();
|
||||||
|
|||||||
114
webapp/actions/integration_actions.jsx
Обычный файл
114
webapp/actions/integration_actions.jsx
Обычный файл
@@ -0,0 +1,114 @@
|
|||||||
|
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
|
||||||
|
|
||||||
|
import TeamStore from 'stores/team_store.jsx';
|
||||||
|
import UserStore from 'stores/user_store.jsx';
|
||||||
|
|
||||||
|
import * as AsyncClient from 'utils/async_client.jsx';
|
||||||
|
import Client from 'client/web_client.jsx';
|
||||||
|
|
||||||
|
import {ActionTypes} from 'utils/constants.jsx';
|
||||||
|
|
||||||
|
export function loadIncomingHooks() {
|
||||||
|
Client.listIncomingHooks(
|
||||||
|
(data) => {
|
||||||
|
AppDispatcher.handleServerAction({
|
||||||
|
type: ActionTypes.RECEIVED_INCOMING_WEBHOOKS,
|
||||||
|
teamId: TeamStore.getCurrentId(),
|
||||||
|
incomingWebhooks: data
|
||||||
|
});
|
||||||
|
|
||||||
|
loadProfilesForIncomingHooks(data);
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
AsyncClient.dispatchError(err, 'listIncomingHooks');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadProfilesForIncomingHooks(hooks) {
|
||||||
|
const profilesToLoad = {};
|
||||||
|
for (let i = 0; i < hooks.length; i++) {
|
||||||
|
const hook = hooks[i];
|
||||||
|
if (!UserStore.hasProfile(hook.user_id)) {
|
||||||
|
profilesToLoad[hook.user_id] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const list = Object.keys(profilesToLoad);
|
||||||
|
if (list.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncClient.getProfilesByIds(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadOutgoingHooks() {
|
||||||
|
Client.listOutgoingHooks(
|
||||||
|
(data) => {
|
||||||
|
AppDispatcher.handleServerAction({
|
||||||
|
type: ActionTypes.RECEIVED_OUTGOING_WEBHOOKS,
|
||||||
|
teamId: TeamStore.getCurrentId(),
|
||||||
|
outgoingWebhooks: data
|
||||||
|
});
|
||||||
|
|
||||||
|
loadProfilesForOutgoingHooks(data);
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
AsyncClient.dispatchError(err, 'listOutgoingHooks');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadProfilesForOutgoingHooks(hooks) {
|
||||||
|
const profilesToLoad = {};
|
||||||
|
for (let i = 0; i < hooks.length; i++) {
|
||||||
|
const hook = hooks[i];
|
||||||
|
if (!UserStore.hasProfile(hook.creator_id)) {
|
||||||
|
profilesToLoad[hook.creator_id] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const list = Object.keys(profilesToLoad);
|
||||||
|
if (list.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncClient.getProfilesByIds(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadTeamCommands() {
|
||||||
|
Client.listTeamCommands(
|
||||||
|
(data) => {
|
||||||
|
AppDispatcher.handleServerAction({
|
||||||
|
type: ActionTypes.RECEIVED_COMMANDS,
|
||||||
|
teamId: Client.teamId,
|
||||||
|
commands: data
|
||||||
|
});
|
||||||
|
|
||||||
|
loadProfilesForCommands(data);
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
AsyncClient.dispatchError(err, 'loadTeamCommands');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadProfilesForCommands(commands) {
|
||||||
|
const profilesToLoad = {};
|
||||||
|
for (let i = 0; i < commands.length; i++) {
|
||||||
|
const command = commands[i];
|
||||||
|
if (!UserStore.hasProfile(command.creator_id)) {
|
||||||
|
profilesToLoad[command.creator_id] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const list = Object.keys(profilesToLoad);
|
||||||
|
if (list.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncClient.getProfilesByIds(list);
|
||||||
|
}
|
||||||
@@ -8,6 +8,8 @@ import PostStore from 'stores/post_store.jsx';
|
|||||||
import TeamStore from 'stores/team_store.jsx';
|
import TeamStore from 'stores/team_store.jsx';
|
||||||
import UserStore from 'stores/user_store.jsx';
|
import UserStore from 'stores/user_store.jsx';
|
||||||
|
|
||||||
|
import {loadStatusesForChannel} from 'actions/status_actions.jsx';
|
||||||
|
|
||||||
import * as PostUtils from 'utils/post_utils.jsx';
|
import * as PostUtils from 'utils/post_utils.jsx';
|
||||||
import Client from 'client/web_client.jsx';
|
import Client from 'client/web_client.jsx';
|
||||||
import * as AsyncClient from 'utils/async_client.jsx';
|
import * as AsyncClient from 'utils/async_client.jsx';
|
||||||
@@ -52,6 +54,8 @@ export function handleNewPost(post, msg) {
|
|||||||
post,
|
post,
|
||||||
websocketMessageProps
|
websocketMessageProps
|
||||||
});
|
});
|
||||||
|
|
||||||
|
loadProfilesForPosts(data.posts);
|
||||||
},
|
},
|
||||||
(err) => {
|
(err) => {
|
||||||
AsyncClient.dispatchError(err, 'getPost');
|
AsyncClient.dispatchError(err, 'getPost');
|
||||||
@@ -115,7 +119,7 @@ export function setUnreadPost(channelId, postId) {
|
|||||||
member.last_viewed_at = lastViewed;
|
member.last_viewed_at = lastViewed;
|
||||||
member.msg_count = channel.total_msg_count - unreadPosts;
|
member.msg_count = channel.total_msg_count - unreadPosts;
|
||||||
member.mention_count = 0;
|
member.mention_count = 0;
|
||||||
ChannelStore.setChannelMember(member);
|
ChannelStore.storeMyChannelMember(member);
|
||||||
ChannelStore.setUnreadCount(channelId);
|
ChannelStore.setUnreadCount(channelId);
|
||||||
AsyncClient.setLastViewedAt(lastViewed, channelId);
|
AsyncClient.setLastViewedAt(lastViewed, channelId);
|
||||||
}
|
}
|
||||||
@@ -153,9 +157,156 @@ export function getFlaggedPosts() {
|
|||||||
results: data,
|
results: data,
|
||||||
is_flagged_posts: true
|
is_flagged_posts: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
loadProfilesForPosts(data.posts);
|
||||||
},
|
},
|
||||||
(err) => {
|
(err) => {
|
||||||
AsyncClient.dispatchError(err, 'getFlaggedPosts');
|
AsyncClient.dispatchError(err, 'getFlaggedPosts');
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function loadPosts(channelId = ChannelStore.getCurrentId()) {
|
||||||
|
const postList = PostStore.getAllPosts(channelId);
|
||||||
|
const latestPostTime = PostStore.getLatestPostFromPageTime(channelId);
|
||||||
|
|
||||||
|
if (!postList || Object.keys(postList).length === 0 || postList.order.length < Constants.POST_CHUNK_SIZE || latestPostTime === 0) {
|
||||||
|
loadPostsPage(channelId, Constants.POST_CHUNK_SIZE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Client.getPosts(
|
||||||
|
channelId,
|
||||||
|
latestPostTime,
|
||||||
|
(data) => {
|
||||||
|
AppDispatcher.handleServerAction({
|
||||||
|
type: ActionTypes.RECEIVED_POSTS,
|
||||||
|
id: channelId,
|
||||||
|
before: true,
|
||||||
|
numRequested: 0,
|
||||||
|
post_list: data
|
||||||
|
});
|
||||||
|
|
||||||
|
loadProfilesForPosts(data.posts);
|
||||||
|
loadStatusesForChannel(channelId);
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
AsyncClient.dispatchError(err, 'loadPosts');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadPostsPage(channelId = ChannelStore.getCurrentId(), max = Constants.POST_CHUNK_SIZE) {
|
||||||
|
const postList = PostStore.getAllPosts(channelId);
|
||||||
|
|
||||||
|
// if we already have more than POST_CHUNK_SIZE posts,
|
||||||
|
// let's get the amount we have but rounded up to next multiple of POST_CHUNK_SIZE,
|
||||||
|
// with a max
|
||||||
|
let numPosts = Math.min(max, Constants.POST_CHUNK_SIZE);
|
||||||
|
if (postList && postList.order.length > 0) {
|
||||||
|
numPosts = Math.min(max, Constants.POST_CHUNK_SIZE * Math.ceil(postList.order.length / Constants.POST_CHUNK_SIZE));
|
||||||
|
}
|
||||||
|
|
||||||
|
Client.getPostsPage(
|
||||||
|
channelId,
|
||||||
|
0,
|
||||||
|
numPosts,
|
||||||
|
(data) => {
|
||||||
|
AppDispatcher.handleServerAction({
|
||||||
|
type: ActionTypes.RECEIVED_POSTS,
|
||||||
|
id: channelId,
|
||||||
|
before: true,
|
||||||
|
numRequested: numPosts,
|
||||||
|
checkLatest: true,
|
||||||
|
post_list: data
|
||||||
|
});
|
||||||
|
|
||||||
|
loadProfilesForPosts(data.posts);
|
||||||
|
loadStatusesForChannel(channelId);
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
AsyncClient.dispatchError(err, 'loadPostsPage');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadPostsBefore(postId, offset, numPost, isPost) {
|
||||||
|
const channelId = ChannelStore.getCurrentId();
|
||||||
|
if (channelId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Client.getPostsBefore(
|
||||||
|
channelId,
|
||||||
|
postId,
|
||||||
|
offset,
|
||||||
|
numPost,
|
||||||
|
(data) => {
|
||||||
|
AppDispatcher.handleServerAction({
|
||||||
|
type: ActionTypes.RECEIVED_POSTS,
|
||||||
|
id: channelId,
|
||||||
|
before: true,
|
||||||
|
numRequested: numPost,
|
||||||
|
post_list: data,
|
||||||
|
isPost
|
||||||
|
});
|
||||||
|
|
||||||
|
loadProfilesForPosts(data.posts);
|
||||||
|
loadStatusesForChannel(channelId);
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
AsyncClient.dispatchError(err, 'loadPostsBefore');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadPostsAfter(postId, offset, numPost, isPost) {
|
||||||
|
const channelId = ChannelStore.getCurrentId();
|
||||||
|
if (channelId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Client.getPostsAfter(
|
||||||
|
channelId,
|
||||||
|
postId,
|
||||||
|
offset,
|
||||||
|
numPost,
|
||||||
|
(data) => {
|
||||||
|
AppDispatcher.handleServerAction({
|
||||||
|
type: ActionTypes.RECEIVED_POSTS,
|
||||||
|
id: channelId,
|
||||||
|
before: false,
|
||||||
|
numRequested: numPost,
|
||||||
|
post_list: data,
|
||||||
|
isPost
|
||||||
|
});
|
||||||
|
|
||||||
|
loadProfilesForPosts(data.posts);
|
||||||
|
loadStatusesForChannel(channelId);
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
AsyncClient.dispatchError(err, 'loadPostsAfter');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadProfilesForPosts(posts) {
|
||||||
|
const profilesToLoad = {};
|
||||||
|
for (const pid in posts) {
|
||||||
|
if (!posts.hasOwnProperty(pid)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const post = posts[pid];
|
||||||
|
if (!UserStore.hasProfile(post.user_id)) {
|
||||||
|
profilesToLoad[post.user_id] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const list = Object.keys(profilesToLoad);
|
||||||
|
if (list.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncClient.getProfilesByIds(list);
|
||||||
|
}
|
||||||
|
|||||||
133
webapp/actions/status_actions.jsx
Обычный файл
133
webapp/actions/status_actions.jsx
Обычный файл
@@ -0,0 +1,133 @@
|
|||||||
|
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
|
||||||
|
|
||||||
|
import ChannelStore from 'stores/channel_store.jsx';
|
||||||
|
import PostStore from 'stores/post_store.jsx';
|
||||||
|
import PreferenceStore from 'stores/preference_store.jsx';
|
||||||
|
|
||||||
|
import Client from 'client/web_client.jsx';
|
||||||
|
|
||||||
|
import {ActionTypes, Preferences, Constants} from 'utils/constants.jsx';
|
||||||
|
|
||||||
|
export function loadStatusesForChannel(channelId = ChannelStore.getCurrentId()) {
|
||||||
|
const postList = PostStore.getVisiblePosts(channelId);
|
||||||
|
if (!postList || !postList.posts) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const statusesToLoad = {};
|
||||||
|
for (const pid in postList.posts) {
|
||||||
|
if (!postList.posts.hasOwnProperty(pid)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const post = postList.posts[pid];
|
||||||
|
statusesToLoad[post.user_id] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadStatusesByIds(Object.keys(statusesToLoad));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadStatusesForDMSidebar() {
|
||||||
|
const dmPrefs = PreferenceStore.getCategory(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW);
|
||||||
|
const statusesToLoad = [];
|
||||||
|
|
||||||
|
for (const [key, value] of dmPrefs) {
|
||||||
|
if (value === 'true') {
|
||||||
|
statusesToLoad.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadStatusesByIds(statusesToLoad);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadStatusesForChannelAndSidebar() {
|
||||||
|
const statusesToLoad = {};
|
||||||
|
|
||||||
|
const channelId = ChannelStore.getCurrentId();
|
||||||
|
const postList = PostStore.getVisiblePosts(channelId);
|
||||||
|
if (postList && postList.posts) {
|
||||||
|
for (const pid in postList.posts) {
|
||||||
|
if (!postList.posts.hasOwnProperty(pid)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const post = postList.posts[pid];
|
||||||
|
statusesToLoad[post.user_id] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const dmPrefs = PreferenceStore.getCategory(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW);
|
||||||
|
|
||||||
|
for (const [key, value] of dmPrefs) {
|
||||||
|
if (value === 'true') {
|
||||||
|
statusesToLoad[key] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadStatusesByIds(Object.keys(statusesToLoad));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadStatusesForProfilesList(users) {
|
||||||
|
if (users == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const statusesToLoad = [];
|
||||||
|
for (let i = 0; i < users.length; i++) {
|
||||||
|
statusesToLoad.push(users[i].id);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadStatusesByIds(statusesToLoad);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadStatusesForProfilesMap(users) {
|
||||||
|
if (users == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const statusesToLoad = [];
|
||||||
|
for (const userId in users) {
|
||||||
|
if (!users.hasOwnProperty(userId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
statusesToLoad.push(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadStatusesByIds(statusesToLoad);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadStatusesByIds(userIds) {
|
||||||
|
if (userIds.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Client.getStatusesByIds(
|
||||||
|
userIds,
|
||||||
|
(data) => {
|
||||||
|
AppDispatcher.handleServerAction({
|
||||||
|
type: ActionTypes.RECEIVED_STATUSES,
|
||||||
|
statuses: data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let intervalId = '';
|
||||||
|
|
||||||
|
export function startPeriodicStatusUpdates() {
|
||||||
|
clearInterval(intervalId);
|
||||||
|
|
||||||
|
intervalId = setInterval(
|
||||||
|
() => {
|
||||||
|
loadStatusesForChannelAndSidebar();
|
||||||
|
},
|
||||||
|
Constants.STATUS_INTERVAL
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stopPeriodicStatusUpdates() {
|
||||||
|
clearInterval(intervalId);
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
import UserStore from 'stores/user_store.jsx';
|
import UserStore from 'stores/user_store.jsx';
|
||||||
|
import TeamStore from 'stores/team_store.jsx';
|
||||||
|
|
||||||
import Constants from 'utils/constants.jsx';
|
import Constants from 'utils/constants.jsx';
|
||||||
const ActionTypes = Constants.ActionTypes;
|
const ActionTypes = Constants.ActionTypes;
|
||||||
@@ -19,8 +20,6 @@ export function checkIfTeamExists(teamName, onSuccess, onError) {
|
|||||||
export function createTeam(team, onSuccess, onError) {
|
export function createTeam(team, onSuccess, onError) {
|
||||||
Client.createTeam(team,
|
Client.createTeam(team,
|
||||||
(rteam) => {
|
(rteam) => {
|
||||||
AsyncClient.getDirectProfiles();
|
|
||||||
|
|
||||||
AppDispatcher.handleServerAction({
|
AppDispatcher.handleServerAction({
|
||||||
type: ActionTypes.CREATED_TEAM,
|
type: ActionTypes.CREATED_TEAM,
|
||||||
team: rteam,
|
team: rteam,
|
||||||
@@ -36,3 +35,25 @@ export function createTeam(team, onSuccess, onError) {
|
|||||||
onError
|
onError
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function removeUserFromTeam(teamId, userId, success, error) {
|
||||||
|
Client.removeUserFromTeam(
|
||||||
|
teamId,
|
||||||
|
userId,
|
||||||
|
() => {
|
||||||
|
TeamStore.removeMemberInTeam(teamId, userId);
|
||||||
|
AsyncClient.getUser(userId);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
success();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
AsyncClient.dispatchError(err, 'removeUserFromTeam');
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,12 +2,17 @@
|
|||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
|
import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
|
||||||
import * as AsyncClient from 'utils/async_client.jsx';
|
|
||||||
import Client from 'client/web_client.jsx';
|
|
||||||
|
|
||||||
import PreferenceStore from 'stores/preference_store.jsx';
|
import PreferenceStore from 'stores/preference_store.jsx';
|
||||||
import TeamStore from 'stores/team_store.jsx';
|
import TeamStore from 'stores/team_store.jsx';
|
||||||
import UserStore from 'stores/user_store.jsx';
|
import UserStore from 'stores/user_store.jsx';
|
||||||
|
import ChannelStore from 'stores/channel_store.jsx';
|
||||||
|
|
||||||
|
import {loadStatusesForProfilesList, loadStatusesForProfilesMap} from 'actions/status_actions.jsx';
|
||||||
|
|
||||||
|
import {getDirectChannelName} from 'utils/utils.jsx';
|
||||||
|
import * as AsyncClient from 'utils/async_client.jsx';
|
||||||
|
import Client from 'client/web_client.jsx';
|
||||||
|
|
||||||
import {ActionTypes, Preferences} from 'utils/constants.jsx';
|
import {ActionTypes, Preferences} from 'utils/constants.jsx';
|
||||||
|
|
||||||
@@ -29,9 +34,179 @@ export function switchFromLdapToEmail(email, password, ldapPassword, onSuccess,
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMoreDmList() {
|
export function loadProfilesAndTeamMembers(offset, limit, teamId = TeamStore.getCurrentId(), success, error) {
|
||||||
AsyncClient.getTeamMembers(TeamStore.getCurrentId());
|
Client.getProfilesInTeam(
|
||||||
AsyncClient.getProfilesForDirectMessageList();
|
teamId,
|
||||||
|
offset,
|
||||||
|
limit,
|
||||||
|
(data) => {
|
||||||
|
AppDispatcher.handleServerAction({
|
||||||
|
type: ActionTypes.RECEIVED_PROFILES_IN_TEAM,
|
||||||
|
profiles: data,
|
||||||
|
team_id: teamId,
|
||||||
|
offset,
|
||||||
|
count: Object.keys(data).length
|
||||||
|
});
|
||||||
|
|
||||||
|
loadTeamMembersForProfilesMap(data, teamId, success, error);
|
||||||
|
loadStatusesForProfilesMap(data);
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
AsyncClient.dispatchError(err, 'getProfilesInTeam');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadTeamMembersForProfilesMap(profiles, teamId = TeamStore.getCurrentId(), success, error) {
|
||||||
|
const membersToLoad = {};
|
||||||
|
for (const pid in profiles) {
|
||||||
|
if (!profiles.hasOwnProperty(pid)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TeamStore.hasActiveMemberInTeam(teamId, pid)) {
|
||||||
|
membersToLoad[pid] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const list = Object.keys(membersToLoad);
|
||||||
|
if (list.length === 0) {
|
||||||
|
if (success) {
|
||||||
|
success({});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadTeamMembersForProfiles(list, teamId, success, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadTeamMembersForProfilesList(profiles, teamId = TeamStore.getCurrentId(), success, error) {
|
||||||
|
const membersToLoad = {};
|
||||||
|
for (let i = 0; i < profiles.length; i++) {
|
||||||
|
const pid = profiles[i].id;
|
||||||
|
|
||||||
|
if (!TeamStore.hasActiveMemberInTeam(teamId, pid)) {
|
||||||
|
membersToLoad[pid] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const list = Object.keys(membersToLoad);
|
||||||
|
if (list.length === 0) {
|
||||||
|
if (success) {
|
||||||
|
success({});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadTeamMembersForProfiles(list, teamId, success, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadTeamMembersForProfiles(userIds, teamId, success, error) {
|
||||||
|
Client.getTeamMembersByIds(
|
||||||
|
teamId,
|
||||||
|
userIds,
|
||||||
|
(data) => {
|
||||||
|
const memberMap = {};
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
memberMap[data[i].user_id] = data[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
AppDispatcher.handleServerAction({
|
||||||
|
type: ActionTypes.RECEIVED_MEMBERS_IN_TEAM,
|
||||||
|
team_id: teamId,
|
||||||
|
team_members: memberMap
|
||||||
|
});
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
success(data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
AsyncClient.dispatchError(err, 'getTeamMembersByIds');
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function populateDMChannelsWithProfiles(userIds) {
|
||||||
|
const currentUserId = UserStore.getCurrentId();
|
||||||
|
|
||||||
|
for (let i = 0; i < userIds.length; i++) {
|
||||||
|
const channelName = getDirectChannelName(currentUserId, userIds[i]);
|
||||||
|
const channel = ChannelStore.getByName(channelName);
|
||||||
|
if (channel) {
|
||||||
|
UserStore.saveUserIdInChannel(channel.id, userIds[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadProfilesAndTeamMembersForDMSidebar() {
|
||||||
|
const dmPrefs = PreferenceStore.getCategory(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW);
|
||||||
|
const teamId = TeamStore.getCurrentId();
|
||||||
|
const profilesToLoad = [];
|
||||||
|
const membersToLoad = [];
|
||||||
|
|
||||||
|
for (const [key, value] of dmPrefs) {
|
||||||
|
if (value === 'true') {
|
||||||
|
if (!UserStore.hasProfile(key)) {
|
||||||
|
profilesToLoad.push(key);
|
||||||
|
}
|
||||||
|
membersToLoad.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (profilesToLoad.length > 0) {
|
||||||
|
Client.getProfilesByIds(
|
||||||
|
profilesToLoad,
|
||||||
|
(data) => {
|
||||||
|
AppDispatcher.handleServerAction({
|
||||||
|
type: ActionTypes.RECEIVED_PROFILES,
|
||||||
|
profiles: data
|
||||||
|
});
|
||||||
|
|
||||||
|
// Use membersToLoad so we get all the DM profiles even if they were already loaded
|
||||||
|
populateDMChannelsWithProfiles(membersToLoad);
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
AsyncClient.dispatchError(err, 'getProfilesByIds');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
populateDMChannelsWithProfiles(membersToLoad);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (membersToLoad.length > 0) {
|
||||||
|
Client.getTeamMembersByIds(
|
||||||
|
teamId,
|
||||||
|
membersToLoad,
|
||||||
|
(data) => {
|
||||||
|
const memberMap = {};
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
memberMap[data[i].user_id] = data[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
const nonMembersMap = {};
|
||||||
|
for (let i = 0; i < membersToLoad.length; i++) {
|
||||||
|
if (!memberMap[membersToLoad[i]]) {
|
||||||
|
nonMembersMap[membersToLoad[i]] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AppDispatcher.handleServerAction({
|
||||||
|
type: ActionTypes.RECEIVED_MEMBERS_IN_TEAM,
|
||||||
|
team_id: teamId,
|
||||||
|
team_members: memberMap,
|
||||||
|
non_team_members: nonMembersMap
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
AsyncClient.dispatchError(err, 'getTeamMembersByIds');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function saveTheme(teamId, theme, onSuccess, onError) {
|
export function saveTheme(teamId, theme, onSuccess, onError) {
|
||||||
@@ -82,3 +257,62 @@ function onThemeSaved(teamId, theme, onSuccess) {
|
|||||||
|
|
||||||
onSuccess();
|
onSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function searchUsers(term, teamId = TeamStore.getCurrentId(), options = {}, success, error) {
|
||||||
|
Client.searchUsers(
|
||||||
|
term,
|
||||||
|
teamId,
|
||||||
|
options,
|
||||||
|
(data) => {
|
||||||
|
loadStatusesForProfilesList(data);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
success(data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
AsyncClient.dispatchError(err, 'searchUsers');
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function autocompleteUsersInChannel(username, channelId, success, error) {
|
||||||
|
Client.autocompleteUsersInChannel(
|
||||||
|
username,
|
||||||
|
channelId,
|
||||||
|
(data) => {
|
||||||
|
if (success) {
|
||||||
|
success(data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
AsyncClient.dispatchError(err, 'autocompleteUsersInChannel');
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function autocompleteUsersInTeam(username, success, error) {
|
||||||
|
Client.autocompleteUsersInTeam(
|
||||||
|
username,
|
||||||
|
(data) => {
|
||||||
|
if (success) {
|
||||||
|
success(data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
AsyncClient.dispatchError(err, 'autocompleteUsersInTeam');
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,8 +3,6 @@
|
|||||||
|
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
|
|
||||||
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
|
|
||||||
|
|
||||||
import UserStore from 'stores/user_store.jsx';
|
import UserStore from 'stores/user_store.jsx';
|
||||||
import TeamStore from 'stores/team_store.jsx';
|
import TeamStore from 'stores/team_store.jsx';
|
||||||
import PostStore from 'stores/post_store.jsx';
|
import PostStore from 'stores/post_store.jsx';
|
||||||
@@ -20,10 +18,11 @@ import * as Utils from 'utils/utils.jsx';
|
|||||||
import * as AsyncClient from 'utils/async_client.jsx';
|
import * as AsyncClient from 'utils/async_client.jsx';
|
||||||
|
|
||||||
import * as GlobalActions from 'actions/global_actions.jsx';
|
import * as GlobalActions from 'actions/global_actions.jsx';
|
||||||
import * as UserActions from 'actions/user_actions.jsx';
|
import {handleNewPost, loadPosts} from 'actions/post_actions.jsx';
|
||||||
import {handleNewPost} from 'actions/post_actions.jsx';
|
import {loadProfilesAndTeamMembersForDMSidebar} from 'actions/user_actions.jsx';
|
||||||
|
import * as StatusActions from 'actions/status_actions.jsx';
|
||||||
|
|
||||||
import {Constants, SocketEvents, ActionTypes} from 'utils/constants.jsx';
|
import {Constants, SocketEvents, UserStatuses} from 'utils/constants.jsx';
|
||||||
|
|
||||||
import {browserHistory} from 'react-router/es6';
|
import {browserHistory} from 'react-router/es6';
|
||||||
|
|
||||||
@@ -53,6 +52,7 @@ export function initialize() {
|
|||||||
connUrl += Client.getUsersRoute() + '/websocket';
|
connUrl += Client.getUsersRoute() + '/websocket';
|
||||||
|
|
||||||
WebSocketClient.setEventCallback(handleEvent);
|
WebSocketClient.setEventCallback(handleEvent);
|
||||||
|
WebSocketClient.setFirstConnectCallback(handleFirstConnect);
|
||||||
WebSocketClient.setReconnectCallback(handleReconnect);
|
WebSocketClient.setReconnectCallback(handleReconnect);
|
||||||
WebSocketClient.setCloseCallback(handleClose);
|
WebSocketClient.setCloseCallback(handleClose);
|
||||||
WebSocketClient.initialize(connUrl);
|
WebSocketClient.initialize(connUrl);
|
||||||
@@ -64,22 +64,19 @@ export function close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getStatuses() {
|
export function getStatuses() {
|
||||||
WebSocketClient.getStatuses(
|
StatusActions.loadStatusesForChannelAndSidebar();
|
||||||
(resp) => {
|
}
|
||||||
AppDispatcher.handleServerAction({
|
|
||||||
type: ActionTypes.RECEIVED_STATUSES,
|
function handleFirstConnect() {
|
||||||
statuses: resp.data
|
getStatuses();
|
||||||
});
|
ErrorStore.clearLastError();
|
||||||
}
|
ErrorStore.emitChange();
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleReconnect() {
|
function handleReconnect() {
|
||||||
if (Client.teamId) {
|
if (Client.teamId) {
|
||||||
AsyncClient.getChannels();
|
AsyncClient.getChannels();
|
||||||
AsyncClient.getPosts(ChannelStore.getCurrentId());
|
loadPosts(ChannelStore.getCurrentId());
|
||||||
AsyncClient.getTeamMembers(TeamStore.getCurrentId());
|
|
||||||
AsyncClient.getProfiles();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getStatuses();
|
getStatuses();
|
||||||
@@ -112,7 +109,7 @@ function handleEvent(msg) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case SocketEvents.NEW_USER:
|
case SocketEvents.NEW_USER:
|
||||||
handleNewUserEvent();
|
handleNewUserEvent(msg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SocketEvents.LEAVE_TEAM:
|
case SocketEvents.LEAVE_TEAM:
|
||||||
@@ -170,6 +167,10 @@ function handleEvent(msg) {
|
|||||||
function handleNewPostEvent(msg) {
|
function handleNewPostEvent(msg) {
|
||||||
const post = JSON.parse(msg.data.post);
|
const post = JSON.parse(msg.data.post);
|
||||||
handleNewPost(post, msg);
|
handleNewPost(post, msg);
|
||||||
|
|
||||||
|
if (UserStore.getStatus(post.user_id) !== UserStatuses.ONLINE) {
|
||||||
|
StatusActions.loadStatusesByIds([post.user_id]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePostEditEvent(msg) {
|
function handlePostEditEvent(msg) {
|
||||||
@@ -196,36 +197,33 @@ function handlePostDeleteEvent(msg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleNewUserEvent() {
|
function handleNewUserEvent(msg) {
|
||||||
AsyncClient.getTeamMembers(TeamStore.getCurrentId());
|
AsyncClient.getUser(msg.user_id);
|
||||||
AsyncClient.getProfiles();
|
AsyncClient.getChannelStats();
|
||||||
AsyncClient.getDirectProfiles();
|
loadProfilesAndTeamMembersForDMSidebar();
|
||||||
AsyncClient.getChannelExtraInfo();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleLeaveTeamEvent(msg) {
|
function handleLeaveTeamEvent(msg) {
|
||||||
if (UserStore.getCurrentId() === msg.data.user_id) {
|
if (UserStore.getCurrentId() === msg.data.user_id) {
|
||||||
TeamStore.removeTeamMember(msg.broadcast.team_id);
|
TeamStore.removeMyTeamMember(msg.broadcast.team_id);
|
||||||
|
|
||||||
// if the are on the team begin removed redirect them to the root
|
// if they are on the team being removed redirect them to the root
|
||||||
if (TeamStore.getCurrentId() === msg.broadcast.team_id) {
|
if (TeamStore.getCurrentId() === msg.broadcast.team_id) {
|
||||||
TeamStore.setCurrentId('');
|
TeamStore.setCurrentId('');
|
||||||
Client.setTeamId('');
|
Client.setTeamId('');
|
||||||
browserHistory.push('/');
|
browserHistory.push('/');
|
||||||
}
|
}
|
||||||
} else if (TeamStore.getCurrentId() === msg.broadcast.team_id) {
|
|
||||||
UserActions.getMoreDmList();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDirectAddedEvent(msg) {
|
function handleDirectAddedEvent(msg) {
|
||||||
AsyncClient.getChannel(msg.broadcast.channel_id);
|
AsyncClient.getChannel(msg.broadcast.channel_id);
|
||||||
AsyncClient.getDirectProfiles();
|
loadProfilesAndTeamMembersForDMSidebar();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleUserAddedEvent(msg) {
|
function handleUserAddedEvent(msg) {
|
||||||
if (ChannelStore.getCurrentId() === msg.broadcast.channel_id) {
|
if (ChannelStore.getCurrentId() === msg.broadcast.channel_id) {
|
||||||
AsyncClient.getChannelExtraInfo();
|
AsyncClient.getChannelStats();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TeamStore.getCurrentId() === msg.data.team_id && UserStore.getCurrentId() === msg.data.user_id) {
|
if (TeamStore.getCurrentId() === msg.data.team_id && UserStore.getCurrentId() === msg.data.user_id) {
|
||||||
@@ -248,7 +246,7 @@ function handleUserRemovedEvent(msg) {
|
|||||||
$('#removed_from_channel').modal('show');
|
$('#removed_from_channel').modal('show');
|
||||||
}
|
}
|
||||||
} else if (ChannelStore.getCurrentId() === msg.broadcast.channel_id) {
|
} else if (ChannelStore.getCurrentId() === msg.broadcast.channel_id) {
|
||||||
AsyncClient.getChannelExtraInfo();
|
AsyncClient.getChannelStats();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,6 +285,10 @@ function handlePreferenceChangedEvent(msg) {
|
|||||||
|
|
||||||
function handleUserTypingEvent(msg) {
|
function handleUserTypingEvent(msg) {
|
||||||
GlobalActions.emitRemoteUserTypingEvent(msg.broadcast.channel_id, msg.data.user_id, msg.data.parent_id);
|
GlobalActions.emitRemoteUserTypingEvent(msg.broadcast.channel_id, msg.data.user_id, msg.data.parent_id);
|
||||||
|
|
||||||
|
if (UserStore.getStatus(msg.data.user_id) !== UserStatuses.ONLINE) {
|
||||||
|
StatusActions.loadStatusesByIds([msg.data.user_id]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleStatusChangedEvent(msg) {
|
function handleStatusChangedEvent(msg) {
|
||||||
@@ -301,4 +303,4 @@ function handleHelloEvent(msg) {
|
|||||||
function handleWebrtc(msg) {
|
function handleWebrtc(msg) {
|
||||||
const data = msg.data;
|
const data = msg.data;
|
||||||
return WebrtcActions.handle(data);
|
return WebrtcActions.handle(data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,11 +73,7 @@ export default class Client {
|
|||||||
return `${this.url}${this.urlVersion}/teams`;
|
return `${this.url}${this.urlVersion}/teams`;
|
||||||
}
|
}
|
||||||
|
|
||||||
getTeamNeededRoute() {
|
getTeamNeededRoute(teamId = this.getTeamId()) {
|
||||||
return `${this.url}${this.urlVersion}/teams/${this.getTeamId()}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
getTeamNeededManualRoute(teamId) {
|
|
||||||
return `${this.url}${this.urlVersion}/teams/${teamId}`;
|
return `${this.url}${this.urlVersion}/teams/${teamId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -565,15 +561,43 @@ export default class Client {
|
|||||||
end(this.handleResponse.bind(this, 'getMyTeam', success, error));
|
end(this.handleResponse.bind(this, 'getMyTeam', success, error));
|
||||||
}
|
}
|
||||||
|
|
||||||
getTeamMembers(teamId, success, error) {
|
getTeamMembers(teamId, offset, limit, success, error) {
|
||||||
request.
|
request.
|
||||||
get(`${this.getTeamsRoute()}/members/${teamId}`).
|
get(`${this.getTeamNeededRoute(teamId)}/members/${offset}/${limit}`).
|
||||||
set(this.defaultHeaders).
|
set(this.defaultHeaders).
|
||||||
type('application/json').
|
type('application/json').
|
||||||
accept('application/json').
|
accept('application/json').
|
||||||
end(this.handleResponse.bind(this, 'getTeamMembers', success, error));
|
end(this.handleResponse.bind(this, 'getTeamMembers', success, error));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTeamMember(teamId, userId, success, error) {
|
||||||
|
request.
|
||||||
|
get(`${this.getTeamNeededRoute(teamId)}/members/${userId}`).
|
||||||
|
set(this.defaultHeaders).
|
||||||
|
type('application/json').
|
||||||
|
accept('application/json').
|
||||||
|
end(this.handleResponse.bind(this, 'getTeamMember', success, error));
|
||||||
|
}
|
||||||
|
|
||||||
|
getTeamMembersByIds(teamId, userIds, success, error) {
|
||||||
|
request.
|
||||||
|
post(`${this.getTeamNeededRoute(teamId)}/members/ids`).
|
||||||
|
set(this.defaultHeaders).
|
||||||
|
type('application/json').
|
||||||
|
accept('application/json').
|
||||||
|
send(userIds).
|
||||||
|
end(this.handleResponse.bind(this, 'getTeamMembersByIds', success, error));
|
||||||
|
}
|
||||||
|
|
||||||
|
getTeamStats(teamId, success, error) {
|
||||||
|
request.
|
||||||
|
get(`${this.getTeamNeededRoute(teamId)}/stats`).
|
||||||
|
set(this.defaultHeaders).
|
||||||
|
type('application/json').
|
||||||
|
accept('application/json').
|
||||||
|
end(this.handleResponse.bind(this, 'getTeamStats', success, error));
|
||||||
|
}
|
||||||
|
|
||||||
inviteMembers(data, success, error) {
|
inviteMembers(data, success, error) {
|
||||||
request.
|
request.
|
||||||
post(`${this.getTeamNeededRoute()}/invite_members`).
|
post(`${this.getTeamNeededRoute()}/invite_members`).
|
||||||
@@ -740,7 +764,7 @@ export default class Client {
|
|||||||
};
|
};
|
||||||
|
|
||||||
request.
|
request.
|
||||||
post(`${this.getTeamNeededManualRoute(teamId)}/update_member_roles`).
|
post(`${this.getTeamNeededRoute(teamId)}/update_member_roles`).
|
||||||
set(this.defaultHeaders).
|
set(this.defaultHeaders).
|
||||||
type('application/json').
|
type('application/json').
|
||||||
accept('application/json').
|
accept('application/json').
|
||||||
@@ -1003,40 +1027,78 @@ export default class Client {
|
|||||||
end(this.handleResponse.bind(this, 'getRecentlyActiveUsers', success, error));
|
end(this.handleResponse.bind(this, 'getRecentlyActiveUsers', success, error));
|
||||||
}
|
}
|
||||||
|
|
||||||
getDirectProfiles(success, error) {
|
getProfiles(offset, limit, success, error) {
|
||||||
request.
|
request.
|
||||||
get(`${this.getUsersRoute()}/direct_profiles`).
|
get(`${this.getUsersRoute()}/${offset}/${limit}`).
|
||||||
set(this.defaultHeaders).
|
|
||||||
type('application/json').
|
|
||||||
accept('application/json').
|
|
||||||
end(this.handleResponse.bind(this, 'getDirectProfiles', success, error));
|
|
||||||
}
|
|
||||||
|
|
||||||
getProfiles(success, error) {
|
|
||||||
request.
|
|
||||||
get(`${this.getUsersRoute()}/profiles/${this.getTeamId()}`).
|
|
||||||
set(this.defaultHeaders).
|
set(this.defaultHeaders).
|
||||||
type('application/json').
|
type('application/json').
|
||||||
accept('application/json').
|
accept('application/json').
|
||||||
end(this.handleResponse.bind(this, 'getProfiles', success, error));
|
end(this.handleResponse.bind(this, 'getProfiles', success, error));
|
||||||
}
|
}
|
||||||
|
|
||||||
getProfilesForTeam(teamId, success, error) {
|
getProfilesInTeam(teamId, offset, limit, success, error) {
|
||||||
request.
|
request.
|
||||||
get(`${this.getUsersRoute()}/profiles/${teamId}`).
|
get(`${this.getTeamNeededRoute(teamId)}/users/${offset}/${limit}`).
|
||||||
set(this.defaultHeaders).
|
set(this.defaultHeaders).
|
||||||
type('application/json').
|
type('application/json').
|
||||||
accept('application/json').
|
accept('application/json').
|
||||||
end(this.handleResponse.bind(this, 'getProfilesForTeam', success, error));
|
end(this.handleResponse.bind(this, 'getProfilesInTeam', success, error));
|
||||||
}
|
}
|
||||||
|
|
||||||
getProfilesForDirectMessageList(success, error) {
|
getProfilesInChannel(channelId, offset, limit, success, error) {
|
||||||
request.
|
request.
|
||||||
get(`${this.getUsersRoute()}/profiles_for_dm_list/${this.getTeamId()}`).
|
get(`${this.getChannelNeededRoute(channelId)}/users/${offset}/${limit}`).
|
||||||
set(this.defaultHeaders).
|
set(this.defaultHeaders).
|
||||||
type('application/json').
|
type('application/json').
|
||||||
accept('application/json').
|
accept('application/json').
|
||||||
end(this.handleResponse.bind(this, 'getProfilesForDirectMessageList', success, error));
|
end(this.handleResponse.bind(this, 'getProfilesInChannel', success, error));
|
||||||
|
}
|
||||||
|
|
||||||
|
getProfilesNotInChannel(channelId, offset, limit, success, error) {
|
||||||
|
request.
|
||||||
|
get(`${this.getChannelNeededRoute(channelId)}/users/not_in_channel/${offset}/${limit}`).
|
||||||
|
set(this.defaultHeaders).
|
||||||
|
type('application/json').
|
||||||
|
accept('application/json').
|
||||||
|
end(this.handleResponse.bind(this, 'getProfilesNotInChannel', success, error));
|
||||||
|
}
|
||||||
|
|
||||||
|
getProfilesByIds(userIds, success, error) {
|
||||||
|
request.
|
||||||
|
post(`${this.getUsersRoute()}/ids`).
|
||||||
|
set(this.defaultHeaders).
|
||||||
|
type('application/json').
|
||||||
|
accept('application/json').
|
||||||
|
send(userIds).
|
||||||
|
end(this.handleResponse.bind(this, 'getProfilesByIds', success, error));
|
||||||
|
}
|
||||||
|
|
||||||
|
searchUsers(term, teamId, options, success, error) {
|
||||||
|
request.
|
||||||
|
post(`${this.getUsersRoute()}/search`).
|
||||||
|
set(this.defaultHeaders).
|
||||||
|
type('application/json').
|
||||||
|
accept('application/json').
|
||||||
|
send({term, team_id: teamId, ...options}).
|
||||||
|
end(this.handleResponse.bind(this, 'searchUsers', success, error));
|
||||||
|
}
|
||||||
|
|
||||||
|
autocompleteUsersInChannel(term, channelId, success, error) {
|
||||||
|
request.
|
||||||
|
get(`${this.getChannelNeededRoute(channelId)}/users/autocomplete?term=${encodeURIComponent(term)}`).
|
||||||
|
set(this.defaultHeaders).
|
||||||
|
type('application/json').
|
||||||
|
accept('application/json').
|
||||||
|
end(this.handleResponse.bind(this, 'autocompleteUsers', success, error));
|
||||||
|
}
|
||||||
|
|
||||||
|
autocompleteUsersInTeam(term, success, error) {
|
||||||
|
request.
|
||||||
|
get(`${this.getTeamNeededRoute()}/users/autocomplete?term=${encodeURIComponent(term)}`).
|
||||||
|
set(this.defaultHeaders).
|
||||||
|
type('application/json').
|
||||||
|
accept('application/json').
|
||||||
|
end(this.handleResponse.bind(this, 'autocompleteUsers', success, error));
|
||||||
}
|
}
|
||||||
|
|
||||||
getStatuses(success, error) {
|
getStatuses(success, error) {
|
||||||
@@ -1048,6 +1110,16 @@ export default class Client {
|
|||||||
end(this.handleResponse.bind(this, 'getStatuses', success, error));
|
end(this.handleResponse.bind(this, 'getStatuses', success, error));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getStatusesByIds(userIds, success, error) {
|
||||||
|
request.
|
||||||
|
post(`${this.getUsersRoute()}/status/ids`).
|
||||||
|
set(this.defaultHeaders).
|
||||||
|
type('application/json').
|
||||||
|
accept('application/json').
|
||||||
|
send(userIds).
|
||||||
|
end(this.handleResponse.bind(this, 'getStatuses', success, error));
|
||||||
|
}
|
||||||
|
|
||||||
setActiveChannel(id, success, error) {
|
setActiveChannel(id, success, error) {
|
||||||
request.
|
request.
|
||||||
post(`${this.getUsersRoute()}/status/set_active_channel`).
|
post(`${this.getUsersRoute()}/status/set_active_channel`).
|
||||||
@@ -1285,18 +1357,22 @@ export default class Client {
|
|||||||
end(this.handleResponse.bind(this, 'getChannelCounts', success, error));
|
end(this.handleResponse.bind(this, 'getChannelCounts', success, error));
|
||||||
}
|
}
|
||||||
|
|
||||||
getChannelExtraInfo(channelId, memberLimit, success, error) {
|
getChannelStats(channelId, success, error) {
|
||||||
var url = `${this.getChannelNeededRoute(channelId)}/extra_info`;
|
|
||||||
if (memberLimit) {
|
|
||||||
url += '/' + memberLimit;
|
|
||||||
}
|
|
||||||
|
|
||||||
request.
|
request.
|
||||||
get(url).
|
get(`${this.getChannelNeededRoute(channelId)}/stats`).
|
||||||
set(this.defaultHeaders).
|
set(this.defaultHeaders).
|
||||||
type('application/json').
|
type('application/json').
|
||||||
accept('application/json').
|
accept('application/json').
|
||||||
end(this.handleResponse.bind(this, 'getChannelExtraInfo', success, error));
|
end(this.handleResponse.bind(this, 'getChannelStats', success, error));
|
||||||
|
}
|
||||||
|
|
||||||
|
getChannelMember(channelId, userId, success, error) {
|
||||||
|
request.
|
||||||
|
get(`${this.getChannelNeededRoute(channelId)}/members/${userId}`).
|
||||||
|
set(this.defaultHeaders).
|
||||||
|
type('application/json').
|
||||||
|
accept('application/json').
|
||||||
|
end(this.handleResponse.bind(this, 'getChannelMember', success, error));
|
||||||
}
|
}
|
||||||
|
|
||||||
addChannelMember(channelId, userId, success, error) {
|
addChannelMember(channelId, userId, success, error) {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export default class WebSocketClient {
|
|||||||
this.connectFailCount = 0;
|
this.connectFailCount = 0;
|
||||||
this.eventCallback = null;
|
this.eventCallback = null;
|
||||||
this.responseCallbacks = {};
|
this.responseCallbacks = {};
|
||||||
|
this.firstConnectCallback = null;
|
||||||
this.reconnectCallback = null;
|
this.reconnectCallback = null;
|
||||||
this.errorCallback = null;
|
this.errorCallback = null;
|
||||||
this.closeCallback = null;
|
this.closeCallback = null;
|
||||||
@@ -29,12 +30,13 @@ export default class WebSocketClient {
|
|||||||
this.conn = new WebSocket(connectionUrl);
|
this.conn = new WebSocket(connectionUrl);
|
||||||
|
|
||||||
this.conn.onopen = () => {
|
this.conn.onopen = () => {
|
||||||
if (this.reconnectCallback) {
|
|
||||||
this.reconnectCallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.connectFailCount > 0) {
|
if (this.connectFailCount > 0) {
|
||||||
console.log('websocket re-established connection'); //eslint-disable-line no-console
|
console.log('websocket re-established connection'); //eslint-disable-line no-console
|
||||||
|
if (this.reconnectCallback) {
|
||||||
|
this.reconnectCallback();
|
||||||
|
}
|
||||||
|
} else if (this.firstConnectCallback) {
|
||||||
|
this.firstConnectCallback();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.connectFailCount = 0;
|
this.connectFailCount = 0;
|
||||||
@@ -104,6 +106,10 @@ export default class WebSocketClient {
|
|||||||
this.eventCallback = callback;
|
this.eventCallback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setFirstConnectCallback(callback) {
|
||||||
|
this.firstConnectCallback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
setReconnectCallback(callback) {
|
setReconnectCallback(callback) {
|
||||||
this.reconnectCallback = callback;
|
this.reconnectCallback = callback;
|
||||||
}
|
}
|
||||||
@@ -157,4 +163,10 @@ export default class WebSocketClient {
|
|||||||
getStatuses(callback) {
|
getStatuses(callback) {
|
||||||
this.sendMessage('get_statuses', null, callback);
|
this.sendMessage('get_statuses', null, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getStatusesByIds(userIds, callback) {
|
||||||
|
const data = {};
|
||||||
|
data.user_ids = userIds;
|
||||||
|
this.sendMessage('get_statuses_by_ids', data, callback);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export default class AdminNavbarDropdown extends React.Component {
|
|||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
teams: TeamStore.getAll(),
|
teams: TeamStore.getAll(),
|
||||||
teamMembers: TeamStore.getTeamMembers()
|
teamMembers: TeamStore.getMyTeamMembers()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ export default class AdminNavbarDropdown extends React.Component {
|
|||||||
onTeamChange() {
|
onTeamChange() {
|
||||||
this.setState({
|
this.setState({
|
||||||
teams: TeamStore.getAll(),
|
teams: TeamStore.getAll(),
|
||||||
teamMembers: TeamStore.getTeamMembers()
|
teamMembers: TeamStore.getMyTeamMembers()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ import UserStore from 'stores/user_store.jsx';
|
|||||||
import ConfirmModal from '../confirm_modal.jsx';
|
import ConfirmModal from '../confirm_modal.jsx';
|
||||||
import TeamStore from 'stores/team_store.jsx';
|
import TeamStore from 'stores/team_store.jsx';
|
||||||
|
|
||||||
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
export default class UserItem extends React.Component {
|
export default class AdminTeamMembersDropdown extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ export default class UserItem extends React.Component {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
Client.updateTeamMemberRoles(
|
Client.updateTeamMemberRoles(
|
||||||
this.props.team.id,
|
this.props.teamMember.team_id,
|
||||||
this.props.user.id,
|
this.props.user.id,
|
||||||
'team_user',
|
'team_user',
|
||||||
() => {
|
() => {
|
||||||
@@ -74,7 +74,7 @@ export default class UserItem extends React.Component {
|
|||||||
|
|
||||||
handleRemoveFromTeam() {
|
handleRemoveFromTeam() {
|
||||||
Client.removeUserFromTeam(
|
Client.removeUserFromTeam(
|
||||||
this.props.team.id,
|
this.props.teamMember.team_id,
|
||||||
this.props.user.id,
|
this.props.user.id,
|
||||||
() => {
|
() => {
|
||||||
this.props.refreshProfiles();
|
this.props.refreshProfiles();
|
||||||
@@ -111,7 +111,7 @@ export default class UserItem extends React.Component {
|
|||||||
|
|
||||||
doMakeTeamAdmin() {
|
doMakeTeamAdmin() {
|
||||||
Client.updateTeamMemberRoles(
|
Client.updateTeamMemberRoles(
|
||||||
this.props.team.id,
|
this.props.teamMember.team_id,
|
||||||
this.props.user.id,
|
this.props.user.id,
|
||||||
'team_user team_admin',
|
'team_user team_admin',
|
||||||
() => {
|
() => {
|
||||||
@@ -241,7 +241,6 @@ export default class UserItem extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const me = UserStore.getCurrentUser();
|
const me = UserStore.getCurrentUser();
|
||||||
const email = user.email;
|
|
||||||
let showMakeMember = Utils.isAdmin(teamMember.roles) || Utils.isSystemAdmin(user.roles);
|
let showMakeMember = Utils.isAdmin(teamMember.roles) || Utils.isSystemAdmin(user.roles);
|
||||||
let showMakeAdmin = !Utils.isAdmin(teamMember.roles) && !Utils.isSystemAdmin(user.roles);
|
let showMakeAdmin = !Utils.isAdmin(teamMember.roles) && !Utils.isSystemAdmin(user.roles);
|
||||||
let showMakeSystemAdmin = !Utils.isSystemAdmin(user.roles);
|
let showMakeSystemAdmin = !Utils.isSystemAdmin(user.roles);
|
||||||
@@ -406,39 +405,8 @@ export default class UserItem extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mfaActiveText;
|
|
||||||
if (mfaEnabled) {
|
|
||||||
if (user.mfa_active) {
|
|
||||||
mfaActiveText = (
|
|
||||||
<FormattedHTMLMessage
|
|
||||||
id='admin.user_item.mfaYes'
|
|
||||||
defaultMessage=', <strong>MFA</strong>: Yes'
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
mfaActiveText = (
|
|
||||||
<FormattedHTMLMessage
|
|
||||||
id='admin.user_item.mfaNo'
|
|
||||||
defaultMessage=', <strong>MFA</strong>: No'
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let authServiceText;
|
|
||||||
let passwordReset;
|
let passwordReset;
|
||||||
if (user.auth_service) {
|
if (user.auth_service) {
|
||||||
const service = (user.auth_service === Constants.LDAP_SERVICE || user.auth_service === Constants.SAML_SERVICE) ? user.auth_service.toUpperCase() : Utils.toTitleCase(user.auth_service);
|
|
||||||
authServiceText = (
|
|
||||||
<FormattedHTMLMessage
|
|
||||||
id='admin.user_item.authServiceNotEmail'
|
|
||||||
defaultMessage=', <strong>Sign-in Method:</strong> {service}'
|
|
||||||
values={{
|
|
||||||
service
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
passwordReset = (
|
passwordReset = (
|
||||||
<li role='presentation'>
|
<li role='presentation'>
|
||||||
<a
|
<a
|
||||||
@@ -454,13 +422,6 @@ export default class UserItem extends React.Component {
|
|||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
authServiceText = (
|
|
||||||
<FormattedHTMLMessage
|
|
||||||
id='admin.user_item.authServiceEmail'
|
|
||||||
defaultMessage=', <strong>Sign-in Method:</strong> Email'
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
passwordReset = (
|
passwordReset = (
|
||||||
<li role='presentation'>
|
<li role='presentation'>
|
||||||
<a
|
<a
|
||||||
@@ -531,63 +492,38 @@ export default class UserItem extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='more-modal__row'>
|
<div className='dropdown member-drop'>
|
||||||
<img
|
<a
|
||||||
className='more-modal__image pull-left'
|
href='#'
|
||||||
src={`${Client.getUsersRoute()}/${user.id}/image?time=${user.update_at}`}
|
className='dropdown-toggle theme'
|
||||||
height='36'
|
type='button'
|
||||||
width='36'
|
data-toggle='dropdown'
|
||||||
/>
|
aria-expanded='true'
|
||||||
<div className='more-modal__details'>
|
>
|
||||||
<div className='more-modal__name'>{displayedName}</div>
|
<span>{currentRoles} </span>
|
||||||
<div className='more-modal__description'>
|
<span className='caret'/>
|
||||||
<FormattedHTMLMessage
|
</a>
|
||||||
id='admin.user_item.emailTitle'
|
<ul
|
||||||
defaultMessage='<strong>Email:</strong> {email}'
|
className='dropdown-menu member-menu'
|
||||||
values={{
|
role='menu'
|
||||||
email
|
>
|
||||||
}}
|
{removeFromTeam}
|
||||||
/>
|
{makeAdmin}
|
||||||
{authServiceText}
|
{makeMember}
|
||||||
{mfaActiveText}
|
{makeActive}
|
||||||
</div>
|
{makeNotActive}
|
||||||
{serverError}
|
{makeSystemAdmin}
|
||||||
</div>
|
{mfaReset}
|
||||||
<div className='more-modal__actions'>
|
{passwordReset}
|
||||||
<div className='dropdown member-drop'>
|
</ul>
|
||||||
<a
|
|
||||||
href='#'
|
|
||||||
className='dropdown-toggle theme'
|
|
||||||
type='button'
|
|
||||||
data-toggle='dropdown'
|
|
||||||
aria-expanded='true'
|
|
||||||
>
|
|
||||||
<span>{currentRoles} </span>
|
|
||||||
<span className='caret'/>
|
|
||||||
</a>
|
|
||||||
<ul
|
|
||||||
className='dropdown-menu member-menu'
|
|
||||||
role='menu'
|
|
||||||
>
|
|
||||||
{removeFromTeam}
|
|
||||||
{makeAdmin}
|
|
||||||
{makeMember}
|
|
||||||
{makeActive}
|
|
||||||
{makeNotActive}
|
|
||||||
{makeSystemAdmin}
|
|
||||||
{mfaReset}
|
|
||||||
{passwordReset}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{makeDemoteModal}
|
{makeDemoteModal}
|
||||||
|
{serverError}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UserItem.propTypes = {
|
AdminTeamMembersDropdown.propTypes = {
|
||||||
team: React.PropTypes.object.isRequired,
|
|
||||||
user: React.PropTypes.object.isRequired,
|
user: React.PropTypes.object.isRequired,
|
||||||
teamMember: React.PropTypes.object.isRequired,
|
teamMember: React.PropTypes.object.isRequired,
|
||||||
refreshProfiles: React.PropTypes.func.isRequired,
|
refreshProfiles: React.PropTypes.func.isRequired,
|
||||||
@@ -1,16 +1,25 @@
|
|||||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
import AdminStore from 'stores/admin_store.jsx';
|
import SearchableUserList from 'components/searchable_user_list.jsx';
|
||||||
import Client from 'client/web_client.jsx';
|
import AdminTeamMembersDropdown from './admin_team_members_dropdown.jsx';
|
||||||
import FormError from 'components/form_error.jsx';
|
|
||||||
import LoadingScreen from '../loading_screen.jsx';
|
|
||||||
import UserItem from './user_item.jsx';
|
|
||||||
import ResetPasswordModal from './reset_password_modal.jsx';
|
import ResetPasswordModal from './reset_password_modal.jsx';
|
||||||
|
import FormError from 'components/form_error.jsx';
|
||||||
|
|
||||||
import {FormattedMessage} from 'react-intl';
|
import AdminStore from 'stores/admin_store.jsx';
|
||||||
|
import TeamStore from 'stores/team_store.jsx';
|
||||||
|
import UserStore from 'stores/user_store.jsx';
|
||||||
|
|
||||||
|
import {searchUsers, loadProfilesAndTeamMembers, loadTeamMembersForProfilesList} from 'actions/user_actions.jsx';
|
||||||
|
import {getTeamStats} from 'utils/async_client.jsx';
|
||||||
|
|
||||||
|
import Constants from 'utils/constants.jsx';
|
||||||
|
import * as Utils from 'utils/utils.jsx';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
|
||||||
|
|
||||||
|
const USERS_PER_PAGE = 50;
|
||||||
|
|
||||||
export default class UserList extends React.Component {
|
export default class UserList extends React.Component {
|
||||||
static get propTypes() {
|
static get propTypes() {
|
||||||
@@ -23,34 +32,49 @@ export default class UserList extends React.Component {
|
|||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.onAllTeamsChange = this.onAllTeamsChange.bind(this);
|
this.onAllTeamsChange = this.onAllTeamsChange.bind(this);
|
||||||
|
this.onStatsChange = this.onStatsChange.bind(this);
|
||||||
|
this.onUsersChange = this.onUsersChange.bind(this);
|
||||||
|
this.onTeamChange = this.onTeamChange.bind(this);
|
||||||
|
|
||||||
this.getTeamProfiles = this.getTeamProfiles.bind(this);
|
|
||||||
this.getCurrentTeamProfiles = this.getCurrentTeamProfiles.bind(this);
|
|
||||||
this.doPasswordReset = this.doPasswordReset.bind(this);
|
this.doPasswordReset = this.doPasswordReset.bind(this);
|
||||||
this.doPasswordResetDismiss = this.doPasswordResetDismiss.bind(this);
|
this.doPasswordResetDismiss = this.doPasswordResetDismiss.bind(this);
|
||||||
this.doPasswordResetSubmit = this.doPasswordResetSubmit.bind(this);
|
this.doPasswordResetSubmit = this.doPasswordResetSubmit.bind(this);
|
||||||
this.getTeamMemberForUser = this.getTeamMemberForUser.bind(this);
|
this.nextPage = this.nextPage.bind(this);
|
||||||
|
this.search = this.search.bind(this);
|
||||||
|
this.loadComplete = this.loadComplete.bind(this);
|
||||||
|
|
||||||
|
const stats = TeamStore.getStats(this.props.params.team);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
team: AdminStore.getTeam(this.props.params.team),
|
team: AdminStore.getTeam(this.props.params.team),
|
||||||
users: null,
|
users: [],
|
||||||
teamMembers: null,
|
teamMembers: TeamStore.getMembersInTeam(this.props.params.team),
|
||||||
|
total: stats.member_count,
|
||||||
serverError: null,
|
serverError: null,
|
||||||
showPasswordModal: false,
|
showPasswordModal: false,
|
||||||
|
loading: true,
|
||||||
user: null
|
user: null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.getCurrentTeamProfiles();
|
|
||||||
|
|
||||||
AdminStore.addAllTeamsChangeListener(this.onAllTeamsChange);
|
AdminStore.addAllTeamsChangeListener(this.onAllTeamsChange);
|
||||||
|
UserStore.addInTeamChangeListener(this.onUsersChange);
|
||||||
|
TeamStore.addChangeListener(this.onTeamChange);
|
||||||
|
TeamStore.addStatsChangeListener(this.onStatsChange);
|
||||||
|
|
||||||
|
loadProfilesAndTeamMembers(0, Constants.PROFILE_CHUNK_SIZE, this.props.params.team, this.loadComplete);
|
||||||
|
getTeamStats(this.props.params.team);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
if (nextProps.params.team !== this.props.params.team) {
|
if (nextProps.params.team !== this.props.params.team) {
|
||||||
|
const stats = TeamStore.getStats(nextProps.params.team);
|
||||||
this.setState({
|
this.setState({
|
||||||
team: AdminStore.getTeam(nextProps.params.team)
|
team: AdminStore.getTeam(nextProps.params.team),
|
||||||
|
users: [],
|
||||||
|
teamMembers: TeamStore.getMembersInTeam(nextProps.params.team),
|
||||||
|
total: stats.member_count
|
||||||
});
|
});
|
||||||
|
|
||||||
this.getTeamProfiles(nextProps.params.team);
|
this.getTeamProfiles(nextProps.params.team);
|
||||||
@@ -59,6 +83,13 @@ export default class UserList extends React.Component {
|
|||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
AdminStore.removeAllTeamsChangeListener(this.onAllTeamsChange);
|
AdminStore.removeAllTeamsChangeListener(this.onAllTeamsChange);
|
||||||
|
UserStore.removeInTeamChangeListener(this.onUsersChange);
|
||||||
|
TeamStore.removeChangeListener(this.onTeamChange);
|
||||||
|
TeamStore.removeStatsChangeListener(this.onStatsChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadComplete() {
|
||||||
|
this.setState({loading: false});
|
||||||
}
|
}
|
||||||
|
|
||||||
onAllTeamsChange() {
|
onAllTeamsChange() {
|
||||||
@@ -67,59 +98,21 @@ export default class UserList extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrentTeamProfiles() {
|
onStatsChange() {
|
||||||
this.getTeamProfiles(this.props.params.team);
|
const stats = TeamStore.getStats(this.props.params.team);
|
||||||
|
this.setState({total: stats.member_count});
|
||||||
}
|
}
|
||||||
|
|
||||||
getTeamProfiles(teamId) {
|
onUsersChange() {
|
||||||
Client.getTeamMembers(
|
this.setState({users: UserStore.getProfileListInTeam(this.props.params.team)});
|
||||||
teamId,
|
}
|
||||||
(data) => {
|
|
||||||
this.setState({
|
|
||||||
teamMembers: data
|
|
||||||
});
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
this.setState({
|
|
||||||
teamMembers: null,
|
|
||||||
serverError: err.message
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
Client.getProfilesForTeam(
|
onTeamChange() {
|
||||||
teamId,
|
this.setState({teamMembers: TeamStore.getMembersInTeam(this.props.params.team)});
|
||||||
(users) => {
|
}
|
||||||
var memberList = [];
|
|
||||||
for (var id in users) {
|
|
||||||
if (users.hasOwnProperty(id)) {
|
|
||||||
memberList.push(users[id]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
memberList.sort((a, b) => {
|
nextPage(page) {
|
||||||
if (a.username < b.username) {
|
loadProfilesAndTeamMembers((page + 1) * USERS_PER_PAGE, USERS_PER_PAGE, this.props.params.team);
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (a.username > b.username) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
users: memberList
|
|
||||||
});
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
this.setState({
|
|
||||||
users: null,
|
|
||||||
serverError: err.message
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
doPasswordReset(user) {
|
doPasswordReset(user) {
|
||||||
@@ -144,20 +137,21 @@ export default class UserList extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getTeamMemberForUser(userId) {
|
search(term) {
|
||||||
if (this.state.teamMembers) {
|
if (term === '') {
|
||||||
for (const index in this.state.teamMembers) {
|
this.setState({search: false, users: UserStore.getProfileListInTeam(this.props.params.team)});
|
||||||
if (this.state.teamMembers.hasOwnProperty(index)) {
|
return;
|
||||||
var teamMember = this.state.teamMembers[index];
|
|
||||||
|
|
||||||
if (teamMember.user_id === userId) {
|
|
||||||
return teamMember;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
searchUsers(
|
||||||
|
term,
|
||||||
|
this.props.params.team,
|
||||||
|
{},
|
||||||
|
(users) => {
|
||||||
|
this.setState({loading: true, search: true, users});
|
||||||
|
loadTeamMembersForProfilesList(users, this.props.params.team, this.loadComplete);
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@@ -165,41 +159,71 @@ export default class UserList extends React.Component {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.users == null || this.state.teamMembers == null) {
|
const teamMembers = this.state.teamMembers;
|
||||||
return (
|
const users = this.state.users;
|
||||||
<div className='wrapper--fixed'>
|
const actionUserProps = {};
|
||||||
<h3>
|
const extraInfo = {};
|
||||||
<FormattedMessage
|
const mfaEnabled = global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.MFA === 'true' && global.window.mm_config.EnableMultifactorAuthentication === 'true';
|
||||||
id='admin.userList.title'
|
|
||||||
defaultMessage='Users for {team}'
|
|
||||||
values={{
|
|
||||||
team: this.state.team.name
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</h3>
|
|
||||||
<FormError error={this.state.serverError}/>
|
|
||||||
<LoadingScreen/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
var memberList = this.state.users.map((user) => {
|
let usersToDisplay;
|
||||||
var teamMember = this.getTeamMemberForUser(user.id);
|
if (this.state.loading) {
|
||||||
|
usersToDisplay = null;
|
||||||
|
} else {
|
||||||
|
usersToDisplay = [];
|
||||||
|
|
||||||
if (!teamMember || teamMember.delete_at > 0) {
|
for (let i = 0; i < users.length; i++) {
|
||||||
return null;
|
const user = users[i];
|
||||||
|
|
||||||
|
if (teamMembers[user.id]) {
|
||||||
|
usersToDisplay.push(user);
|
||||||
|
actionUserProps[user.id] = {
|
||||||
|
teamMember: teamMembers[user.id]
|
||||||
|
};
|
||||||
|
|
||||||
|
const info = [];
|
||||||
|
|
||||||
|
if (user.auth_service) {
|
||||||
|
const service = (user.auth_service === Constants.LDAP_SERVICE || user.auth_service === Constants.SAML_SERVICE) ? user.auth_service.toUpperCase() : Utils.toTitleCase(user.auth_service);
|
||||||
|
info.push(
|
||||||
|
<FormattedHTMLMessage
|
||||||
|
id='admin.user_item.authServiceNotEmail'
|
||||||
|
defaultMessage='<strong>Sign-in Method:</strong> {service}'
|
||||||
|
values={{
|
||||||
|
service
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
info.push(
|
||||||
|
<FormattedHTMLMessage
|
||||||
|
id='admin.user_item.authServiceEmail'
|
||||||
|
defaultMessage='<strong>Sign-in Method:</strong> Email'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mfaEnabled) {
|
||||||
|
if (user.mfa_active) {
|
||||||
|
info.push(
|
||||||
|
<FormattedHTMLMessage
|
||||||
|
id='admin.user_item.mfaYes'
|
||||||
|
defaultMessage='<strong>MFA</strong>: Yes'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
info.push(
|
||||||
|
<FormattedHTMLMessage
|
||||||
|
id='admin.user_item.mfaNo'
|
||||||
|
defaultMessage='<strong>MFA</strong>: No'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extraInfo[user.id] = info;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return (
|
|
||||||
<UserItem
|
|
||||||
team={this.state.team}
|
|
||||||
key={'user_' + user.id}
|
|
||||||
user={user}
|
|
||||||
teamMember={teamMember}
|
|
||||||
refreshProfiles={this.getCurrentTeamProfiles}
|
|
||||||
doPasswordReset={this.doPasswordReset}
|
|
||||||
/>);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='wrapper--fixed'>
|
<div className='wrapper--fixed'>
|
||||||
@@ -209,7 +233,7 @@ export default class UserList extends React.Component {
|
|||||||
defaultMessage='Users for {team} ({count})'
|
defaultMessage='Users for {team} ({count})'
|
||||||
values={{
|
values={{
|
||||||
team: this.state.team.name,
|
team: this.state.team.name,
|
||||||
count: this.state.users.length
|
count: this.state.total
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</h3>
|
</h3>
|
||||||
@@ -219,7 +243,20 @@ export default class UserList extends React.Component {
|
|||||||
role='form'
|
role='form'
|
||||||
>
|
>
|
||||||
<div className='more-modal__list member-list-holder'>
|
<div className='more-modal__list member-list-holder'>
|
||||||
{memberList}
|
<SearchableUserList
|
||||||
|
users={usersToDisplay}
|
||||||
|
usersPerPage={USERS_PER_PAGE}
|
||||||
|
total={this.state.total}
|
||||||
|
extraInfo={extraInfo}
|
||||||
|
nextPage={this.nextPage}
|
||||||
|
search={this.search}
|
||||||
|
actions={[AdminTeamMembersDropdown]}
|
||||||
|
actionProps={{
|
||||||
|
refreshProfiles: this.getCurrentTeamProfiles,
|
||||||
|
doPasswordReset: this.doPasswordReset
|
||||||
|
}}
|
||||||
|
actionUserProps={actionUserProps}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<ResetPasswordModal
|
<ResetPasswordModal
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ class SystemAnalytics extends React.Component {
|
|||||||
const stats = this.state.stats;
|
const stats = this.state.stats;
|
||||||
|
|
||||||
let advancedCounts;
|
let advancedCounts;
|
||||||
|
let advancedStats;
|
||||||
let advancedGraphs;
|
let advancedGraphs;
|
||||||
let banner;
|
let banner;
|
||||||
if (global.window.mm_license.IsLicensed === 'true') {
|
if (global.window.mm_license.IsLicensed === 'true') {
|
||||||
@@ -130,6 +131,41 @@ class SystemAnalytics extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
advancedStats = (
|
||||||
|
<div className='row'>
|
||||||
|
<StatisticCount
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='analytics.system.totalWebsockets'
|
||||||
|
defaultMessage='Websocket Conns'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
icon='fa-user'
|
||||||
|
count={stats[StatTypes.TOTAL_WEBSOCKET_CONNECTIONS]}
|
||||||
|
/>
|
||||||
|
<StatisticCount
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='analytics.system.totalMasterDbConnections'
|
||||||
|
defaultMessage='Master DB Conns'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
icon='fa-terminal'
|
||||||
|
count={stats[StatTypes.TOTAL_MASTER_DB_CONNECTIONS]}
|
||||||
|
/>
|
||||||
|
<StatisticCount
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='analytics.system.totalReadDbConnections'
|
||||||
|
defaultMessage='Replica DB Conns'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
icon='fa-terminal'
|
||||||
|
count={stats[StatTypes.TOTAL_READ_DB_CONNECTIONS]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
const channelTypeData = formatChannelDoughtnutData(stats[StatTypes.TOTAL_PUBLIC_CHANNELS], stats[StatTypes.TOTAL_PRIVATE_GROUPS], this.props.intl);
|
const channelTypeData = formatChannelDoughtnutData(stats[StatTypes.TOTAL_PUBLIC_CHANNELS], stats[StatTypes.TOTAL_PRIVATE_GROUPS], this.props.intl);
|
||||||
const postTypeData = formatPostDoughtnutData(stats[StatTypes.TOTAL_FILE_POSTS], stats[StatTypes.TOTAL_HASHTAG_POSTS], stats[StatTypes.TOTAL_POSTS], this.props.intl);
|
const postTypeData = formatPostDoughtnutData(stats[StatTypes.TOTAL_FILE_POSTS], stats[StatTypes.TOTAL_HASHTAG_POSTS], stats[StatTypes.TOTAL_POSTS], this.props.intl);
|
||||||
|
|
||||||
@@ -246,6 +282,7 @@ class SystemAnalytics extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{advancedCounts}
|
{advancedCounts}
|
||||||
|
{advancedStats}
|
||||||
{advancedGraphs}
|
{advancedGraphs}
|
||||||
<div className='row'>
|
<div className='row'>
|
||||||
<LineChart
|
<LineChart
|
||||||
|
|||||||
@@ -63,13 +63,15 @@ export default class ChannelHeader extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getStateFromStores() {
|
getStateFromStores() {
|
||||||
const extraInfo = ChannelStore.getExtraInfo(this.props.channelId);
|
const stats = ChannelStore.getStats(this.props.channelId);
|
||||||
|
|
||||||
|
const users = UserStore.getProfileListInChannel(this.props.channelId);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
channel: ChannelStore.get(this.props.channelId),
|
channel: ChannelStore.get(this.props.channelId),
|
||||||
memberChannel: ChannelStore.getMember(this.props.channelId),
|
memberChannel: ChannelStore.getMyMember(this.props.channelId),
|
||||||
users: extraInfo.members,
|
users,
|
||||||
userCount: extraInfo.member_count,
|
userCount: stats.member_count,
|
||||||
currentUser: UserStore.getCurrentUser(),
|
currentUser: UserStore.getCurrentUser(),
|
||||||
enableFormatting: PreferenceStore.getBool(Preferences.CATEGORY_ADVANCED_SETTINGS, 'formatting', true),
|
enableFormatting: PreferenceStore.getBool(Preferences.CATEGORY_ADVANCED_SETTINGS, 'formatting', true),
|
||||||
isBusy: WebrtcStore.isBusy()
|
isBusy: WebrtcStore.isBusy()
|
||||||
@@ -89,10 +91,10 @@ export default class ChannelHeader extends React.Component {
|
|||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
ChannelStore.addChangeListener(this.onListenerChange);
|
ChannelStore.addChangeListener(this.onListenerChange);
|
||||||
ChannelStore.addExtraInfoChangeListener(this.onListenerChange);
|
ChannelStore.addStatsChangeListener(this.onListenerChange);
|
||||||
SearchStore.addSearchChangeListener(this.onListenerChange);
|
SearchStore.addSearchChangeListener(this.onListenerChange);
|
||||||
PreferenceStore.addChangeListener(this.onListenerChange);
|
PreferenceStore.addChangeListener(this.onListenerChange);
|
||||||
UserStore.addChangeListener(this.onListenerChange);
|
UserStore.addInChannelChangeListener(this.onListenerChange);
|
||||||
UserStore.addStatusesChangeListener(this.onListenerChange);
|
UserStore.addStatusesChangeListener(this.onListenerChange);
|
||||||
WebrtcStore.addChangedListener(this.onListenerChange);
|
WebrtcStore.addChangedListener(this.onListenerChange);
|
||||||
WebrtcStore.addBusyListener(this.onBusy);
|
WebrtcStore.addBusyListener(this.onBusy);
|
||||||
@@ -102,10 +104,10 @@ export default class ChannelHeader extends React.Component {
|
|||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
ChannelStore.removeChangeListener(this.onListenerChange);
|
ChannelStore.removeChangeListener(this.onListenerChange);
|
||||||
ChannelStore.removeExtraInfoChangeListener(this.onListenerChange);
|
ChannelStore.removeStatsChangeListener(this.onListenerChange);
|
||||||
SearchStore.removeSearchChangeListener(this.onListenerChange);
|
SearchStore.removeSearchChangeListener(this.onListenerChange);
|
||||||
PreferenceStore.removeChangeListener(this.onListenerChange);
|
PreferenceStore.removeChangeListener(this.onListenerChange);
|
||||||
UserStore.removeChangeListener(this.onListenerChange);
|
UserStore.removeInChannelChangeListener(this.onListenerChange);
|
||||||
UserStore.removeStatusesChangeListener(this.onListenerChange);
|
UserStore.removeStatusesChangeListener(this.onListenerChange);
|
||||||
WebrtcStore.removeChangedListener(this.onListenerChange);
|
WebrtcStore.removeChangedListener(this.onListenerChange);
|
||||||
WebrtcStore.removeBusyListener(this.onBusy);
|
WebrtcStore.removeBusyListener(this.onBusy);
|
||||||
@@ -117,10 +119,7 @@ export default class ChannelHeader extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onListenerChange() {
|
onListenerChange() {
|
||||||
const newState = this.getStateFromStores();
|
this.setState(this.getStateFromStores());
|
||||||
if (!Utils.areObjectsEqual(newState, this.state)) {
|
|
||||||
this.setState(newState);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLeave() {
|
handleLeave() {
|
||||||
@@ -265,7 +264,6 @@ export default class ChannelHeader extends React.Component {
|
|||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
let channelTitle = channel.display_name;
|
let channelTitle = channel.display_name;
|
||||||
const currentId = this.state.currentUser.id;
|
|
||||||
const isAdmin = TeamStore.isTeamAdminForCurrentTeam() || UserStore.isSystemAdminForCurrentUser();
|
const isAdmin = TeamStore.isTeamAdminForCurrentTeam() || UserStore.isSystemAdminForCurrentUser();
|
||||||
const isSystemAdmin = UserStore.isSystemAdminForCurrentUser();
|
const isSystemAdmin = UserStore.isSystemAdminForCurrentUser();
|
||||||
const isDirect = (this.state.channel.type === 'D');
|
const isDirect = (this.state.channel.type === 'D');
|
||||||
@@ -273,13 +271,8 @@ export default class ChannelHeader extends React.Component {
|
|||||||
|
|
||||||
if (isDirect) {
|
if (isDirect) {
|
||||||
const userMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
|
const userMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
|
||||||
let contact;
|
const contact = this.state.users[0];
|
||||||
if (this.state.users.length > 1) {
|
if (contact) {
|
||||||
if (this.state.users[0].id === currentId) {
|
|
||||||
contact = this.state.users[1];
|
|
||||||
} else {
|
|
||||||
contact = this.state.users[0];
|
|
||||||
}
|
|
||||||
channelTitle = Utils.displayUsername(contact.id);
|
channelTitle = Utils.displayUsername(contact.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import * as AsyncClient from 'utils/async_client.jsx';
|
|
||||||
import Client from 'client/web_client.jsx';
|
|
||||||
|
|
||||||
import {FormattedMessage} from 'react-intl';
|
|
||||||
import SpinnerButton from 'components/spinner_button.jsx';
|
import SpinnerButton from 'components/spinner_button.jsx';
|
||||||
|
|
||||||
|
import {addUserToChannel} from 'actions/channel_actions.jsx';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
export default class ChannelInviteButton extends React.Component {
|
export default class ChannelInviteButton extends React.Component {
|
||||||
static get propTypes() {
|
static get propTypes() {
|
||||||
return {
|
return {
|
||||||
@@ -37,7 +36,7 @@ export default class ChannelInviteButton extends React.Component {
|
|||||||
addingUser: true
|
addingUser: true
|
||||||
});
|
});
|
||||||
|
|
||||||
Client.addChannelMember(
|
addUserToChannel(
|
||||||
this.props.channel.id,
|
this.props.channel.id,
|
||||||
this.props.user.id,
|
this.props.user.id,
|
||||||
() => {
|
() => {
|
||||||
@@ -46,7 +45,6 @@ export default class ChannelInviteButton extends React.Component {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.props.onInviteError(null);
|
this.props.onInviteError(null);
|
||||||
AsyncClient.getChannelExtraInfo();
|
|
||||||
},
|
},
|
||||||
(err) => {
|
(err) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
|||||||
@@ -1,124 +1,85 @@
|
|||||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
import $ from 'jquery';
|
|
||||||
import ChannelInviteButton from './channel_invite_button.jsx';
|
import ChannelInviteButton from './channel_invite_button.jsx';
|
||||||
import FilteredUserList from './filtered_user_list.jsx';
|
import SearchableUserList from './searchable_user_list.jsx';
|
||||||
import LoadingScreen from './loading_screen.jsx';
|
import LoadingScreen from './loading_screen.jsx';
|
||||||
|
|
||||||
import ChannelStore from 'stores/channel_store.jsx';
|
import ChannelStore from 'stores/channel_store.jsx';
|
||||||
import UserStore from 'stores/user_store.jsx';
|
import UserStore from 'stores/user_store.jsx';
|
||||||
|
import TeamStore from 'stores/team_store.jsx';
|
||||||
|
|
||||||
|
import {searchUsers} from 'actions/user_actions.jsx';
|
||||||
|
|
||||||
import * as Utils from 'utils/utils.jsx';
|
import * as Utils from 'utils/utils.jsx';
|
||||||
import * as AsyncClient from 'utils/async_client.jsx';
|
import * as AsyncClient from 'utils/async_client.jsx';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {Modal} from 'react-bootstrap';
|
||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
import {Modal} from 'react-bootstrap';
|
const USERS_PER_PAGE = 50;
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
export default class ChannelInviteModal extends React.Component {
|
export default class ChannelInviteModal extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.onListenerChange = this.onListenerChange.bind(this);
|
this.onChange = this.onChange.bind(this);
|
||||||
this.getStateFromStores = this.getStateFromStores.bind(this);
|
|
||||||
this.handleInviteError = this.handleInviteError.bind(this);
|
this.handleInviteError = this.handleInviteError.bind(this);
|
||||||
|
this.nextPage = this.nextPage.bind(this);
|
||||||
|
this.search = this.search.bind(this);
|
||||||
|
|
||||||
this.state = this.getStateFromStores();
|
this.term = '';
|
||||||
}
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
|
||||||
if (!this.props.show && !nextProps.show) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Utils.areObjectsEqual(this.props, nextProps)) {
|
const channelStats = ChannelStore.getStats(props.channel.id);
|
||||||
return true;
|
const teamStats = TeamStore.getCurrentStats();
|
||||||
}
|
|
||||||
|
|
||||||
if (!Utils.areObjectsEqual(this.state, nextState)) {
|
this.state = {
|
||||||
return true;
|
users: [],
|
||||||
}
|
total: teamStats.member_count - channelStats.member_count,
|
||||||
|
search: false
|
||||||
return false;
|
|
||||||
}
|
|
||||||
getStateFromStores() {
|
|
||||||
const users = UserStore.getActiveOnlyProfiles();
|
|
||||||
|
|
||||||
if ($.isEmptyObject(users)) {
|
|
||||||
return {
|
|
||||||
loading: true
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure we have all members of this channel before rendering
|
|
||||||
const extraInfo = ChannelStore.getCurrentExtraInfo();
|
|
||||||
if (extraInfo.member_count !== extraInfo.members.length) {
|
|
||||||
AsyncClient.getChannelExtraInfo(this.props.channel.id, -1);
|
|
||||||
|
|
||||||
return {
|
|
||||||
loading: true
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentUser = UserStore.getCurrentUser();
|
|
||||||
if (!currentUser) {
|
|
||||||
return {
|
|
||||||
loading: true
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentMember = ChannelStore.getCurrentMember();
|
|
||||||
if (!currentMember) {
|
|
||||||
return {
|
|
||||||
loading: true
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const memberIds = extraInfo.members.map((user) => user.id);
|
|
||||||
|
|
||||||
var nonmembers = [];
|
|
||||||
for (var id in users) {
|
|
||||||
if (memberIds.indexOf(id) === -1) {
|
|
||||||
nonmembers.push(users[id]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nonmembers.sort((a, b) => {
|
|
||||||
return a.username.localeCompare(b.username);
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
nonmembers,
|
|
||||||
loading: false,
|
|
||||||
currentUser,
|
|
||||||
currentMember
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
if (!this.props.show && nextProps.show) {
|
if (!this.props.show && nextProps.show) {
|
||||||
ChannelStore.addExtraInfoChangeListener(this.onListenerChange);
|
TeamStore.addStatsChangeListener(this.onChange);
|
||||||
ChannelStore.addChangeListener(this.onListenerChange);
|
ChannelStore.addStatsChangeListener(this.onChange);
|
||||||
UserStore.addChangeListener(this.onListenerChange);
|
UserStore.addNotInChannelChangeListener(this.onChange);
|
||||||
this.onListenerChange();
|
UserStore.addStatusesChangeListener(this.onChange);
|
||||||
|
|
||||||
|
this.onChange();
|
||||||
|
AsyncClient.getProfilesNotInChannel(this.props.channel.id, 0);
|
||||||
|
AsyncClient.getTeamStats(TeamStore.getCurrentId());
|
||||||
} else if (this.props.show && !nextProps.show) {
|
} else if (this.props.show && !nextProps.show) {
|
||||||
ChannelStore.removeExtraInfoChangeListener(this.onListenerChange);
|
TeamStore.removeStatsChangeListener(this.onChange);
|
||||||
ChannelStore.removeChangeListener(this.onListenerChange);
|
ChannelStore.removeStatsChangeListener(this.onChange);
|
||||||
UserStore.removeChangeListener(this.onListenerChange);
|
UserStore.removeNotInChannelChangeListener(this.onChange);
|
||||||
|
UserStore.removeStatusesChangeListener(this.onChange);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
ChannelStore.removeExtraInfoChangeListener(this.onListenerChange);
|
ChannelStore.removeStatsChangeListener(this.onChange);
|
||||||
ChannelStore.removeChangeListener(this.onListenerChange);
|
ChannelStore.removeChangeListener(this.onChange);
|
||||||
UserStore.removeChangeListener(this.onListenerChange);
|
UserStore.removeNotInChannelChangeListener(this.onChange);
|
||||||
}
|
}
|
||||||
onListenerChange() {
|
|
||||||
var newState = this.getStateFromStores();
|
onChange() {
|
||||||
if (!Utils.areObjectsEqual(this.state, newState)) {
|
if (this.state.search) {
|
||||||
this.setState(newState);
|
this.search(this.term);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const channelStats = ChannelStore.getStats(this.props.channel.id);
|
||||||
|
const teamStats = TeamStore.getCurrentStats();
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
users: UserStore.getProfileListNotInChannel(this.props.channel.id),
|
||||||
|
total: teamStats.member_count - channelStats.member_count
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleInviteError(err) {
|
handleInviteError(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -130,6 +91,29 @@ export default class ChannelInviteModal extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nextPage(page) {
|
||||||
|
AsyncClient.getProfilesNotInChannel(this.props.channel.id, (page + 1) * USERS_PER_PAGE, USERS_PER_PAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
search(term) {
|
||||||
|
this.term = term;
|
||||||
|
|
||||||
|
if (term === '') {
|
||||||
|
this.setState({users: UserStore.getProfileListNotInChannel(), search: false});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
searchUsers(
|
||||||
|
term,
|
||||||
|
TeamStore.getCurrentId(),
|
||||||
|
{not_in_channel: this.props.channel.id},
|
||||||
|
(users) => {
|
||||||
|
this.setState({search: true, users});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
var inviteError = null;
|
var inviteError = null;
|
||||||
if (this.state.inviteError) {
|
if (this.state.inviteError) {
|
||||||
@@ -145,9 +129,13 @@ export default class ChannelInviteModal extends React.Component {
|
|||||||
maxHeight = Utils.windowHeight() - 300;
|
maxHeight = Utils.windowHeight() - 300;
|
||||||
}
|
}
|
||||||
content = (
|
content = (
|
||||||
<FilteredUserList
|
<SearchableUserList
|
||||||
style={{maxHeight}}
|
style={{maxHeight}}
|
||||||
users={this.state.nonmembers}
|
users={this.state.users}
|
||||||
|
usersPerPage={USERS_PER_PAGE}
|
||||||
|
total={this.state.total}
|
||||||
|
nextPage={this.nextPage}
|
||||||
|
search={this.search}
|
||||||
actions={[ChannelInviteButton]}
|
actions={[ChannelInviteButton]}
|
||||||
actionProps={{
|
actionProps={{
|
||||||
channel: this.props.channel,
|
channel: this.props.channel,
|
||||||
|
|||||||
@@ -1,122 +1,89 @@
|
|||||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
import FilteredUserList from './filtered_user_list.jsx';
|
import SearchableUserList from './searchable_user_list.jsx';
|
||||||
import LoadingScreen from './loading_screen.jsx';
|
import LoadingScreen from './loading_screen.jsx';
|
||||||
import ChannelInviteModal from './channel_invite_modal.jsx';
|
import ChannelInviteModal from './channel_invite_modal.jsx';
|
||||||
|
|
||||||
import UserStore from 'stores/user_store.jsx';
|
import UserStore from 'stores/user_store.jsx';
|
||||||
import ChannelStore from 'stores/channel_store.jsx';
|
import ChannelStore from 'stores/channel_store.jsx';
|
||||||
|
import TeamStore from 'stores/team_store.jsx';
|
||||||
|
|
||||||
|
import {searchUsers} from 'actions/user_actions.jsx';
|
||||||
|
import {removeUserFromChannel} from 'actions/channel_actions.jsx';
|
||||||
|
|
||||||
import * as AsyncClient from 'utils/async_client.jsx';
|
import * as AsyncClient from 'utils/async_client.jsx';
|
||||||
import Client from 'client/web_client.jsx';
|
|
||||||
import * as Utils from 'utils/utils.jsx';
|
import * as Utils from 'utils/utils.jsx';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {Modal} from 'react-bootstrap';
|
||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
import {Modal} from 'react-bootstrap';
|
const USERS_PER_PAGE = 50;
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
export default class ChannelMembersModal extends React.Component {
|
export default class ChannelMembersModal extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.getStateFromStores = this.getStateFromStores.bind(this);
|
|
||||||
this.onChange = this.onChange.bind(this);
|
this.onChange = this.onChange.bind(this);
|
||||||
this.handleRemove = this.handleRemove.bind(this);
|
this.handleRemove = this.handleRemove.bind(this);
|
||||||
|
|
||||||
this.createRemoveMemberButton = this.createRemoveMemberButton.bind(this);
|
this.createRemoveMemberButton = this.createRemoveMemberButton.bind(this);
|
||||||
|
this.search = this.search.bind(this);
|
||||||
|
this.nextPage = this.nextPage.bind(this);
|
||||||
|
|
||||||
|
this.term = '';
|
||||||
|
|
||||||
|
const stats = ChannelStore.getStats(props.channel.id);
|
||||||
|
|
||||||
// the rest of the state gets populated when the modal is shown
|
|
||||||
this.state = {
|
this.state = {
|
||||||
showInviteModal: false
|
users: [],
|
||||||
|
total: stats.member_count,
|
||||||
|
showInviteModal: false,
|
||||||
|
search: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
|
||||||
if (!Utils.areObjectsEqual(this.props, nextProps)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Utils.areObjectsEqual(this.state, nextState)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
getStateFromStores() {
|
|
||||||
const extraInfo = ChannelStore.getCurrentExtraInfo();
|
|
||||||
const profiles = UserStore.getActiveOnlyProfiles();
|
|
||||||
|
|
||||||
if (extraInfo.member_count !== extraInfo.members.length) {
|
|
||||||
AsyncClient.getChannelExtraInfo(this.props.channel.id, -1);
|
|
||||||
|
|
||||||
return {
|
|
||||||
loading: true
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const memberList = extraInfo.members.map((member) => {
|
|
||||||
return profiles[member.id];
|
|
||||||
});
|
|
||||||
|
|
||||||
function compareByUsername(a, b) {
|
|
||||||
if (a.username < b.username) {
|
|
||||||
return -1;
|
|
||||||
} else if (a.username > b.username) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
memberList.sort(compareByUsername);
|
|
||||||
|
|
||||||
return {
|
|
||||||
memberList,
|
|
||||||
loading: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
if (!this.props.show && nextProps.show) {
|
if (!this.props.show && nextProps.show) {
|
||||||
ChannelStore.addExtraInfoChangeListener(this.onChange);
|
ChannelStore.addStatsChangeListener(this.onChange);
|
||||||
ChannelStore.addChangeListener(this.onChange);
|
UserStore.addInChannelChangeListener(this.onChange);
|
||||||
|
UserStore.addStatusesChangeListener(this.onChange);
|
||||||
|
|
||||||
this.onChange();
|
this.onChange();
|
||||||
|
AsyncClient.getProfilesInChannel(this.props.channel.id, 0);
|
||||||
} else if (this.props.show && !nextProps.show) {
|
} else if (this.props.show && !nextProps.show) {
|
||||||
ChannelStore.removeExtraInfoChangeListener(this.onChange);
|
ChannelStore.removeStatsChangeListener(this.onChange);
|
||||||
ChannelStore.removeChangeListener(this.onChange);
|
UserStore.removeInChannelChangeListener(this.onChange);
|
||||||
|
UserStore.removeStatusesChangeListener(this.onChange);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange() {
|
onChange() {
|
||||||
const newState = this.getStateFromStores();
|
if (this.state.search) {
|
||||||
if (!Utils.areObjectsEqual(this.state, newState)) {
|
this.search(this.term);
|
||||||
this.setState(newState);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const stats = ChannelStore.getStats(this.props.channel.id);
|
||||||
|
this.setState({
|
||||||
|
users: UserStore.getProfileListInChannel(this.props.channel.id),
|
||||||
|
total: stats.member_count
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRemove(user) {
|
handleRemove(user) {
|
||||||
const userId = user.id;
|
const userId = user.id;
|
||||||
|
|
||||||
Client.removeChannelMember(
|
removeUserFromChannel(
|
||||||
ChannelStore.getCurrentId(),
|
this.props.channel.id,
|
||||||
userId,
|
userId,
|
||||||
() => {
|
null,
|
||||||
const memberList = this.state.memberList.slice();
|
|
||||||
for (let i = 0; i < memberList.length; i++) {
|
|
||||||
if (userId === memberList[i].id) {
|
|
||||||
memberList.splice(i, 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({memberList});
|
|
||||||
AsyncClient.getChannelExtraInfo();
|
|
||||||
},
|
|
||||||
(err) => {
|
(err) => {
|
||||||
this.setState({inviteError: err.message});
|
this.setState({inviteError: err.message});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
createRemoveMemberButton({user}) {
|
createRemoveMemberButton({user}) {
|
||||||
if (user.id === UserStore.getCurrentId()) {
|
if (user.id === UserStore.getCurrentId()) {
|
||||||
return null;
|
return null;
|
||||||
@@ -135,6 +102,29 @@ export default class ChannelMembersModal extends React.Component {
|
|||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nextPage(page) {
|
||||||
|
AsyncClient.getProfilesInChannel(this.props.channel.id, (page + 1) * USERS_PER_PAGE, USERS_PER_PAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
search(term) {
|
||||||
|
this.term = term;
|
||||||
|
|
||||||
|
if (term === '') {
|
||||||
|
this.setState({users: UserStore.getProfileListInChannel(this.props.channel.id), search: false});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
searchUsers(
|
||||||
|
term,
|
||||||
|
TeamStore.getCurrentId(),
|
||||||
|
{in_channel: this.props.channel.id},
|
||||||
|
(users) => {
|
||||||
|
this.setState({search: true, users});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let content;
|
let content;
|
||||||
if (this.state.loading) {
|
if (this.state.loading) {
|
||||||
@@ -151,9 +141,13 @@ export default class ChannelMembersModal extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
content = (
|
content = (
|
||||||
<FilteredUserList
|
<SearchableUserList
|
||||||
style={{maxHeight}}
|
style={{maxHeight}}
|
||||||
users={this.state.memberList}
|
users={this.state.users}
|
||||||
|
usersPerPage={USERS_PER_PAGE}
|
||||||
|
total={this.state.total}
|
||||||
|
nextPage={this.nextPage}
|
||||||
|
search={this.search}
|
||||||
actions={removeButton}
|
actions={removeButton}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -65,9 +65,9 @@ export default class ChannelNotificationsModal extends React.Component {
|
|||||||
Client.updateChannelNotifyProps(data,
|
Client.updateChannelNotifyProps(data,
|
||||||
() => {
|
() => {
|
||||||
// YUCK
|
// YUCK
|
||||||
var member = ChannelStore.getMember(channelId);
|
var member = ChannelStore.getMyMember(channelId);
|
||||||
member.notify_props.desktop = notifyLevel;
|
member.notify_props.desktop = notifyLevel;
|
||||||
ChannelStore.setChannelMember(member);
|
ChannelStore.storeMyChannelMember(member);
|
||||||
this.updateSection('');
|
this.updateSection('');
|
||||||
},
|
},
|
||||||
(err) => {
|
(err) => {
|
||||||
@@ -256,13 +256,13 @@ export default class ChannelNotificationsModal extends React.Component {
|
|||||||
mark_unread: markUnreadLevel
|
mark_unread: markUnreadLevel
|
||||||
};
|
};
|
||||||
|
|
||||||
//TODO: This should be fixed, moved to event_helpers
|
//TODO: This should be fixed, moved to actions
|
||||||
Client.updateChannelNotifyProps(data,
|
Client.updateChannelNotifyProps(data,
|
||||||
() => {
|
() => {
|
||||||
// Yuck...
|
// Yuck...
|
||||||
var member = ChannelStore.getMember(channelId);
|
var member = ChannelStore.getMyMember(channelId);
|
||||||
member.notify_props.mark_unread = markUnreadLevel;
|
member.notify_props.mark_unread = markUnreadLevel;
|
||||||
ChannelStore.setChannelMember(member);
|
ChannelStore.storeMyChannelMember(member);
|
||||||
this.updateSection('');
|
this.updateSection('');
|
||||||
},
|
},
|
||||||
(err) => {
|
(err) => {
|
||||||
|
|||||||
@@ -8,12 +8,13 @@ import SwitchChannelProvider from './suggestion/switch_channel_provider.jsx';
|
|||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
import {Modal} from 'react-bootstrap';
|
import {Modal} from 'react-bootstrap';
|
||||||
|
|
||||||
|
import {goToChannel, openDirectChannelToUser} from 'actions/channel_actions.jsx';
|
||||||
|
|
||||||
import ChannelStore from 'stores/channel_store.jsx';
|
import ChannelStore from 'stores/channel_store.jsx';
|
||||||
import UserStore from 'stores/user_store.jsx';
|
import UserStore from 'stores/user_store.jsx';
|
||||||
|
|
||||||
import Constants from 'utils/constants.jsx';
|
import Constants from 'utils/constants.jsx';
|
||||||
import * as Utils from 'utils/utils.jsx';
|
import * as Utils from 'utils/utils.jsx';
|
||||||
import * as ChannelActions from 'actions/channel_actions.jsx';
|
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
@@ -27,30 +28,14 @@ export default class SwitchChannelModal extends React.Component {
|
|||||||
this.onExited = this.onExited.bind(this);
|
this.onExited = this.onExited.bind(this);
|
||||||
this.handleKeyDown = this.handleKeyDown.bind(this);
|
this.handleKeyDown = this.handleKeyDown.bind(this);
|
||||||
this.handleSubmit = this.handleSubmit.bind(this);
|
this.handleSubmit = this.handleSubmit.bind(this);
|
||||||
this.handleDmUserChange = this.handleDmUserChange.bind(this);
|
|
||||||
this.suggestionProviders = [new SwitchChannelProvider()];
|
this.suggestionProviders = [new SwitchChannelProvider()];
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
dmUsers: UserStore.getDirectProfiles(),
|
|
||||||
text: '',
|
text: '',
|
||||||
error: ''
|
error: ''
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
UserStore.addDmListChangeListener(this.handleDmUserChange);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
UserStore.removeDmListChangeListener(this.handleDmUserChange);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleDmUserChange() {
|
|
||||||
this.setState({
|
|
||||||
dmUsers: UserStore.getDirectProfiles()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (this.props.show && !prevProps.show) {
|
if (this.props.show && !prevProps.show) {
|
||||||
const textbox = this.refs.search.getTextbox();
|
const textbox = this.refs.search.getTextbox();
|
||||||
@@ -97,18 +82,13 @@ export default class SwitchChannelModal extends React.Component {
|
|||||||
const name = this.state.text.trim();
|
const name = this.state.text.trim();
|
||||||
let channel = null;
|
let channel = null;
|
||||||
|
|
||||||
|
// TODO: Replace this hack with something reasonable
|
||||||
if (name.indexOf(Utils.localizeMessage('channel_switch_modal.dm', '(Direct Message)')) > 0) {
|
if (name.indexOf(Utils.localizeMessage('channel_switch_modal.dm', '(Direct Message)')) > 0) {
|
||||||
const dmUsername = name.substr(0, name.indexOf(Utils.localizeMessage('channel_switch_modal.dm', '(Direct Message)')) - 1);
|
const dmUsername = name.substr(0, name.indexOf(Utils.localizeMessage('channel_switch_modal.dm', '(Direct Message)')) - 1);
|
||||||
let user = null;
|
const user = UserStore.getProfileByUsername(dmUsername);
|
||||||
for (const id in this.state.dmUsers) {
|
|
||||||
if (this.state.dmUsers[id].username === dmUsername) {
|
|
||||||
user = this.state.dmUsers[id];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
Utils.openDirectChannelToUser(
|
openDirectChannelToUser(
|
||||||
user,
|
user,
|
||||||
(ch) => {
|
(ch) => {
|
||||||
channel = ch;
|
channel = ch;
|
||||||
@@ -123,7 +103,7 @@ export default class SwitchChannelModal extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (channel !== null) {
|
if (channel !== null) {
|
||||||
ChannelActions.goToChannel(channel);
|
goToChannel(channel);
|
||||||
this.onHide();
|
this.onHide();
|
||||||
} else if (this.state.text !== '') {
|
} else if (this.state.text !== '') {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
|||||||
@@ -1,26 +1,27 @@
|
|||||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
import $ from 'jquery';
|
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import Client from 'client/web_client.jsx';
|
|
||||||
import * as UserAgent from 'utils/user_agent.jsx';
|
|
||||||
import * as AsyncClient from 'utils/async_client.jsx';
|
|
||||||
import * as GlobalActions from 'actions/global_actions.jsx';
|
|
||||||
import Textbox from './textbox.jsx';
|
import Textbox from './textbox.jsx';
|
||||||
|
|
||||||
import BrowserStore from 'stores/browser_store.jsx';
|
import BrowserStore from 'stores/browser_store.jsx';
|
||||||
import PostStore from 'stores/post_store.jsx';
|
import PostStore from 'stores/post_store.jsx';
|
||||||
import MessageHistoryStore from 'stores/message_history_store.jsx';
|
import MessageHistoryStore from 'stores/message_history_store.jsx';
|
||||||
import PreferenceStore from 'stores/preference_store.jsx';
|
import PreferenceStore from 'stores/preference_store.jsx';
|
||||||
|
|
||||||
|
import * as GlobalActions from 'actions/global_actions.jsx';
|
||||||
|
import {loadPosts} from 'actions/post_actions.jsx';
|
||||||
|
|
||||||
|
import Client from 'client/web_client.jsx';
|
||||||
|
import * as UserAgent from 'utils/user_agent.jsx';
|
||||||
|
import * as AsyncClient from 'utils/async_client.jsx';
|
||||||
import * as Utils from 'utils/utils.jsx';
|
import * as Utils from 'utils/utils.jsx';
|
||||||
|
|
||||||
import Constants from 'utils/constants.jsx';
|
import Constants from 'utils/constants.jsx';
|
||||||
|
const KeyCodes = Constants.KeyCodes;
|
||||||
|
|
||||||
import {FormattedMessage} from 'react-intl';
|
import $ from 'jquery';
|
||||||
|
|
||||||
var KeyCodes = Constants.KeyCodes;
|
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
export default class EditPostModal extends React.Component {
|
export default class EditPostModal extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -77,7 +78,7 @@ export default class EditPostModal extends React.Component {
|
|||||||
Client.updatePost(
|
Client.updatePost(
|
||||||
updatedPost,
|
updatedPost,
|
||||||
() => {
|
() => {
|
||||||
AsyncClient.getPosts(updatedPost.channel_id);
|
loadPosts(updatedPost.channel_id);
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
},
|
},
|
||||||
(err) => {
|
(err) => {
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
import React from 'react';
|
import EmojiListItem from './emoji_list_item.jsx';
|
||||||
|
import LoadingScreen from 'components/loading_screen.jsx';
|
||||||
|
|
||||||
|
import EmojiStore from 'stores/emoji_store.jsx';
|
||||||
|
import UserStore from 'stores/user_store.jsx';
|
||||||
|
|
||||||
|
import {loadEmoji} from 'actions/emoji_actions.jsx';
|
||||||
|
|
||||||
import * as AsyncClient from 'utils/async_client.jsx';
|
import * as AsyncClient from 'utils/async_client.jsx';
|
||||||
import EmojiStore from 'stores/emoji_store.jsx';
|
|
||||||
import * as Utils from 'utils/utils.jsx';
|
import * as Utils from 'utils/utils.jsx';
|
||||||
|
|
||||||
import {FormattedMessage} from 'react-intl';
|
import React from 'react';
|
||||||
import EmojiListItem from './emoji_list_item.jsx';
|
|
||||||
import {Link} from 'react-router';
|
import {Link} from 'react-router';
|
||||||
import LoadingScreen from 'components/loading_screen.jsx';
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
export default class EmojiList extends React.Component {
|
export default class EmojiList extends React.Component {
|
||||||
static get propTypes() {
|
static get propTypes() {
|
||||||
@@ -24,28 +28,30 @@ export default class EmojiList extends React.Component {
|
|||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.handleEmojiChange = this.handleEmojiChange.bind(this);
|
this.handleEmojiChange = this.handleEmojiChange.bind(this);
|
||||||
|
this.handleUserChange = this.handleUserChange.bind(this);
|
||||||
this.deleteEmoji = this.deleteEmoji.bind(this);
|
this.deleteEmoji = this.deleteEmoji.bind(this);
|
||||||
|
|
||||||
this.updateFilter = this.updateFilter.bind(this);
|
this.updateFilter = this.updateFilter.bind(this);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
emojis: EmojiStore.getCustomEmojiMap(),
|
emojis: EmojiStore.getCustomEmojiMap(),
|
||||||
loading: !EmojiStore.hasReceivedCustomEmojis(),
|
loading: !EmojiStore.hasReceivedCustomEmojis(),
|
||||||
filter: ''
|
filter: '',
|
||||||
|
users: UserStore.getProfiles()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
EmojiStore.addChangeListener(this.handleEmojiChange);
|
EmojiStore.addChangeListener(this.handleEmojiChange);
|
||||||
|
UserStore.addChangeListener(this.handleUserChange);
|
||||||
|
|
||||||
if (window.mm_config.EnableCustomEmoji === 'true') {
|
if (window.mm_config.EnableCustomEmoji === 'true') {
|
||||||
AsyncClient.listEmoji();
|
loadEmoji();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
EmojiStore.removeChangeListener(this.handleEmojiChange);
|
EmojiStore.removeChangeListener(this.handleEmojiChange);
|
||||||
|
UserStore.removeChangeListener(this.handleUserChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleEmojiChange() {
|
handleEmojiChange() {
|
||||||
@@ -55,6 +61,10 @@ export default class EmojiList extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleUserChange() {
|
||||||
|
this.setState({users: UserStore.getProfiles()});
|
||||||
|
}
|
||||||
|
|
||||||
updateFilter(e) {
|
updateFilter(e) {
|
||||||
this.setState({
|
this.setState({
|
||||||
filter: e.target.value
|
filter: e.target.value
|
||||||
@@ -98,6 +108,7 @@ export default class EmojiList extends React.Component {
|
|||||||
emoji={emoji}
|
emoji={emoji}
|
||||||
onDelete={onDelete}
|
onDelete={onDelete}
|
||||||
filter={filter}
|
filter={filter}
|
||||||
|
creator={this.state.users[emoji.creator_id] || {}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import EmojiStore from 'stores/emoji_store.jsx';
|
import EmojiStore from 'stores/emoji_store.jsx';
|
||||||
import UserStore from 'stores/user_store.jsx';
|
|
||||||
import * as Utils from 'utils/utils.jsx';
|
import * as Utils from 'utils/utils.jsx';
|
||||||
|
|
||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
@@ -14,7 +14,8 @@ export default class EmojiListItem extends React.Component {
|
|||||||
return {
|
return {
|
||||||
emoji: React.PropTypes.object.isRequired,
|
emoji: React.PropTypes.object.isRequired,
|
||||||
onDelete: React.PropTypes.func.isRequired,
|
onDelete: React.PropTypes.func.isRequired,
|
||||||
filter: React.PropTypes.string
|
filter: React.PropTypes.string,
|
||||||
|
creator: React.PropTypes.object.isRequired
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,10 +23,6 @@ export default class EmojiListItem extends React.Component {
|
|||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.handleDelete = this.handleDelete.bind(this);
|
this.handleDelete = this.handleDelete.bind(this);
|
||||||
|
|
||||||
this.state = {
|
|
||||||
creator: UserStore.getProfile(this.props.emoji.creator_id)
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDelete(e) {
|
handleDelete(e) {
|
||||||
@@ -57,7 +54,7 @@ export default class EmojiListItem extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const emoji = this.props.emoji;
|
const emoji = this.props.emoji;
|
||||||
const creator = this.state.creator;
|
const creator = this.props.creator;
|
||||||
const filter = this.props.filter ? this.props.filter.toLowerCase() : '';
|
const filter = this.props.filter ? this.props.filter.toLowerCase() : '';
|
||||||
|
|
||||||
if (!this.matchesFilter(emoji, creator, filter)) {
|
if (!this.matchesFilter(emoji, creator, filter)) {
|
||||||
|
|||||||
@@ -2,9 +2,6 @@
|
|||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import * as Utils from 'utils/utils.jsx';
|
|
||||||
|
|
||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
export default class InstalledCommand extends React.Component {
|
export default class InstalledCommand extends React.Component {
|
||||||
@@ -13,7 +10,8 @@ export default class InstalledCommand extends React.Component {
|
|||||||
command: React.PropTypes.object.isRequired,
|
command: React.PropTypes.object.isRequired,
|
||||||
onRegenToken: React.PropTypes.func.isRequired,
|
onRegenToken: React.PropTypes.func.isRequired,
|
||||||
onDelete: React.PropTypes.func.isRequired,
|
onDelete: React.PropTypes.func.isRequired,
|
||||||
filter: React.PropTypes.string
|
filter: React.PropTypes.string,
|
||||||
|
creator: React.PropTypes.object.isRequired
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,7 +111,7 @@ export default class InstalledCommand extends React.Component {
|
|||||||
id='installed_integrations.creation'
|
id='installed_integrations.creation'
|
||||||
defaultMessage='Created by {creator} on {createAt, date, full}'
|
defaultMessage='Created by {creator} on {createAt, date, full}'
|
||||||
values={{
|
values={{
|
||||||
creator: Utils.displayUsername(command.creator_id),
|
creator: this.props.creator.username,
|
||||||
createAt: command.create_at
|
createAt: command.create_at
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
import React from 'react';
|
import BackstageList from 'components/backstage/components/backstage_list.jsx';
|
||||||
|
import InstalledCommand from './installed_command.jsx';
|
||||||
|
|
||||||
import * as AsyncClient from 'utils/async_client.jsx';
|
|
||||||
import IntegrationStore from 'stores/integration_store.jsx';
|
import IntegrationStore from 'stores/integration_store.jsx';
|
||||||
import TeamStore from 'stores/team_store.jsx';
|
import TeamStore from 'stores/team_store.jsx';
|
||||||
|
import UserStore from 'stores/user_store.jsx';
|
||||||
|
|
||||||
|
import {loadTeamCommands} from 'actions/integration_actions.jsx';
|
||||||
|
|
||||||
|
import * as AsyncClient from 'utils/async_client.jsx';
|
||||||
import * as Utils from 'utils/utils.jsx';
|
import * as Utils from 'utils/utils.jsx';
|
||||||
|
|
||||||
import BackstageList from 'components/backstage/components/backstage_list.jsx';
|
import React from 'react';
|
||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
import InstalledCommand from './installed_command.jsx';
|
|
||||||
|
|
||||||
export default class InstalledCommands extends React.Component {
|
export default class InstalledCommands extends React.Component {
|
||||||
static get propTypes() {
|
static get propTypes() {
|
||||||
@@ -23,7 +27,7 @@ export default class InstalledCommands extends React.Component {
|
|||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.handleIntegrationChange = this.handleIntegrationChange.bind(this);
|
this.handleIntegrationChange = this.handleIntegrationChange.bind(this);
|
||||||
|
this.handleUserChange = this.handleUserChange.bind(this);
|
||||||
this.regenCommandToken = this.regenCommandToken.bind(this);
|
this.regenCommandToken = this.regenCommandToken.bind(this);
|
||||||
this.deleteCommand = this.deleteCommand.bind(this);
|
this.deleteCommand = this.deleteCommand.bind(this);
|
||||||
|
|
||||||
@@ -31,20 +35,23 @@ export default class InstalledCommands extends React.Component {
|
|||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
commands: IntegrationStore.getCommands(teamId),
|
commands: IntegrationStore.getCommands(teamId),
|
||||||
loading: !IntegrationStore.hasReceivedCommands(teamId)
|
loading: !IntegrationStore.hasReceivedCommands(teamId),
|
||||||
|
users: UserStore.getProfiles()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
IntegrationStore.addChangeListener(this.handleIntegrationChange);
|
IntegrationStore.addChangeListener(this.handleIntegrationChange);
|
||||||
|
UserStore.addChangeListener(this.handleUserChange);
|
||||||
|
|
||||||
if (window.mm_config.EnableCommands === 'true') {
|
if (window.mm_config.EnableCommands === 'true') {
|
||||||
AsyncClient.listTeamCommands();
|
loadTeamCommands();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
IntegrationStore.removeChangeListener(this.handleIntegrationChange);
|
IntegrationStore.removeChangeListener(this.handleIntegrationChange);
|
||||||
|
UserStore.removeChangeListener(this.handleUserChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleIntegrationChange() {
|
handleIntegrationChange() {
|
||||||
@@ -56,6 +63,10 @@ export default class InstalledCommands extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleUserChange() {
|
||||||
|
this.setState({users: UserStore.getProfiles()});
|
||||||
|
}
|
||||||
|
|
||||||
regenCommandToken(command) {
|
regenCommandToken(command) {
|
||||||
AsyncClient.regenCommandToken(command.id);
|
AsyncClient.regenCommandToken(command.id);
|
||||||
}
|
}
|
||||||
@@ -72,6 +83,7 @@ export default class InstalledCommands extends React.Component {
|
|||||||
command={command}
|
command={command}
|
||||||
onRegenToken={this.regenCommandToken}
|
onRegenToken={this.regenCommandToken}
|
||||||
onDelete={this.deleteCommand}
|
onDelete={this.deleteCommand}
|
||||||
|
creator={this.state.users[command.creator_id] || {}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ export default class InstalledIncomingWebhook extends React.Component {
|
|||||||
return {
|
return {
|
||||||
incomingWebhook: React.PropTypes.object.isRequired,
|
incomingWebhook: React.PropTypes.object.isRequired,
|
||||||
onDelete: React.PropTypes.func.isRequired,
|
onDelete: React.PropTypes.func.isRequired,
|
||||||
filter: React.PropTypes.string
|
filter: React.PropTypes.string,
|
||||||
|
creator: React.PropTypes.object.isRequired
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,7 +109,7 @@ export default class InstalledIncomingWebhook extends React.Component {
|
|||||||
id='installed_integrations.creation'
|
id='installed_integrations.creation'
|
||||||
defaultMessage='Created by {creator} on {createAt, date, full}'
|
defaultMessage='Created by {creator} on {createAt, date, full}'
|
||||||
values={{
|
values={{
|
||||||
creator: Utils.displayUsername(incomingWebhook.user_id),
|
creator: this.props.creator.username,
|
||||||
createAt: incomingWebhook.create_at
|
createAt: incomingWebhook.create_at
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
import React from 'react';
|
import BackstageList from 'components/backstage/components/backstage_list.jsx';
|
||||||
|
import InstalledIncomingWebhook from './installed_incoming_webhook.jsx';
|
||||||
|
|
||||||
import * as AsyncClient from 'utils/async_client.jsx';
|
|
||||||
import IntegrationStore from 'stores/integration_store.jsx';
|
import IntegrationStore from 'stores/integration_store.jsx';
|
||||||
import TeamStore from 'stores/team_store.jsx';
|
import TeamStore from 'stores/team_store.jsx';
|
||||||
|
import UserStore from 'stores/user_store.jsx';
|
||||||
|
|
||||||
|
import {loadIncomingHooks} from 'actions/integration_actions.jsx';
|
||||||
|
|
||||||
|
import * as AsyncClient from 'utils/async_client.jsx';
|
||||||
import * as Utils from 'utils/utils.jsx';
|
import * as Utils from 'utils/utils.jsx';
|
||||||
|
|
||||||
import BackstageList from 'components/backstage/components/backstage_list.jsx';
|
import React from 'react';
|
||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
import InstalledIncomingWebhook from './installed_incoming_webhook.jsx';
|
|
||||||
|
|
||||||
export default class InstalledIncomingWebhooks extends React.Component {
|
export default class InstalledIncomingWebhooks extends React.Component {
|
||||||
static get propTypes() {
|
static get propTypes() {
|
||||||
@@ -23,27 +27,30 @@ export default class InstalledIncomingWebhooks extends React.Component {
|
|||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.handleIntegrationChange = this.handleIntegrationChange.bind(this);
|
this.handleIntegrationChange = this.handleIntegrationChange.bind(this);
|
||||||
|
this.handleUserChange = this.handleUserChange.bind(this);
|
||||||
this.deleteIncomingWebhook = this.deleteIncomingWebhook.bind(this);
|
this.deleteIncomingWebhook = this.deleteIncomingWebhook.bind(this);
|
||||||
|
|
||||||
const teamId = TeamStore.getCurrentId();
|
const teamId = TeamStore.getCurrentId();
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
incomingWebhooks: IntegrationStore.getIncomingWebhooks(teamId),
|
incomingWebhooks: IntegrationStore.getIncomingWebhooks(teamId),
|
||||||
loading: !IntegrationStore.hasReceivedIncomingWebhooks(teamId)
|
loading: !IntegrationStore.hasReceivedIncomingWebhooks(teamId),
|
||||||
|
users: UserStore.getProfiles()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
IntegrationStore.addChangeListener(this.handleIntegrationChange);
|
IntegrationStore.addChangeListener(this.handleIntegrationChange);
|
||||||
|
UserStore.addChangeListener(this.handleUserChange);
|
||||||
|
|
||||||
if (window.mm_config.EnableIncomingWebhooks === 'true') {
|
if (window.mm_config.EnableIncomingWebhooks === 'true') {
|
||||||
AsyncClient.listIncomingHooks();
|
loadIncomingHooks();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
IntegrationStore.removeChangeListener(this.handleIntegrationChange);
|
IntegrationStore.removeChangeListener(this.handleIntegrationChange);
|
||||||
|
UserStore.removeChangeListener(this.handleUserChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleIntegrationChange() {
|
handleIntegrationChange() {
|
||||||
@@ -55,6 +62,12 @@ export default class InstalledIncomingWebhooks extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleUserChange() {
|
||||||
|
this.setState({
|
||||||
|
users: UserStore.getProfiles()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
deleteIncomingWebhook(incomingWebhook) {
|
deleteIncomingWebhook(incomingWebhook) {
|
||||||
AsyncClient.deleteIncomingHook(incomingWebhook.id);
|
AsyncClient.deleteIncomingHook(incomingWebhook.id);
|
||||||
}
|
}
|
||||||
@@ -66,6 +79,7 @@ export default class InstalledIncomingWebhooks extends React.Component {
|
|||||||
key={incomingWebhook.id}
|
key={incomingWebhook.id}
|
||||||
incomingWebhook={incomingWebhook}
|
incomingWebhook={incomingWebhook}
|
||||||
onDelete={this.deleteIncomingWebhook}
|
onDelete={this.deleteIncomingWebhook}
|
||||||
|
creator={this.state.users[incomingWebhook.user_id] || {}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import ChannelStore from 'stores/channel_store.jsx';
|
import ChannelStore from 'stores/channel_store.jsx';
|
||||||
import * as Utils from 'utils/utils.jsx';
|
|
||||||
|
|
||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
@@ -14,7 +13,8 @@ export default class InstalledOutgoingWebhook extends React.Component {
|
|||||||
outgoingWebhook: React.PropTypes.object.isRequired,
|
outgoingWebhook: React.PropTypes.object.isRequired,
|
||||||
onRegenToken: React.PropTypes.func.isRequired,
|
onRegenToken: React.PropTypes.func.isRequired,
|
||||||
onDelete: React.PropTypes.func.isRequired,
|
onDelete: React.PropTypes.func.isRequired,
|
||||||
filter: React.PropTypes.string
|
filter: React.PropTypes.string,
|
||||||
|
creator: React.PropTypes.object.isRequired
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,7 +195,7 @@ export default class InstalledOutgoingWebhook extends React.Component {
|
|||||||
id='installed_integrations.creation'
|
id='installed_integrations.creation'
|
||||||
defaultMessage='Created by {creator} on {createAt, date, full}'
|
defaultMessage='Created by {creator} on {createAt, date, full}'
|
||||||
values={{
|
values={{
|
||||||
creator: Utils.displayUsername(outgoingWebhook.creator_id),
|
creator: this.props.creator.username,
|
||||||
createAt: outgoingWebhook.create_at
|
createAt: outgoingWebhook.create_at
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
import React from 'react';
|
import BackstageList from 'components/backstage/components/backstage_list.jsx';
|
||||||
|
import InstalledOutgoingWebhook from './installed_outgoing_webhook.jsx';
|
||||||
|
|
||||||
import * as AsyncClient from 'utils/async_client.jsx';
|
|
||||||
import IntegrationStore from 'stores/integration_store.jsx';
|
import IntegrationStore from 'stores/integration_store.jsx';
|
||||||
import TeamStore from 'stores/team_store.jsx';
|
import TeamStore from 'stores/team_store.jsx';
|
||||||
import * as Utils from 'utils/utils.jsx';
|
import UserStore from 'stores/user_store.jsx';
|
||||||
|
|
||||||
import BackstageList from 'components/backstage/components/backstage_list.jsx';
|
import {loadOutgoingHooks} from 'actions/integration_actions.jsx';
|
||||||
|
|
||||||
|
import * as Utils from 'utils/utils.jsx';
|
||||||
|
import * as AsyncClient from 'utils/async_client.jsx';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
import InstalledOutgoingWebhook from './installed_outgoing_webhook.jsx';
|
|
||||||
|
|
||||||
export default class InstalledOutgoingWebhooks extends React.Component {
|
export default class InstalledOutgoingWebhooks extends React.Component {
|
||||||
static get propTypes() {
|
static get propTypes() {
|
||||||
@@ -23,7 +27,7 @@ export default class InstalledOutgoingWebhooks extends React.Component {
|
|||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.handleIntegrationChange = this.handleIntegrationChange.bind(this);
|
this.handleIntegrationChange = this.handleIntegrationChange.bind(this);
|
||||||
|
this.handleUserChange = this.handleUserChange.bind(this);
|
||||||
this.regenOutgoingWebhookToken = this.regenOutgoingWebhookToken.bind(this);
|
this.regenOutgoingWebhookToken = this.regenOutgoingWebhookToken.bind(this);
|
||||||
this.deleteOutgoingWebhook = this.deleteOutgoingWebhook.bind(this);
|
this.deleteOutgoingWebhook = this.deleteOutgoingWebhook.bind(this);
|
||||||
|
|
||||||
@@ -31,20 +35,23 @@ export default class InstalledOutgoingWebhooks extends React.Component {
|
|||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
outgoingWebhooks: IntegrationStore.getOutgoingWebhooks(teamId),
|
outgoingWebhooks: IntegrationStore.getOutgoingWebhooks(teamId),
|
||||||
loading: !IntegrationStore.hasReceivedOutgoingWebhooks(teamId)
|
loading: !IntegrationStore.hasReceivedOutgoingWebhooks(teamId),
|
||||||
|
users: UserStore.getProfiles()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
IntegrationStore.addChangeListener(this.handleIntegrationChange);
|
IntegrationStore.addChangeListener(this.handleIntegrationChange);
|
||||||
|
UserStore.addChangeListener(this.handleUserChange);
|
||||||
|
|
||||||
if (window.mm_config.EnableOutgoingWebhooks === 'true') {
|
if (window.mm_config.EnableOutgoingWebhooks === 'true') {
|
||||||
AsyncClient.listOutgoingHooks();
|
loadOutgoingHooks();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
IntegrationStore.removeChangeListener(this.handleIntegrationChange);
|
IntegrationStore.removeChangeListener(this.handleIntegrationChange);
|
||||||
|
UserStore.removeChangeListener(this.handleUserChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleIntegrationChange() {
|
handleIntegrationChange() {
|
||||||
@@ -56,6 +63,10 @@ export default class InstalledOutgoingWebhooks extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleUserChange() {
|
||||||
|
this.setState({users: UserStore.getProfiles()});
|
||||||
|
}
|
||||||
|
|
||||||
regenOutgoingWebhookToken(outgoingWebhook) {
|
regenOutgoingWebhookToken(outgoingWebhook) {
|
||||||
AsyncClient.regenOutgoingHookToken(outgoingWebhook.id);
|
AsyncClient.regenOutgoingHookToken(outgoingWebhook.id);
|
||||||
}
|
}
|
||||||
@@ -72,6 +83,7 @@ export default class InstalledOutgoingWebhooks extends React.Component {
|
|||||||
outgoingWebhook={outgoingWebhook}
|
outgoingWebhook={outgoingWebhook}
|
||||||
onRegenToken={this.regenOutgoingWebhookToken}
|
onRegenToken={this.regenOutgoingWebhookToken}
|
||||||
onDelete={this.deleteOutgoingWebhook}
|
onDelete={this.deleteOutgoingWebhook}
|
||||||
|
creator={this.state.users[outgoingWebhook.creator_id] || {}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,21 +1,24 @@
|
|||||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
import $ from 'jquery';
|
|
||||||
import LoadingScreen from 'components/loading_screen.jsx';
|
import LoadingScreen from 'components/loading_screen.jsx';
|
||||||
import * as AsyncClient from 'utils/async_client.jsx';
|
|
||||||
import UserStore from 'stores/user_store.jsx';
|
import UserStore from 'stores/user_store.jsx';
|
||||||
import BrowserStore from 'stores/browser_store.jsx';
|
import BrowserStore from 'stores/browser_store.jsx';
|
||||||
import PreferenceStore from 'stores/preference_store.jsx';
|
import PreferenceStore from 'stores/preference_store.jsx';
|
||||||
import * as Utils from 'utils/utils.jsx';
|
|
||||||
import * as GlobalActions from 'actions/global_actions.jsx';
|
import * as GlobalActions from 'actions/global_actions.jsx';
|
||||||
import * as WebSocketActions from 'actions/websocket_actions.jsx';
|
import * as WebSocketActions from 'actions/websocket_actions.jsx';
|
||||||
|
import {loadEmoji} from 'actions/emoji_actions.jsx';
|
||||||
|
|
||||||
|
import * as Utils from 'utils/utils.jsx';
|
||||||
import Constants from 'utils/constants.jsx';
|
import Constants from 'utils/constants.jsx';
|
||||||
|
|
||||||
import {browserHistory} from 'react-router/es6';
|
import {browserHistory} from 'react-router/es6';
|
||||||
|
|
||||||
const BACKSPACE_CHAR = 8;
|
const BACKSPACE_CHAR = 8;
|
||||||
|
|
||||||
|
import $ from 'jquery';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
// import the EmojiStore so that it'll register to receive the results of the listEmojis call further down
|
// import the EmojiStore so that it'll register to receive the results of the listEmojis call further down
|
||||||
@@ -148,7 +151,7 @@ export default class LoggedIn extends React.Component {
|
|||||||
|
|
||||||
// Get custom emoji from the server
|
// Get custom emoji from the server
|
||||||
if (window.mm_config.EnableCustomEmoji === 'true') {
|
if (window.mm_config.EnableCustomEmoji === 'true') {
|
||||||
AsyncClient.listEmoji();
|
loadEmoji(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,62 +1,94 @@
|
|||||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
import FilteredUserList from './filtered_user_list.jsx';
|
import SearchableUserList from 'components/searchable_user_list.jsx';
|
||||||
import TeamMembersDropdown from './team_members_dropdown.jsx';
|
import TeamMembersDropdown from 'components/team_members_dropdown.jsx';
|
||||||
|
|
||||||
import UserStore from 'stores/user_store.jsx';
|
import UserStore from 'stores/user_store.jsx';
|
||||||
import TeamStore from 'stores/team_store.jsx';
|
import TeamStore from 'stores/team_store.jsx';
|
||||||
import * as AsyncClient from 'utils/async_client.jsx';
|
|
||||||
|
import {searchUsers, loadProfilesAndTeamMembers, loadTeamMembersForProfilesList} from 'actions/user_actions.jsx';
|
||||||
|
import {getTeamStats} from 'utils/async_client.jsx';
|
||||||
|
|
||||||
|
import Constants from 'utils/constants.jsx';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
const USERS_PER_PAGE = 50;
|
||||||
|
|
||||||
export default class MemberListTeam extends React.Component {
|
export default class MemberListTeam extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.getUsers = this.getUsers.bind(this);
|
|
||||||
this.onChange = this.onChange.bind(this);
|
this.onChange = this.onChange.bind(this);
|
||||||
this.onTeamChange = this.onTeamChange.bind(this);
|
this.onStatsChange = this.onStatsChange.bind(this);
|
||||||
|
this.search = this.search.bind(this);
|
||||||
|
this.loadComplete = this.loadComplete.bind(this);
|
||||||
|
|
||||||
|
const stats = TeamStore.getCurrentStats();
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
users: this.getUsers(),
|
users: UserStore.getProfileListInTeam(),
|
||||||
teamMembers: TeamStore.getMembersForTeam()
|
teamMembers: Object.assign([], TeamStore.getMembersInTeam()),
|
||||||
|
total: stats.member_count,
|
||||||
|
search: false,
|
||||||
|
loading: true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
UserStore.addChangeListener(this.onChange);
|
UserStore.addInTeamChangeListener(this.onChange);
|
||||||
TeamStore.addChangeListener(this.onTeamChange);
|
UserStore.addStatusesChangeListener(this.onChange);
|
||||||
AsyncClient.getTeamMembers(TeamStore.getCurrentId());
|
TeamStore.addChangeListener(this.onChange);
|
||||||
|
TeamStore.addStatsChangeListener(this.onStatsChange);
|
||||||
|
|
||||||
|
loadProfilesAndTeamMembers(0, Constants.PROFILE_CHUNK_SIZE, TeamStore.getCurrentId(), this.loadComplete);
|
||||||
|
getTeamStats(TeamStore.getCurrentId());
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
UserStore.removeChangeListener(this.onChange);
|
UserStore.removeInTeamChangeListener(this.onChange);
|
||||||
TeamStore.removeChangeListener(this.onTeamChange);
|
UserStore.removeStatusesChangeListener(this.onChange);
|
||||||
|
TeamStore.removeChangeListener(this.onChange);
|
||||||
|
TeamStore.removeStatsChangeListener(this.onStatsChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
getUsers() {
|
loadComplete() {
|
||||||
const profiles = UserStore.getProfiles();
|
this.setState({loading: false});
|
||||||
const users = [];
|
|
||||||
|
|
||||||
for (const id of Object.keys(profiles)) {
|
|
||||||
users.push(profiles[id]);
|
|
||||||
}
|
|
||||||
|
|
||||||
users.sort((a, b) => a.username.localeCompare(b.username));
|
|
||||||
|
|
||||||
return users;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange() {
|
onChange() {
|
||||||
this.setState({
|
if (!this.state.search) {
|
||||||
users: this.getUsers()
|
this.setState({users: UserStore.getProfileListInTeam()});
|
||||||
});
|
}
|
||||||
|
|
||||||
|
this.setState({teamMembers: Object.assign([], TeamStore.getMembersInTeam())});
|
||||||
}
|
}
|
||||||
|
|
||||||
onTeamChange() {
|
onStatsChange() {
|
||||||
this.setState({
|
const stats = TeamStore.getCurrentStats();
|
||||||
teamMembers: TeamStore.getMembersForTeam()
|
this.setState({total: stats.member_count});
|
||||||
});
|
}
|
||||||
|
|
||||||
|
nextPage(page) {
|
||||||
|
loadProfilesAndTeamMembers((page + 1) * USERS_PER_PAGE, USERS_PER_PAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
search(term) {
|
||||||
|
if (term === '') {
|
||||||
|
this.setState({search: false, users: UserStore.getProfileListInTeam()});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
searchUsers(
|
||||||
|
term,
|
||||||
|
TeamStore.getCurrentId(),
|
||||||
|
{},
|
||||||
|
(users) => {
|
||||||
|
this.setState({loading: true, search: true, users});
|
||||||
|
loadTeamMembersForProfilesList(users, TeamStore.getCurrentId(), this.loadComplete);
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@@ -65,12 +97,38 @@ export default class MemberListTeam extends React.Component {
|
|||||||
teamMembersDropdown = [TeamMembersDropdown];
|
teamMembersDropdown = [TeamMembersDropdown];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const teamMembers = this.state.teamMembers;
|
||||||
|
const users = this.state.users;
|
||||||
|
const actionUserProps = {};
|
||||||
|
|
||||||
|
let usersToDisplay;
|
||||||
|
if (this.state.loading) {
|
||||||
|
usersToDisplay = null;
|
||||||
|
} else {
|
||||||
|
usersToDisplay = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < users.length; i++) {
|
||||||
|
const user = users[i];
|
||||||
|
|
||||||
|
if (teamMembers[user.id]) {
|
||||||
|
usersToDisplay.push(user);
|
||||||
|
actionUserProps[user.id] = {
|
||||||
|
teamMember: teamMembers[user.id]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FilteredUserList
|
<SearchableUserList
|
||||||
style={this.props.style}
|
style={this.props.style}
|
||||||
users={this.state.users}
|
users={usersToDisplay}
|
||||||
teamMembers={this.state.teamMembers}
|
usersPerPage={USERS_PER_PAGE}
|
||||||
|
total={this.state.total}
|
||||||
|
nextPage={this.nextPage}
|
||||||
|
search={this.search}
|
||||||
actions={teamMembersDropdown}
|
actions={teamMembersDropdown}
|
||||||
|
actionUserProps={actionUserProps}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,73 +1,67 @@
|
|||||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
import FilteredUserList from 'components/filtered_user_list.jsx';
|
import SearchableUserList from 'components/searchable_user_list.jsx';
|
||||||
import SpinnerButton from 'components/spinner_button.jsx';
|
import SpinnerButton from 'components/spinner_button.jsx';
|
||||||
import LoadingScreen from 'components/loading_screen.jsx';
|
|
||||||
|
|
||||||
import {getMoreDmList} from 'actions/user_actions.jsx';
|
import {searchUsers} from 'actions/user_actions.jsx';
|
||||||
|
import {openDirectChannelToUser} from 'actions/channel_actions.jsx';
|
||||||
|
|
||||||
import UserStore from 'stores/user_store.jsx';
|
import UserStore from 'stores/user_store.jsx';
|
||||||
import TeamStore from 'stores/team_store.jsx';
|
import TeamStore from 'stores/team_store.jsx';
|
||||||
|
|
||||||
|
import * as AsyncClient from 'utils/async_client.jsx';
|
||||||
import * as Utils from 'utils/utils.jsx';
|
import * as Utils from 'utils/utils.jsx';
|
||||||
|
import Constants from 'utils/constants.jsx';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {Modal} from 'react-bootstrap';
|
import {Modal} from 'react-bootstrap';
|
||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
import {browserHistory} from 'react-router/es6';
|
import {browserHistory} from 'react-router/es6';
|
||||||
|
|
||||||
|
const USERS_PER_PAGE = 50;
|
||||||
|
|
||||||
export default class MoreDirectChannels extends React.Component {
|
export default class MoreDirectChannels extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.handleHide = this.handleHide.bind(this);
|
this.handleHide = this.handleHide.bind(this);
|
||||||
this.handleShowDirectChannel = this.handleShowDirectChannel.bind(this);
|
this.handleShowDirectChannel = this.handleShowDirectChannel.bind(this);
|
||||||
this.handleUserChange = this.handleUserChange.bind(this);
|
this.onChange = this.onChange.bind(this);
|
||||||
this.onTeamChange = this.onTeamChange.bind(this);
|
|
||||||
this.createJoinDirectChannelButton = this.createJoinDirectChannelButton.bind(this);
|
this.createJoinDirectChannelButton = this.createJoinDirectChannelButton.bind(this);
|
||||||
|
this.toggleList = this.toggleList.bind(this);
|
||||||
|
this.nextPage = this.nextPage.bind(this);
|
||||||
|
this.search = this.search.bind(this);
|
||||||
|
this.loadComplete = this.loadComplete.bind(this);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
users: UserStore.getProfilesForDmList(),
|
users: UserStore.getProfileListInTeam(TeamStore.getCurrentId(), true),
|
||||||
teamMembers: TeamStore.getMembersForTeam(),
|
|
||||||
loadingDMChannel: -1,
|
loadingDMChannel: -1,
|
||||||
usersLoaded: false,
|
listType: 'team',
|
||||||
teamMembersLoaded: false
|
loading: false,
|
||||||
|
search: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
UserStore.addDmListChangeListener(this.handleUserChange);
|
UserStore.addChangeListener(this.onChange);
|
||||||
TeamStore.addChangeListener(this.onTeamChange);
|
UserStore.addInTeamChangeListener(this.onChange);
|
||||||
|
UserStore.addStatusesChangeListener(this.onChange);
|
||||||
|
TeamStore.addChangeListener(this.onChange);
|
||||||
|
|
||||||
|
AsyncClient.getProfiles(0, Constants.PROFILE_CHUNK_SIZE);
|
||||||
|
AsyncClient.getProfilesInTeam(TeamStore.getCurrentId(), 0, Constants.PROFILE_CHUNK_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
UserStore.removeDmListChangeListener(this.handleUserChange);
|
UserStore.removeChangeListener(this.onChange);
|
||||||
TeamStore.removeChangeListener(this.onTeamChange);
|
UserStore.removeInTeamChangeListener(this.onChange);
|
||||||
|
UserStore.removeStatusesChangeListener(this.onChange);
|
||||||
|
TeamStore.removeChangeListener(this.onChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
loadComplete() {
|
||||||
if (nextProps.show !== this.props.show) {
|
this.setState({loading: false});
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextProps.onModalDismissed.toString() !== this.props.onModalDismissed.toString()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextState.loadingDMChannel !== this.state.loadingDMChannel) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Utils.areObjectsEqual(nextState.users, this.state.users)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Utils.areObjectsEqual(nextState.teamMembers, this.state.teamMembers)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleHide() {
|
handleHide() {
|
||||||
@@ -84,7 +78,7 @@ export default class MoreDirectChannels extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.setState({loadingDMChannel: teammate.id});
|
this.setState({loadingDMChannel: teammate.id});
|
||||||
Utils.openDirectChannelToUser(
|
openDirectChannelToUser(
|
||||||
teammate,
|
teammate,
|
||||||
(channel) => {
|
(channel) => {
|
||||||
browserHistory.push(TeamStore.getCurrentTeamUrl() + '/channels/' + channel.name);
|
browserHistory.push(TeamStore.getCurrentTeamUrl() + '/channels/' + channel.name);
|
||||||
@@ -97,17 +91,35 @@ export default class MoreDirectChannels extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleUserChange() {
|
onChange(force) {
|
||||||
|
if (this.state.search && !force) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let users;
|
||||||
|
if (this.state.listType === 'any') {
|
||||||
|
users = UserStore.getProfileList();
|
||||||
|
} else {
|
||||||
|
users = UserStore.getProfileListInTeam(TeamStore.getCurrentId(), true);
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
users: UserStore.getProfilesForDmList(),
|
users
|
||||||
usersLoaded: true
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onTeamChange() {
|
toggleList(e) {
|
||||||
|
const listType = e.target.value;
|
||||||
|
let users;
|
||||||
|
if (listType === 'any') {
|
||||||
|
users = UserStore.getProfileList();
|
||||||
|
} else {
|
||||||
|
users = UserStore.getProfileListInTeam(TeamStore.getCurrentId(), true);
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
teamMembers: TeamStore.getMembersForTeam(),
|
users,
|
||||||
teamMembersLoaded: true
|
listType
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,38 +138,96 @@ export default class MoreDirectChannels extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nextPage(page) {
|
||||||
|
if (this.state.listType === 'any') {
|
||||||
|
AsyncClient.getProfiles((page + 1) * USERS_PER_PAGE, USERS_PER_PAGE);
|
||||||
|
} else {
|
||||||
|
AsyncClient.getProfilesInTeam(TeamStore.getCurrentId(), (page + 1) * USERS_PER_PAGE, USERS_PER_PAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
search(term) {
|
||||||
|
if (term === '') {
|
||||||
|
this.onChange(true);
|
||||||
|
this.setState({search: false});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let teamId;
|
||||||
|
if (this.state.listType === 'any') {
|
||||||
|
teamId = '';
|
||||||
|
} else {
|
||||||
|
teamId = TeamStore.getCurrentId();
|
||||||
|
}
|
||||||
|
|
||||||
|
searchUsers(
|
||||||
|
term,
|
||||||
|
teamId,
|
||||||
|
{},
|
||||||
|
(users) => {
|
||||||
|
for (let i = 0; i < users.length; i++) {
|
||||||
|
if (users[i].id === UserStore.getCurrentId()) {
|
||||||
|
users.splice(i, 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.setState({search: true, users});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let maxHeight = 1000;
|
let maxHeight = 1000;
|
||||||
if (Utils.windowHeight() <= 1200) {
|
if (Utils.windowHeight() <= 1200) {
|
||||||
maxHeight = Utils.windowHeight() - 300;
|
maxHeight = Utils.windowHeight() - 300;
|
||||||
}
|
}
|
||||||
|
|
||||||
var body = null;
|
let teamToggle;
|
||||||
if (!this.state.usersLoaded || !this.state.teamMembersLoaded) {
|
if (global.window.mm_config.RestrictDirectMessage === 'any') {
|
||||||
body = (<LoadingScreen/>);
|
teamToggle = (
|
||||||
} else {
|
<div className='member-select__container'>
|
||||||
var showTeamToggle = false;
|
<select
|
||||||
if (global.window.mm_config.RestrictDirectMessage === 'any') {
|
className='form-control'
|
||||||
showTeamToggle = true;
|
id='restrictList'
|
||||||
}
|
ref='restrictList'
|
||||||
|
defaultValue='team'
|
||||||
body = (
|
onChange={this.toggleList}
|
||||||
<FilteredUserList
|
>
|
||||||
style={{maxHeight}}
|
<option value='any'>
|
||||||
users={this.state.users}
|
<FormattedMessage
|
||||||
teamMembers={this.state.teamMembers}
|
id='filtered_user_list.any_team'
|
||||||
actions={[this.createJoinDirectChannelButton]}
|
defaultMessage='All Users'
|
||||||
showTeamToggle={showTeamToggle}
|
/>
|
||||||
/>
|
</option>
|
||||||
|
<option value='team'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='filtered_user_list.team_only'
|
||||||
|
defaultMessage='Members of this Team'
|
||||||
|
/>
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<span
|
||||||
|
className='member-show'
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id='filtered_user_list.show'
|
||||||
|
defaultMessage='Filter:'
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let users = this.state.users;
|
||||||
|
if (this.state.loading) {
|
||||||
|
users = null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
dialogClassName='more-modal more-direct-channels'
|
dialogClassName='more-modal more-direct-channels'
|
||||||
show={this.props.show}
|
show={this.props.show}
|
||||||
onHide={this.handleHide}
|
onHide={this.handleHide}
|
||||||
onEntered={getMoreDmList}
|
|
||||||
>
|
>
|
||||||
<Modal.Header closeButton={true}>
|
<Modal.Header closeButton={true}>
|
||||||
<Modal.Title>
|
<Modal.Title>
|
||||||
@@ -168,7 +238,16 @@ export default class MoreDirectChannels extends React.Component {
|
|||||||
</Modal.Title>
|
</Modal.Title>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
{body}
|
{teamToggle}
|
||||||
|
<SearchableUserList
|
||||||
|
key={'moreDirectChannelsList_' + this.state.listType}
|
||||||
|
style={{maxHeight}}
|
||||||
|
users={users}
|
||||||
|
usersPerPage={USERS_PER_PAGE}
|
||||||
|
nextPage={this.nextPage}
|
||||||
|
search={this.search}
|
||||||
|
actions={[this.createJoinDirectChannelButton]}
|
||||||
|
/>
|
||||||
</Modal.Body>
|
</Modal.Body>
|
||||||
<Modal.Footer>
|
<Modal.Footer>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -69,8 +69,8 @@ export default class Navbar extends React.Component {
|
|||||||
return {
|
return {
|
||||||
channel: ChannelStore.getCurrent(),
|
channel: ChannelStore.getCurrent(),
|
||||||
member: ChannelStore.getCurrentMember(),
|
member: ChannelStore.getCurrentMember(),
|
||||||
users: ChannelStore.getCurrentExtraInfo().members,
|
users: [],
|
||||||
userCount: ChannelStore.getCurrentExtraInfo().member_count,
|
userCount: ChannelStore.getCurrentStats().member_count,
|
||||||
currentUser: UserStore.getCurrentUser()
|
currentUser: UserStore.getCurrentUser()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -81,7 +81,7 @@ export default class Navbar extends React.Component {
|
|||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
ChannelStore.addChangeListener(this.onChange);
|
ChannelStore.addChangeListener(this.onChange);
|
||||||
ChannelStore.addExtraInfoChangeListener(this.onChange);
|
ChannelStore.addStatsChangeListener(this.onChange);
|
||||||
UserStore.addStatusesChangeListener(this.onChange);
|
UserStore.addStatusesChangeListener(this.onChange);
|
||||||
$('.inner-wrap').click(this.hideSidebars);
|
$('.inner-wrap').click(this.hideSidebars);
|
||||||
document.addEventListener('keydown', this.showChannelSwitchModal);
|
document.addEventListener('keydown', this.showChannelSwitchModal);
|
||||||
@@ -89,7 +89,7 @@ export default class Navbar extends React.Component {
|
|||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
ChannelStore.removeChangeListener(this.onChange);
|
ChannelStore.removeChangeListener(this.onChange);
|
||||||
ChannelStore.removeExtraInfoChangeListener(this.onChange);
|
ChannelStore.removeStatsChangeListener(this.onChange);
|
||||||
UserStore.removeStatusesChangeListener(this.onChange);
|
UserStore.removeStatusesChangeListener(this.onChange);
|
||||||
document.removeEventListener('keydown', this.showChannelSwitchModal);
|
document.removeEventListener('keydown', this.showChannelSwitchModal);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import UserStore from 'stores/user_store.jsx';
|
|||||||
import PreferenceStore from 'stores/preference_store.jsx';
|
import PreferenceStore from 'stores/preference_store.jsx';
|
||||||
import ChannelStore from 'stores/channel_store.jsx';
|
import ChannelStore from 'stores/channel_store.jsx';
|
||||||
import * as GlobalActions from 'actions/global_actions.jsx';
|
import * as GlobalActions from 'actions/global_actions.jsx';
|
||||||
|
import {startPeriodicStatusUpdates, stopPeriodicStatusUpdates} from 'actions/status_actions.jsx';
|
||||||
import Constants from 'utils/constants.jsx';
|
import Constants from 'utils/constants.jsx';
|
||||||
const TutorialSteps = Constants.TutorialSteps;
|
const TutorialSteps = Constants.TutorialSteps;
|
||||||
const Preferences = Constants.Preferences;
|
const Preferences = Constants.Preferences;
|
||||||
@@ -80,6 +81,7 @@ export default class NeedsTeam extends React.Component {
|
|||||||
if (tutorialStep <= TutorialSteps.INTRO_SCREENS) {
|
if (tutorialStep <= TutorialSteps.INTRO_SCREENS) {
|
||||||
browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/tutorial');
|
browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/tutorial');
|
||||||
}
|
}
|
||||||
|
stopPeriodicStatusUpdates();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@@ -89,6 +91,8 @@ export default class NeedsTeam extends React.Component {
|
|||||||
// Emit view action
|
// Emit view action
|
||||||
GlobalActions.viewLoggedIn();
|
GlobalActions.viewLoggedIn();
|
||||||
|
|
||||||
|
startPeriodicStatusUpdates();
|
||||||
|
|
||||||
// Set up tracking for whether the window is active
|
// Set up tracking for whether the window is active
|
||||||
window.isActive = true;
|
window.isActive = true;
|
||||||
$(window).on('focus', () => {
|
$(window).on('focus', () => {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import ChannelStore from 'stores/channel_store.jsx';
|
|||||||
function getCountsStateFromStores() {
|
function getCountsStateFromStores() {
|
||||||
var count = 0;
|
var count = 0;
|
||||||
var channels = ChannelStore.getAll();
|
var channels = ChannelStore.getAll();
|
||||||
var members = ChannelStore.getAllMembers();
|
var members = ChannelStore.getMyMembers();
|
||||||
|
|
||||||
channels.forEach((channel) => {
|
channels.forEach((channel) => {
|
||||||
var channelMember = members[channel.id];
|
var channelMember = members[channel.id];
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ import ProfilePicture from 'components/profile_picture.jsx';
|
|||||||
import TeamStore from 'stores/team_store.jsx';
|
import TeamStore from 'stores/team_store.jsx';
|
||||||
import UserStore from 'stores/user_store.jsx';
|
import UserStore from 'stores/user_store.jsx';
|
||||||
|
|
||||||
|
import {openDirectChannelToUser} from 'actions/channel_actions.jsx';
|
||||||
|
|
||||||
|
import * as AsyncClient from 'utils/async_client.jsx';
|
||||||
import Client from 'client/web_client.jsx';
|
import Client from 'client/web_client.jsx';
|
||||||
import * as Utils from 'utils/utils.jsx';
|
import * as Utils from 'utils/utils.jsx';
|
||||||
import Constants from 'utils/constants.jsx';
|
|
||||||
|
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
@@ -22,20 +24,18 @@ export default class PopoverListMembers extends React.Component {
|
|||||||
|
|
||||||
this.handleShowDirectChannel = this.handleShowDirectChannel.bind(this);
|
this.handleShowDirectChannel = this.handleShowDirectChannel.bind(this);
|
||||||
this.closePopover = this.closePopover.bind(this);
|
this.closePopover = this.closePopover.bind(this);
|
||||||
|
|
||||||
|
this.state = {showPopover: false};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
$('.member-list__popover .popover-content').perfectScrollbar();
|
$('.member-list__popover .popover-content').perfectScrollbar();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount() {
|
|
||||||
this.setState({showPopover: false});
|
|
||||||
}
|
|
||||||
|
|
||||||
handleShowDirectChannel(teammate, e) {
|
handleShowDirectChannel(teammate, e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
Utils.openDirectChannelToUser(
|
openDirectChannelToUser(
|
||||||
teammate,
|
teammate,
|
||||||
(channel, channelAlreadyExisted) => {
|
(channel, channelAlreadyExisted) => {
|
||||||
browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/channels/' + channel.name);
|
browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/channels/' + channel.name);
|
||||||
@@ -90,12 +90,6 @@ export default class PopoverListMembers extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (name) {
|
if (name) {
|
||||||
let status;
|
|
||||||
if (m.status) {
|
|
||||||
status = m.status;
|
|
||||||
} else {
|
|
||||||
status = UserStore.getStatus(m.id);
|
|
||||||
}
|
|
||||||
popoverHtml.push(
|
popoverHtml.push(
|
||||||
<div
|
<div
|
||||||
className='more-modal__row'
|
className='more-modal__row'
|
||||||
@@ -103,7 +97,6 @@ export default class PopoverListMembers extends React.Component {
|
|||||||
>
|
>
|
||||||
<ProfilePicture
|
<ProfilePicture
|
||||||
src={`${Client.getUsersRoute()}/${m.id}/image?time=${m.update_at}`}
|
src={`${Client.getUsersRoute()}/${m.id}/image?time=${m.update_at}`}
|
||||||
status={status}
|
|
||||||
width='26'
|
width='26'
|
||||||
height='26'
|
height='26'
|
||||||
/>
|
/>
|
||||||
@@ -123,19 +116,27 @@ export default class PopoverListMembers extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
popoverHtml.push(
|
||||||
|
<div
|
||||||
|
className='more-modal__row'
|
||||||
|
key={'popover-member-more'}
|
||||||
|
>
|
||||||
|
<div className='col-sm-5'/>
|
||||||
|
<div className='more-modal__details'>
|
||||||
|
<div
|
||||||
|
className='more-modal__name'
|
||||||
|
>
|
||||||
|
{'...'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let count = this.props.memberCount;
|
const count = this.props.memberCount;
|
||||||
let countText = '-';
|
let countText = '-';
|
||||||
|
if (count > 0) {
|
||||||
// fall back to checking the length of the member list if the count isn't set
|
|
||||||
if (!count && members) {
|
|
||||||
count = members.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count > Constants.MAX_CHANNEL_POPOVER_COUNT) {
|
|
||||||
countText = Constants.MAX_CHANNEL_POPOVER_COUNT + '+';
|
|
||||||
} else if (count > 0) {
|
|
||||||
countText = count.toString();
|
countText = count.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,7 +152,10 @@ export default class PopoverListMembers extends React.Component {
|
|||||||
id='member_popover'
|
id='member_popover'
|
||||||
className='member-popover__trigger'
|
className='member-popover__trigger'
|
||||||
ref='member_popover_target'
|
ref='member_popover_target'
|
||||||
onClick={(e) => this.setState({popoverTarget: e.target, showPopover: !this.state.showPopover})}
|
onClick={(e) => {
|
||||||
|
this.setState({popoverTarget: e.target, showPopover: !this.state.showPopover});
|
||||||
|
AsyncClient.getProfilesInChannel(this.props.channel.id, 0);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
{countText}
|
{countText}
|
||||||
|
|||||||
@@ -4,11 +4,10 @@
|
|||||||
import PostStore from 'stores/post_store.jsx';
|
import PostStore from 'stores/post_store.jsx';
|
||||||
import ChannelStore from 'stores/channel_store.jsx';
|
import ChannelStore from 'stores/channel_store.jsx';
|
||||||
|
|
||||||
|
import {loadPosts} from 'actions/post_actions.jsx';
|
||||||
|
|
||||||
import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
|
import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
|
||||||
|
|
||||||
import Client from 'client/web_client.jsx';
|
import Client from 'client/web_client.jsx';
|
||||||
import * as AsyncClient from 'utils/async_client.jsx';
|
|
||||||
|
|
||||||
import Constants from 'utils/constants.jsx';
|
import Constants from 'utils/constants.jsx';
|
||||||
const ActionTypes = Constants.ActionTypes;
|
const ActionTypes = Constants.ActionTypes;
|
||||||
|
|
||||||
@@ -29,13 +28,13 @@ export default class PendingPostOptions extends React.Component {
|
|||||||
var post = this.props.post;
|
var post = this.props.post;
|
||||||
Client.createPost(post,
|
Client.createPost(post,
|
||||||
(data) => {
|
(data) => {
|
||||||
AsyncClient.getPosts(post.channel_id);
|
loadPosts(post.channel_id);
|
||||||
|
|
||||||
var channel = ChannelStore.get(post.channel_id);
|
var channel = ChannelStore.get(post.channel_id);
|
||||||
var member = ChannelStore.getMember(post.channel_id);
|
var member = ChannelStore.getMyMember(post.channel_id);
|
||||||
member.msg_count = channel.total_msg_count;
|
member.msg_count = channel.total_msg_count;
|
||||||
member.last_viewed_at = (new Date()).getTime();
|
member.last_viewed_at = (new Date()).getTime();
|
||||||
ChannelStore.setChannelMember(member);
|
ChannelStore.storeMyChannelMember(member);
|
||||||
|
|
||||||
AppDispatcher.handleServerAction({
|
AppDispatcher.handleServerAction({
|
||||||
type: ActionTypes.RECEIVED_POST,
|
type: ActionTypes.RECEIVED_POST,
|
||||||
|
|||||||
@@ -66,6 +66,16 @@ export default class PostList extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
// TODO: Clean-up intro text creation
|
||||||
|
if (this.props.channel && this.props.channel.type === Constants.DM_CHANNEL) {
|
||||||
|
const teammateId = Utils.getUserIdFromChannelName(this.props.channel);
|
||||||
|
if (!this.props.profiles[teammateId] && nextProps.profiles[teammateId]) {
|
||||||
|
this.introText = createChannelIntroMessage(this.props.channel, this.state.fullWidthIntro);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleKeyDown(e) {
|
handleKeyDown(e) {
|
||||||
if (e.which === Constants.KeyCodes.ESCAPE && $('.popover.in,.modal.in').length === 0) {
|
if (e.which === Constants.KeyCodes.ESCAPE && $('.popover.in,.modal.in').length === 0) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|||||||
@@ -35,10 +35,7 @@ export default class PostFocusView extends React.Component {
|
|||||||
const focusedPostId = PostStore.getFocusedPostId();
|
const focusedPostId = PostStore.getFocusedPostId();
|
||||||
|
|
||||||
const channel = ChannelStore.getCurrent();
|
const channel = ChannelStore.getCurrent();
|
||||||
let profiles = UserStore.getProfiles();
|
const profiles = UserStore.getProfiles();
|
||||||
if (channel && channel.type === Constants.DM_CHANNEL) {
|
|
||||||
profiles = Object.assign({}, profiles, UserStore.getDirectProfiles());
|
|
||||||
}
|
|
||||||
|
|
||||||
const joinLeaveEnabled = PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'join_leave', true);
|
const joinLeaveEnabled = PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'join_leave', true);
|
||||||
|
|
||||||
@@ -115,12 +112,7 @@ export default class PostFocusView extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onUserChange() {
|
onUserChange() {
|
||||||
const channel = ChannelStore.getCurrent();
|
this.setState({currentUser: UserStore.getCurrentUser(), profiles: JSON.parse(JSON.stringify(UserStore.getProfiles()))});
|
||||||
let profiles = UserStore.getProfiles();
|
|
||||||
if (channel && channel.type === Constants.DM_CHANNEL) {
|
|
||||||
profiles = Object.assign({}, profiles, UserStore.getDirectProfiles());
|
|
||||||
}
|
|
||||||
this.setState({currentUser: UserStore.getCurrentUser(), profiles: JSON.parse(JSON.stringify(profiles))});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onStatusChange() {
|
onStatusChange() {
|
||||||
|
|||||||
@@ -34,13 +34,10 @@ export default class PostViewController extends React.Component {
|
|||||||
this.onBusy = this.onBusy.bind(this);
|
this.onBusy = this.onBusy.bind(this);
|
||||||
|
|
||||||
const channel = props.channel;
|
const channel = props.channel;
|
||||||
let profiles = UserStore.getProfiles();
|
const profiles = UserStore.getProfiles();
|
||||||
if (channel && channel.type === Constants.DM_CHANNEL) {
|
|
||||||
profiles = Object.assign({}, profiles, UserStore.getDirectProfiles());
|
|
||||||
}
|
|
||||||
|
|
||||||
let lastViewed = Number.MAX_VALUE;
|
let lastViewed = Number.MAX_VALUE;
|
||||||
const member = ChannelStore.getMember(channel.id);
|
const member = ChannelStore.getMyMember(channel.id);
|
||||||
if (member != null) {
|
if (member != null) {
|
||||||
lastViewed = member.last_viewed_at;
|
lastViewed = member.last_viewed_at;
|
||||||
}
|
}
|
||||||
@@ -107,12 +104,7 @@ export default class PostViewController extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onUserChange() {
|
onUserChange() {
|
||||||
const channel = this.state.channel;
|
this.setState({currentUser: UserStore.getCurrentUser(), profiles: JSON.parse(JSON.stringify(UserStore.getProfiles()))});
|
||||||
let profiles = UserStore.getProfiles();
|
|
||||||
if (channel && channel.type === Constants.DM_CHANNEL) {
|
|
||||||
profiles = Object.assign({}, profiles, UserStore.getDirectProfiles());
|
|
||||||
}
|
|
||||||
this.setState({currentUser: UserStore.getCurrentUser(), profiles: JSON.parse(JSON.stringify(profiles))});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onPostsChange() {
|
onPostsChange() {
|
||||||
@@ -165,15 +157,12 @@ export default class PostViewController extends React.Component {
|
|||||||
const channel = nextProps.channel;
|
const channel = nextProps.channel;
|
||||||
|
|
||||||
let lastViewed = Number.MAX_VALUE;
|
let lastViewed = Number.MAX_VALUE;
|
||||||
const member = ChannelStore.getMember(channel.id);
|
const member = ChannelStore.getMyMember(channel.id);
|
||||||
if (member != null) {
|
if (member != null) {
|
||||||
lastViewed = member.last_viewed_at;
|
lastViewed = member.last_viewed_at;
|
||||||
}
|
}
|
||||||
|
|
||||||
let profiles = UserStore.getProfiles();
|
const profiles = UserStore.getProfiles();
|
||||||
if (channel && channel.type === Constants.DM_CHANNEL) {
|
|
||||||
profiles = Object.assign({}, profiles, UserStore.getDirectProfiles());
|
|
||||||
}
|
|
||||||
|
|
||||||
const joinLeaveEnabled = PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'join_leave', true);
|
const joinLeaveEnabled = PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'join_leave', true);
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,10 @@ export default class RhsRootPost extends React.Component {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!Utils.areObjectsEqual(nextProps.user, this.props.user)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!Utils.areObjectsEqual(nextProps.currentUser, this.props.currentUser)) {
|
if (!Utils.areObjectsEqual(nextProps.currentUser, this.props.currentUser)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -85,7 +89,7 @@ export default class RhsRootPost extends React.Component {
|
|||||||
var isOwner = this.props.currentUser.id === post.user_id;
|
var isOwner = this.props.currentUser.id === post.user_id;
|
||||||
var isAdmin = TeamStore.isTeamAdminForCurrentTeam() || UserStore.isSystemAdminForCurrentUser();
|
var isAdmin = TeamStore.isTeamAdminForCurrentTeam() || UserStore.isSystemAdminForCurrentUser();
|
||||||
const isSystemMessage = post.type && post.type.startsWith(Constants.SYSTEM_MESSAGE_PREFIX);
|
const isSystemMessage = post.type && post.type.startsWith(Constants.SYSTEM_MESSAGE_PREFIX);
|
||||||
var timestamp = UserStore.getProfile(post.user_id).update_at;
|
var timestamp = user.update_at;
|
||||||
var channel = ChannelStore.get(post.channel_id);
|
var channel = ChannelStore.get(post.channel_id);
|
||||||
const flagIcon = Constants.FLAG_ICON_SVG;
|
const flagIcon = Constants.FLAG_ICON_SVG;
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import RootPost from './rhs_root_post.jsx';
|
|||||||
import Comment from './rhs_comment.jsx';
|
import Comment from './rhs_comment.jsx';
|
||||||
import FileUploadOverlay from './file_upload_overlay.jsx';
|
import FileUploadOverlay from './file_upload_overlay.jsx';
|
||||||
|
|
||||||
import ChannelStore from 'stores/channel_store.jsx';
|
|
||||||
import PostStore from 'stores/post_store.jsx';
|
import PostStore from 'stores/post_store.jsx';
|
||||||
import UserStore from 'stores/user_store.jsx';
|
import UserStore from 'stores/user_store.jsx';
|
||||||
import PreferenceStore from 'stores/preference_store.jsx';
|
import PreferenceStore from 'stores/preference_store.jsx';
|
||||||
@@ -238,12 +237,7 @@ export default class RhsThread extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
const postsArray = this.state.postsArray;
|
const postsArray = this.state.postsArray;
|
||||||
const selected = this.state.selected;
|
const selected = this.state.selected;
|
||||||
const channel = ChannelStore.get(this.state.selected.channel_id);
|
const profiles = this.state.profiles || {};
|
||||||
|
|
||||||
let profiles = this.state.profiles || {};
|
|
||||||
if (channel && channel.type === Constants.DM_CHANNEL) {
|
|
||||||
profiles = Object.assign({}, profiles, UserStore.getDirectProfiles());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (postsArray == null || selected == null) {
|
if (postsArray == null || selected == null) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Ссылка в новой задаче
Block a user