diff options
author | Joram Wilander <jwawilander@gmail.com> | 2016-07-18 11:10:03 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-07-18 11:10:03 -0400 |
commit | c0ab2636d699c8544ce03a58f61b95cfd66ff7ce (patch) | |
tree | c7d07934e0ff1a75aafb097a184ae150888199c0 /store | |
parent | 180adc79af3d14de6ce62f6e687a6735db3fe82f (diff) | |
download | chat-c0ab2636d699c8544ce03a58f61b95cfd66ff7ce.tar.gz chat-c0ab2636d699c8544ce03a58f61b95cfd66ff7ce.tar.bz2 chat-c0ab2636d699c8544ce03a58f61b95cfd66ff7ce.zip |
PLT-2241 Refactored statuses into a more real-time system (#3573)
* Refactored statuses into a more real-time system
* Updated package.json with correct commit and fixed minor bug
* Minor updates to statuses based on feedback
* When setting status online, update only LastActivityAt if status already exists
Diffstat (limited to 'store')
-rw-r--r-- | store/sql_status_store.go | 166 | ||||
-rw-r--r-- | store/sql_status_store_test.go | 83 | ||||
-rw-r--r-- | store/sql_store.go | 8 | ||||
-rw-r--r-- | store/sql_user_store.go | 86 | ||||
-rw-r--r-- | store/sql_user_store_test.go | 99 | ||||
-rw-r--r-- | store/store.go | 14 |
6 files changed, 272 insertions, 184 deletions
diff --git a/store/sql_status_store.go b/store/sql_status_store.go new file mode 100644 index 000000000..235d12fa8 --- /dev/null +++ b/store/sql_status_store.go @@ -0,0 +1,166 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package store + +import ( + "database/sql" + "github.com/mattermost/platform/model" +) + +const ( + MISSING_STATUS_ERROR = "store.sql_status.get.missing.app_error" +) + +type SqlStatusStore struct { + *SqlStore +} + +func NewSqlStatusStore(sqlStore *SqlStore) StatusStore { + s := &SqlStatusStore{sqlStore} + + for _, db := range sqlStore.GetAllConns() { + table := db.AddTableWithName(model.Status{}, "Status").SetKeys(false, "UserId") + table.ColMap("UserId").SetMaxSize(26) + table.ColMap("Status").SetMaxSize(32) + } + + return s +} + +func (s SqlStatusStore) UpgradeSchemaIfNeeded() { +} + +func (s SqlStatusStore) CreateIndexesIfNotExists() { + s.CreateIndexIfNotExists("idx_status_user_id", "Status", "UserId") + s.CreateIndexIfNotExists("idx_status_status", "Status", "Status") +} + +func (s SqlStatusStore) SaveOrUpdate(status *model.Status) StoreChannel { + storeChannel := make(StoreChannel) + + go func() { + result := StoreResult{} + + 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 { + result.Err = model.NewLocAppError("SqlStatusStore.SaveOrUpdate", "store.sql_status.update.app_error", nil, "") + } + } else { + if err := s.GetMaster().Insert(status); err != nil { + result.Err = model.NewLocAppError("SqlStatusStore.SaveOrUpdate", "store.sql_status.save.app_error", nil, "") + } + } + + storeChannel <- result + close(storeChannel) + }() + + return storeChannel +} + +func (s SqlStatusStore) Get(userId string) StoreChannel { + storeChannel := make(StoreChannel) + + go func() { + result := StoreResult{} + + var status model.Status + + if err := s.GetReplica().SelectOne(&status, + `SELECT + * + FROM + Status + WHERE + UserId = :UserId`, map[string]interface{}{"UserId": userId}); err != nil { + if err == sql.ErrNoRows { + result.Err = model.NewLocAppError("SqlStatusStore.Get", MISSING_STATUS_ERROR, nil, err.Error()) + } else { + result.Err = model.NewLocAppError("SqlStatusStore.Get", "store.sql_status.get.app_error", nil, err.Error()) + } + } else { + result.Data = &status + } + + storeChannel <- result + close(storeChannel) + }() + + return storeChannel +} + +func (s SqlStatusStore) GetOnlineAway() StoreChannel { + storeChannel := make(StoreChannel) + + go func() { + result := StoreResult{} + + var statuses []*model.Status + if _, err := s.GetReplica().Select(&statuses, "SELECT * FROM Status WHERE Status = :Online OR Status = :Away", map[string]interface{}{"Online": model.STATUS_ONLINE, "Away": model.STATUS_AWAY}); err != nil { + result.Err = model.NewLocAppError("SqlStatusStore.GetOnline", "store.sql_status.get_online_away.app_error", nil, err.Error()) + } else { + result.Data = statuses + } + + storeChannel <- result + close(storeChannel) + }() + + return storeChannel +} + +func (s SqlStatusStore) ResetAll() StoreChannel { + storeChannel := make(StoreChannel) + + go func() { + result := StoreResult{} + + if _, err := s.GetMaster().Exec("UPDATE Status SET Status = :Status", map[string]interface{}{"Status": model.STATUS_OFFLINE}); err != nil { + result.Err = model.NewLocAppError("SqlStatusStore.ResetAll", "store.sql_status.reset_all.app_error", nil, "") + } + + storeChannel <- result + close(storeChannel) + }() + + return storeChannel +} + +func (s SqlStatusStore) GetTotalActiveUsersCount() StoreChannel { + storeChannel := make(StoreChannel) + + go func() { + result := StoreResult{} + + time := model.GetMillis() - (1000 * 60 * 60 * 24) + + if count, err := s.GetReplica().SelectInt("SELECT COUNT(UserId) FROM Status WHERE LastActivityAt > :Time", map[string]interface{}{"Time": time}); err != nil { + result.Err = model.NewLocAppError("SqlStatusStore.GetTotalActiveUsersCount", "store.sql_status.get_total_active_users_count.app_error", nil, err.Error()) + } else { + result.Data = count + } + + storeChannel <- result + close(storeChannel) + }() + + return storeChannel +} + +func (s SqlStatusStore) UpdateLastActivityAt(userId string, lastActivityAt int64) StoreChannel { + storeChannel := make(StoreChannel) + + go func() { + result := StoreResult{} + + if _, err := s.GetMaster().Exec("UPDATE Status SET LastActivityAt = :Time WHERE UserId = :UserId", map[string]interface{}{"UserId": userId, "Time": lastActivityAt}); err != nil { + result.Err = model.NewLocAppError("SqlStatusStore.UpdateLastActivityAt", "store.sql_status.update_last_activity_at.app_error", nil, "") + } + + storeChannel <- result + close(storeChannel) + }() + + return storeChannel +} diff --git a/store/sql_status_store_test.go b/store/sql_status_store_test.go new file mode 100644 index 000000000..e16dc14d0 --- /dev/null +++ b/store/sql_status_store_test.go @@ -0,0 +1,83 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package store + +import ( + "github.com/mattermost/platform/model" + "testing" +) + +func TestSqlStatusStore(t *testing.T) { + Setup() + + status := &model.Status{model.NewId(), model.STATUS_ONLINE, 0} + + if err := (<-store.Status().SaveOrUpdate(status)).Err; err != nil { + t.Fatal(err) + } + + status.LastActivityAt = 10 + + if err := (<-store.Status().SaveOrUpdate(status)).Err; err != nil { + t.Fatal(err) + } + + if err := (<-store.Status().Get(status.UserId)).Err; err != nil { + t.Fatal(err) + } + + status2 := &model.Status{model.NewId(), model.STATUS_AWAY, 0} + if err := (<-store.Status().SaveOrUpdate(status2)).Err; err != nil { + t.Fatal(err) + } + + status3 := &model.Status{model.NewId(), model.STATUS_OFFLINE, 0} + if err := (<-store.Status().SaveOrUpdate(status3)).Err; err != nil { + t.Fatal(err) + } + + if result := <-store.Status().GetOnlineAway(); result.Err != nil { + t.Fatal(result.Err) + } else { + statuses := result.Data.([]*model.Status) + for _, status := range statuses { + if status.Status == model.STATUS_OFFLINE { + t.Fatal("should not have returned offline statuses") + } + } + } + + if err := (<-store.Status().ResetAll()).Err; err != nil { + t.Fatal(err) + } + + if result := <-store.Status().Get(status.UserId); result.Err != nil { + t.Fatal(result.Err) + } else { + status := result.Data.(*model.Status) + if status.Status != model.STATUS_OFFLINE { + t.Fatal("should be offline") + } + } + + if result := <-store.Status().UpdateLastActivityAt(status.UserId, 10); result.Err != nil { + t.Fatal(result.Err) + } +} + +func TestActiveUserCount(t *testing.T) { + Setup() + + status := &model.Status{model.NewId(), model.STATUS_ONLINE, model.GetMillis()} + Must(store.Status().SaveOrUpdate(status)) + + if result := <-store.Status().GetTotalActiveUsersCount(); result.Err != nil { + t.Fatal(result.Err) + } else { + count := result.Data.(int64) + if count <= 0 { + t.Fatal() + } + } +} diff --git a/store/sql_store.go b/store/sql_store.go index c33da62cc..2047ad150 100644 --- a/store/sql_store.go +++ b/store/sql_store.go @@ -53,6 +53,7 @@ type SqlStore struct { license LicenseStore recovery PasswordRecoveryStore emoji EmojiStore + status StatusStore SchemaVersion string } @@ -129,6 +130,7 @@ func NewSqlStore() Store { sqlStore.license = NewSqlLicenseStore(sqlStore) sqlStore.recovery = NewSqlPasswordRecoveryStore(sqlStore) sqlStore.emoji = NewSqlEmojiStore(sqlStore) + sqlStore.status = NewSqlStatusStore(sqlStore) err := sqlStore.master.CreateTablesIfNotExists() if err != nil { @@ -152,6 +154,7 @@ func NewSqlStore() Store { sqlStore.license.(*SqlLicenseStore).UpgradeSchemaIfNeeded() sqlStore.recovery.(*SqlPasswordRecoveryStore).UpgradeSchemaIfNeeded() sqlStore.emoji.(*SqlEmojiStore).UpgradeSchemaIfNeeded() + sqlStore.status.(*SqlStatusStore).UpgradeSchemaIfNeeded() sqlStore.team.(*SqlTeamStore).CreateIndexesIfNotExists() sqlStore.channel.(*SqlChannelStore).CreateIndexesIfNotExists() @@ -168,6 +171,7 @@ func NewSqlStore() Store { sqlStore.license.(*SqlLicenseStore).CreateIndexesIfNotExists() sqlStore.recovery.(*SqlPasswordRecoveryStore).CreateIndexesIfNotExists() sqlStore.emoji.(*SqlEmojiStore).CreateIndexesIfNotExists() + sqlStore.status.(*SqlStatusStore).CreateIndexesIfNotExists() sqlStore.preference.(*SqlPreferenceStore).DeleteUnusedFeatures() @@ -696,6 +700,10 @@ func (ss SqlStore) Emoji() EmojiStore { return ss.emoji } +func (ss SqlStore) Status() StatusStore { + return ss.status +} + func (ss SqlStore) DropAllTables() { ss.master.TruncateTables() } diff --git a/store/sql_user_store.go b/store/sql_user_store.go index 325008670..c9e435f34 100644 --- a/store/sql_user_store.go +++ b/store/sql_user_store.go @@ -108,6 +108,10 @@ func (us SqlUserStore) UpgradeSchemaIfNeeded() { us.Preference().Save(&data) } } + + // ADDED for 3.3 remove for 3.7 + us.RemoveColumnIfExists("Users", "LastActivityAt") + us.RemoveColumnIfExists("Users", "LastPingAt") } func themeMigrationFailed(err error) { @@ -187,8 +191,6 @@ func (us SqlUserStore) Update(user *model.User, trustedUpdateData bool) StoreCha user.Password = oldUser.Password user.LastPasswordUpdate = oldUser.LastPasswordUpdate user.LastPictureUpdate = oldUser.LastPictureUpdate - user.LastActivityAt = oldUser.LastActivityAt - user.LastPingAt = oldUser.LastPingAt user.EmailVerified = oldUser.EmailVerified user.FailedAttempts = oldUser.FailedAttempts user.MfaSecret = oldUser.MfaSecret @@ -283,65 +285,6 @@ func (us SqlUserStore) UpdateUpdateAt(userId string) StoreChannel { return storeChannel } -func (us SqlUserStore) UpdateLastPingAt(userId string, time int64) StoreChannel { - storeChannel := make(StoreChannel) - - go func() { - result := StoreResult{} - - if _, err := us.GetMaster().Exec("UPDATE Users SET LastPingAt = :LastPingAt WHERE Id = :UserId", map[string]interface{}{"LastPingAt": time, "UserId": userId}); err != nil { - result.Err = model.NewLocAppError("SqlUserStore.UpdateLastPingAt", "store.sql_user.update_last_ping.app_error", nil, "user_id="+userId) - } else { - result.Data = userId - } - - storeChannel <- result - close(storeChannel) - }() - - return storeChannel -} - -func (us SqlUserStore) UpdateLastActivityAt(userId string, time int64) StoreChannel { - storeChannel := make(StoreChannel) - - go func() { - result := StoreResult{} - - if _, err := us.GetMaster().Exec("UPDATE Users SET LastActivityAt = :LastActivityAt WHERE Id = :UserId", map[string]interface{}{"LastActivityAt": time, "UserId": userId}); err != nil { - result.Err = model.NewLocAppError("SqlUserStore.UpdateLastActivityAt", "store.sql_user.update_last_activity.app_error", nil, "user_id="+userId) - } else { - result.Data = userId - } - - storeChannel <- result - close(storeChannel) - }() - - return storeChannel -} - -func (us SqlUserStore) UpdateUserAndSessionActivity(userId string, sessionId string, time int64) StoreChannel { - storeChannel := make(StoreChannel) - - go func() { - result := StoreResult{} - - if _, err := us.GetMaster().Exec("UPDATE Users SET LastActivityAt = :UserLastActivityAt WHERE Id = :UserId", map[string]interface{}{"UserLastActivityAt": time, "UserId": userId}); err != nil { - result.Err = model.NewLocAppError("SqlUserStore.UpdateLastActivityAt", "store.sql_user.update_last_activity.app_error", nil, "1 user_id="+userId+" session_id="+sessionId+" err="+err.Error()) - } else if _, err := us.GetMaster().Exec("UPDATE Sessions SET LastActivityAt = :SessionLastActivityAt WHERE Id = :SessionId", map[string]interface{}{"SessionLastActivityAt": time, "SessionId": sessionId}); err != nil { - result.Err = model.NewLocAppError("SqlUserStore.UpdateLastActivityAt", "store.sql_user.update_last_activity.app_error", nil, "2 user_id="+userId+" session_id="+sessionId+" err="+err.Error()) - } else { - result.Data = userId - } - - storeChannel <- result - close(storeChannel) - }() - - return storeChannel -} - func (us SqlUserStore) UpdatePassword(userId, hashedPassword string) StoreChannel { storeChannel := make(StoreChannel) @@ -988,27 +931,6 @@ func (us SqlUserStore) GetTotalUsersCount() StoreChannel { return storeChannel } -func (us SqlUserStore) GetTotalActiveUsersCount() StoreChannel { - storeChannel := make(StoreChannel) - - go func() { - result := StoreResult{} - - time := model.GetMillis() - (1000 * 60 * 60 * 24) - - if count, err := us.GetReplica().SelectInt("SELECT COUNT(Id) FROM Users WHERE LastActivityAt > :Time", map[string]interface{}{"Time": time}); err != nil { - result.Err = model.NewLocAppError("SqlUserStore.GetTotalActiveUsersCount", "store.sql_user.get_total_active_users_count.app_error", nil, err.Error()) - } else { - result.Data = count - } - - storeChannel <- result - close(storeChannel) - }() - - return storeChannel -} - func (us SqlUserStore) PermanentDelete(userId string) StoreChannel { storeChannel := make(StoreChannel) diff --git a/store/sql_user_store_test.go b/store/sql_user_store_test.go index b6cfbb0ec..4b722b0b3 100644 --- a/store/sql_user_store_test.go +++ b/store/sql_user_store_test.go @@ -129,50 +129,6 @@ func TestUserStoreUpdateUpdateAt(t *testing.T) { } -func TestUserStoreUpdateLastPingAt(t *testing.T) { - Setup() - - u1 := &model.User{} - u1.Email = model.NewId() - Must(store.User().Save(u1)) - Must(store.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u1.Id})) - - if err := (<-store.User().UpdateLastPingAt(u1.Id, 1234567890)).Err; err != nil { - t.Fatal(err) - } - - if r1 := <-store.User().Get(u1.Id); r1.Err != nil { - t.Fatal(r1.Err) - } else { - if r1.Data.(*model.User).LastPingAt != 1234567890 { - t.Fatal("LastPingAt not updated correctly") - } - } - -} - -func TestUserStoreUpdateLastActivityAt(t *testing.T) { - Setup() - - u1 := &model.User{} - u1.Email = model.NewId() - Must(store.User().Save(u1)) - Must(store.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u1.Id})) - - if err := (<-store.User().UpdateLastActivityAt(u1.Id, 1234567890)).Err; err != nil { - t.Fatal(err) - } - - if r1 := <-store.User().Get(u1.Id); r1.Err != nil { - t.Fatal(r1.Err) - } else { - if r1.Data.(*model.User).LastActivityAt != 1234567890 { - t.Fatal("LastActivityAt not updated correctly") - } - } - -} - func TestUserStoreUpdateFailedPasswordAttempts(t *testing.T) { Setup() @@ -189,41 +145,7 @@ func TestUserStoreUpdateFailedPasswordAttempts(t *testing.T) { t.Fatal(r1.Err) } else { if r1.Data.(*model.User).FailedAttempts != 3 { - t.Fatal("LastActivityAt not updated correctly") - } - } - -} - -func TestUserStoreUpdateUserAndSessionActivity(t *testing.T) { - Setup() - - u1 := &model.User{} - u1.Email = model.NewId() - Must(store.User().Save(u1)) - Must(store.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u1.Id})) - - s1 := model.Session{} - s1.UserId = u1.Id - Must(store.Session().Save(&s1)) - - if err := (<-store.User().UpdateUserAndSessionActivity(u1.Id, s1.Id, 1234567890)).Err; err != nil { - t.Fatal(err) - } - - if r1 := <-store.User().Get(u1.Id); r1.Err != nil { - t.Fatal(r1.Err) - } else { - if r1.Data.(*model.User).LastActivityAt != 1234567890 { - t.Fatal("LastActivityAt not updated correctly for user") - } - } - - if r2 := <-store.Session().Get(s1.Id); r2.Err != nil { - t.Fatal(r2.Err) - } else { - if r2.Data.(*model.Session).LastActivityAt != 1234567890 { - t.Fatal("LastActivityAt not updated correctly for session") + t.Fatal("FailedAttempts not updated correctly") } } @@ -268,25 +190,6 @@ func TestUserCount(t *testing.T) { } } -func TestActiveUserCount(t *testing.T) { - Setup() - - u1 := &model.User{} - u1.Email = model.NewId() - Must(store.User().Save(u1)) - Must(store.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u1.Id})) - <-store.User().UpdateLastActivityAt(u1.Id, model.GetMillis()) - - if result := <-store.User().GetTotalActiveUsersCount(); result.Err != nil { - t.Fatal(result.Err) - } else { - count := result.Data.(int64) - if count <= 0 { - t.Fatal() - } - } -} - func TestUserStoreGetAllProfiles(t *testing.T) { Setup() diff --git a/store/store.go b/store/store.go index 4b5c0e8cd..c495ff927 100644 --- a/store/store.go +++ b/store/store.go @@ -43,6 +43,7 @@ type Store interface { License() LicenseStore PasswordRecovery() PasswordRecoveryStore Emoji() EmojiStore + Status() StatusStore MarkSystemRanUnitTests() Close() DropAllTables() @@ -126,9 +127,6 @@ type UserStore interface { Update(user *model.User, allowRoleUpdate bool) StoreChannel UpdateLastPictureUpdate(userId string) StoreChannel UpdateUpdateAt(userId string) StoreChannel - UpdateLastPingAt(userId string, time int64) StoreChannel - UpdateLastActivityAt(userId string, time int64) StoreChannel - UpdateUserAndSessionActivity(userId string, sessionId string, time int64) StoreChannel UpdatePassword(userId, newPassword string) StoreChannel UpdateAuthData(userId string, service string, authData *string, email string) StoreChannel UpdateMfaSecret(userId, secret string) StoreChannel @@ -150,7 +148,6 @@ type UserStore interface { GetEtagForDirectProfiles(userId string) StoreChannel UpdateFailedPasswordAttempts(userId string, attempts int) StoreChannel GetTotalUsersCount() StoreChannel - GetTotalActiveUsersCount() StoreChannel GetSystemAdminProfiles() StoreChannel PermanentDelete(userId string) StoreChannel AnalyticsUniqueUserCount(teamId string) StoreChannel @@ -264,3 +261,12 @@ type EmojiStore interface { GetAll() StoreChannel Delete(id string, time int64) StoreChannel } + +type StatusStore interface { + SaveOrUpdate(status *model.Status) StoreChannel + Get(userId string) StoreChannel + GetOnlineAway() StoreChannel + ResetAll() StoreChannel + GetTotalActiveUsersCount() StoreChannel + UpdateLastActivityAt(userId string, lastActivityAt int64) StoreChannel +} |