From 11a688d3813646aeb97f58b61c083b019da66cfc Mon Sep 17 00:00:00 2001 From: Christopher Speller Date: Fri, 20 Jan 2017 15:24:53 -0500 Subject: Adding active users statistics to system console (#5141) --- api/admin.go | 26 +++++++++++++++++- store/sql_user_store.go | 25 +++++++++++++++++ store/store.go | 1 + webapp/components/analytics/system_analytics.jsx | 34 ++++++++++++++++++++++++ webapp/i18n/en.json | 2 ++ webapp/utils/async_client.jsx | 8 ++++++ webapp/utils/constants.jsx | 4 ++- 7 files changed, 98 insertions(+), 2 deletions(-) diff --git a/api/admin.go b/api/admin.go index 300796bad..4e4d21b0b 100644 --- a/api/admin.go +++ b/api/admin.go @@ -25,6 +25,11 @@ import ( "github.com/mssola/user_agent" ) +const ( + DAY_MILLISECONDS = 24 * 60 * 60 * 1000 + MONTH_MILLISECONDS = 31 * DAY_MILLISECONDS +) + func InitAdmin() { l4g.Debug(utils.T("api.admin.init.debug")) @@ -382,7 +387,7 @@ func getAnalytics(c *Context, w http.ResponseWriter, r *http.Request) { } if name == "standard" { - var rows model.AnalyticsRows = make([]*model.AnalyticsRow, 8) + var rows model.AnalyticsRows = make([]*model.AnalyticsRow, 10) rows[0] = &model.AnalyticsRow{"channel_open_count", 0} rows[1] = &model.AnalyticsRow{"channel_private_count", 0} rows[2] = &model.AnalyticsRow{"post_count", 0} @@ -391,6 +396,8 @@ func getAnalytics(c *Context, w http.ResponseWriter, r *http.Request) { 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} + rows[8] = &model.AnalyticsRow{"daily_active_users", 0} + rows[9] = &model.AnalyticsRow{"monthly_active_users", 0} openChan := app.Srv.Store.Channel().AnalyticsTypeCount(teamId, model.CHANNEL_OPEN) privateChan := app.Srv.Store.Channel().AnalyticsTypeCount(teamId, model.CHANNEL_PRIVATE) @@ -406,6 +413,9 @@ func getAnalytics(c *Context, w http.ResponseWriter, r *http.Request) { postChan = app.Srv.Store.Post().AnalyticsPostCount(teamId, false, false) } + dailyActiveChan := app.Srv.Store.User().AnalyticsActiveCount(DAY_MILLISECONDS) + monthlyActiveChan := app.Srv.Store.User().AnalyticsActiveCount(MONTH_MILLISECONDS) + if r := <-openChan; r.Err != nil { c.Err = r.Err return @@ -477,6 +487,20 @@ func getAnalytics(c *Context, w http.ResponseWriter, r *http.Request) { rows[7].Value = float64(app.Srv.Store.TotalReadDbConnections()) } + if r := <-dailyActiveChan; r.Err != nil { + c.Err = r.Err + return + } else { + rows[8].Value = float64(r.Data.(int64)) + } + + if r := <-monthlyActiveChan; r.Err != nil { + c.Err = r.Err + return + } else { + rows[9].Value = float64(r.Data.(int64)) + } + w.Write([]byte(rows.ToJson())) } else if name == "post_counts_day" { if skipIntensiveQueries { diff --git a/store/sql_user_store.go b/store/sql_user_store.go index 533757479..09742a4f4 100644 --- a/store/sql_user_store.go +++ b/store/sql_user_store.go @@ -1129,6 +1129,31 @@ func (us SqlUserStore) AnalyticsUniqueUserCount(teamId string) StoreChannel { return storeChannel } +func (us SqlUserStore) AnalyticsActiveCount(timePeriod int64) StoreChannel { + + storeChannel := make(StoreChannel, 1) + + go func() { + result := StoreResult{} + + time := model.GetMillis() - timePeriod + + query := "SELECT COUNT(*) FROM Status WHERE LastActivityAt > :Time" + + v, err := us.GetReplica().SelectInt(query, map[string]interface{}{"Time": time}) + if err != nil { + result.Err = model.NewLocAppError("SqlUserStore.AnalyticsDailyActiveUsers", "store.sql_user.analytics_daily_active_users.app_error", nil, err.Error()) + } else { + result.Data = v + } + + storeChannel <- result + close(storeChannel) + }() + + return storeChannel +} + func (us SqlUserStore) GetUnreadCount(userId string) StoreChannel { storeChannel := make(StoreChannel, 1) diff --git a/store/store.go b/store/store.go index 88a553b7c..730a923c5 100644 --- a/store/store.go +++ b/store/store.go @@ -175,6 +175,7 @@ type UserStore interface { GetSystemAdminProfiles() StoreChannel PermanentDelete(userId string) StoreChannel AnalyticsUniqueUserCount(teamId string) StoreChannel + AnalyticsActiveCount(time int64) StoreChannel GetUnreadCount(userId string) StoreChannel GetUnreadCountForChannel(userId string, channelId string) StoreChannel GetRecentlyActiveUsersForTeam(teamId string) StoreChannel diff --git a/webapp/components/analytics/system_analytics.jsx b/webapp/components/analytics/system_analytics.jsx index dd7b90260..89cc98f0b 100644 --- a/webapp/components/analytics/system_analytics.jsx +++ b/webapp/components/analytics/system_analytics.jsx @@ -358,6 +358,32 @@ class SystemAnalytics extends React.Component { /> ); + const dailyActiveUsers = ( + + } + icon='fa-users' + count={stats[StatTypes.DAILY_ACTIVE_USERS]} + /> + ); + + const monthlyActiveUsers = ( + + } + icon='fa-users' + count={stats[StatTypes.MONTHLY_ACTIVE_USERS]} + /> + ); + let firstRow; let secondRow; if (isLicensed && skippedIntensiveQueries) { @@ -406,6 +432,13 @@ class SystemAnalytics extends React.Component { ); } + const thirdRow = ( +
+ {dailyActiveUsers} + {monthlyActiveUsers} +
+ ); + return (

@@ -417,6 +450,7 @@ class SystemAnalytics extends React.Component { {banner} {firstRow} {secondRow} + {thirdRow} {advancedStats} {advancedGraphs} {postTotalGraph} diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json index 1513a4afe..daa0434dc 100644 --- a/webapp/i18n/en.json +++ b/webapp/i18n/en.json @@ -945,6 +945,8 @@ "analytics.system.textPosts": "Posts with Text-only", "analytics.system.title": "System Statistics", "analytics.system.totalChannels": "Total Channels", + "analytics.system.dailyActiveUsers": "Daily Active Users", + "analytics.system.monthlyActiveUsers": "Monthly Active Users", "analytics.system.totalCommands": "Total Commands", "analytics.system.totalFilePosts": "Posts with Files", "analytics.system.totalHashtagPosts": "Posts with Hashtags", diff --git a/webapp/utils/async_client.jsx b/webapp/utils/async_client.jsx index 25724ec5e..cd38be811 100644 --- a/webapp/utils/async_client.jsx +++ b/webapp/utils/async_client.jsx @@ -1039,6 +1039,14 @@ export function getStandardAnalytics(teamId) { if (data[index].name === 'total_read_db_connections') { stats[StatTypes.TOTAL_READ_DB_CONNECTIONS] = data[index].value; } + + if (data[index].name === 'daily_active_users') { + stats[StatTypes.DAILY_ACTIVE_USERS] = data[index].value; + } + + if (data[index].name === 'monthly_active_users') { + stats[StatTypes.MONTHLY_ACTIVE_USERS] = data[index].value; + } } AppDispatcher.handleServerAction({ diff --git a/webapp/utils/constants.jsx b/webapp/utils/constants.jsx index b1c188d89..86147ee8c 100644 --- a/webapp/utils/constants.jsx +++ b/webapp/utils/constants.jsx @@ -260,7 +260,9 @@ export const Constants = { NEWLY_CREATED_USERS: null, TOTAL_WEBSOCKET_CONNECTIONS: null, TOTAL_MASTER_DB_CONNECTIONS: null, - TOTAL_READ_DB_CONNECTIONS: null + TOTAL_READ_DB_CONNECTIONS: null, + DAILY_ACTIVE_USERS: null, + MONTHLY_ACTIVE_USERS: null }), STAT_MAX_ACTIVE_USERS: 20, STAT_MAX_NEW_USERS: 20, -- cgit v1.2.3-1-g7c22