summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/admin.go35
-rw-r--r--api/admin_test.go15
-rw-r--r--i18n/en.json4
-rw-r--r--model/client.go11
-rw-r--r--model/user.go1
-rw-r--r--store/sql_status_store.go22
-rw-r--r--store/store.go1
-rw-r--r--webapp/client/client.jsx9
-rw-r--r--webapp/utils/async_client.jsx2
9 files changed, 99 insertions, 1 deletions
diff --git a/api/admin.go b/api/admin.go
index d48c8d379..573a22c6b 100644
--- a/api/admin.go
+++ b/api/admin.go
@@ -48,6 +48,7 @@ func InitAdmin() {
BaseRoutes.Admin.Handle("/remove_certificate", ApiAdminSystemRequired(removeCertificate)).Methods("POST")
BaseRoutes.Admin.Handle("/saml_cert_status", ApiAdminSystemRequired(samlCertificateStatus)).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")
}
func getLogs(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -754,3 +755,37 @@ func samlCertificateStatus(c *Context, w http.ResponseWriter, r *http.Request) {
w.Write([]byte(model.StringInterfaceToJson(status)))
}
+
+func getRecentlyActiveUsers(c *Context, w http.ResponseWriter, r *http.Request) {
+ statusMap := map[string]interface{}{}
+
+ 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
+ return
+ } else {
+ profiles := result.Data.(map[string]*model.User)
+
+ for k, p := range profiles {
+ p = sanitizeProfile(c, p)
+
+ if lastActivityAt, ok := statusMap[p.Id].(int64); ok {
+ p.LastActivityAt = lastActivityAt
+ }
+
+ profiles[k] = p
+ }
+
+ w.Write([]byte(model.UserMapToJson(profiles)))
+ }
+
+}
diff --git a/api/admin_test.go b/api/admin_test.go
index 3d8a95676..7f3c584d8 100644
--- a/api/admin_test.go
+++ b/api/admin_test.go
@@ -527,3 +527,18 @@ func TestAdminLdapSyncNow(t *testing.T) {
t.Fatal("Returned Failure")
}
}
+
+func TestGetRecentlyActiveUsers(t *testing.T) {
+ th := Setup().InitBasic()
+
+ user1Id := th.BasicUser.Id
+ user2Id := th.BasicUser2.Id
+
+ if userMap, err := th.BasicClient.GetRecentlyActiveUsers(th.BasicTeam.Id); err != nil {
+ t.Fatal(err)
+ } else if len(userMap.Data.(map[string]*model.User)) != 2 {
+ t.Fatal("should have been 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")
+ }
+}
diff --git a/i18n/en.json b/i18n/en.json
index 920e444ab..b52836bdf 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -4284,6 +4284,10 @@
"translation": "Encountered an error retrieving all the online/away statuses"
},
{
+ "id": "store.sql_status.get_team_statuses.app_error",
+ "translation": "Encountered an error retrieving all statuses from the team members"
+ },
+ {
"id": "store.sql_status.get_total_active_users_count.app_error",
"translation": "We could not count the active users"
},
diff --git a/model/client.go b/model/client.go
index 2c3fb5aca..e54f61347 100644
--- a/model/client.go
+++ b/model/client.go
@@ -820,6 +820,17 @@ func (c *Client) GetClusterStatus() ([]*ClusterInfo, *AppError) {
}
}
+// GetRecentlyActiveUsers returns a map of users including lastActivityAt using user id as the key
+func (c *Client) GetRecentlyActiveUsers(teamId string) (*Result, *AppError) {
+ if r, err := c.DoApiGet("/admin/recently_active_users/"+teamId, "", ""); 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
+ }
+}
+
func (c *Client) GetAllAudits() (*Result, *AppError) {
if r, err := c.DoApiGet("/admin/audits", "", ""); err != nil {
return nil, err
diff --git a/model/user.go b/model/user.go
index 434ce2732..680bc48c9 100644
--- a/model/user.go
+++ b/model/user.go
@@ -48,6 +48,7 @@ type User struct {
Locale string `json:"locale"`
MfaActive bool `json:"mfa_active,omitempty"`
MfaSecret string `json:"mfa_secret,omitempty"`
+ LastActivityAt int64 `db:"-" json:"last_activity_at,omitempty"`
}
// IsValid validates the user and returns an error if it isn't configured
diff --git a/store/sql_status_store.go b/store/sql_status_store.go
index 9f7441796..0915ced48 100644
--- a/store/sql_status_store.go
+++ b/store/sql_status_store.go
@@ -129,6 +129,28 @@ func (s SqlStatusStore) GetOnline() StoreChannel {
return storeChannel
}
+func (s SqlStatusStore) GetAllFromTeam(teamId string) StoreChannel {
+ storeChannel := make(StoreChannel)
+
+ go func() {
+ result := StoreResult{}
+
+ var statuses []*model.Status
+ if _, err := s.GetReplica().Select(&statuses,
+ `SELECT s.* FROM Status AS s INNER JOIN
+ TeamMembers AS tm ON tm.TeamId=:TeamId AND s.UserId=tm.UserId`, map[string]interface{}{"TeamId": teamId}); err != nil {
+ result.Err = model.NewLocAppError("SqlStatusStore.GetAllFromTeam", "store.sql_status.get_team_statuses.app_error", nil, err.Error())
+ } else {
+ result.Data = statuses
+ }
+
+ storeChannel <- result
+ close(storeChannel)
+ }()
+
+ return storeChannel
+}
+
func (s SqlStatusStore) ResetAll() StoreChannel {
storeChannel := make(StoreChannel)
diff --git a/store/store.go b/store/store.go
index d3b908106..cf08a6c64 100644
--- a/store/store.go
+++ b/store/store.go
@@ -275,6 +275,7 @@ type StatusStore interface {
Get(userId string) StoreChannel
GetOnlineAway() StoreChannel
GetOnline() StoreChannel
+ GetAllFromTeam(teamId string) StoreChannel
ResetAll() StoreChannel
GetTotalActiveUsersCount() StoreChannel
UpdateLastActivityAt(userId string, lastActivityAt int64) StoreChannel
diff --git a/webapp/client/client.jsx b/webapp/client/client.jsx
index 80e2cfe3e..6aabee080 100644
--- a/webapp/client/client.jsx
+++ b/webapp/client/client.jsx
@@ -958,6 +958,15 @@ export default class Client {
end(this.handleResponse.bind(this, 'getAudits', success, error));
}
+ getRecentlyActiveUsers(id, success, error) {
+ request.
+ get(`${this.getAdminRoute()}/recently_active_users/${id}`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ end(this.handleResponse.bind(this, 'getRecentlyActiveUsers', success, error));
+ }
+
getDirectProfiles(success, error) {
request.
get(`${this.getUsersRoute()}/direct_profiles`).
diff --git a/webapp/utils/async_client.jsx b/webapp/utils/async_client.jsx
index 585e4b1c0..22b223bc5 100644
--- a/webapp/utils/async_client.jsx
+++ b/webapp/utils/async_client.jsx
@@ -1138,7 +1138,7 @@ export function getRecentAndNewUsersAnalytics(teamId) {
callTracker[callName] = utils.getTimestamp();
- Client.getProfilesForTeam(
+ Client.getRecentlyActiveUsers(
teamId,
(users) => {
const stats = {};