mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
Adding analytics tab
This commit is contained in:
62
api/admin.go
62
api/admin.go
@@ -26,7 +26,7 @@ func InitAdmin(r *mux.Router) {
|
||||
sr.Handle("/test_email", ApiUserRequired(testEmail)).Methods("POST")
|
||||
sr.Handle("/client_props", ApiAppHandler(getClientProperties)).Methods("GET")
|
||||
sr.Handle("/log_client", ApiAppHandler(logClient)).Methods("POST")
|
||||
|
||||
sr.Handle("/analytics/{id:[A-Za-z0-9]+}/{name:[A-Za-z0-9_]+}", ApiAppHandler(getAnalytics)).Methods("GET")
|
||||
}
|
||||
|
||||
func getLogs(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
@@ -142,3 +142,63 @@ func testEmail(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
m["SUCCESS"] = "true"
|
||||
w.Write([]byte(model.MapToJson(m)))
|
||||
}
|
||||
|
||||
func getAnalytics(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if !c.HasSystemAdminPermissions("getAnalytics") {
|
||||
return
|
||||
}
|
||||
|
||||
params := mux.Vars(r)
|
||||
teamId := params["id"]
|
||||
name := params["name"]
|
||||
|
||||
if name == "standard" {
|
||||
var rows model.AnalyticsRows = make([]*model.AnalyticsRow, 3)
|
||||
rows[0] = &model.AnalyticsRow{"channel_open_count", 0}
|
||||
rows[1] = &model.AnalyticsRow{"channel_private_count", 0}
|
||||
rows[2] = &model.AnalyticsRow{"post_count", 0}
|
||||
openChan := Srv.Store.Channel().AnalyticsTypeCount(teamId, model.CHANNEL_OPEN)
|
||||
privateChan := Srv.Store.Channel().AnalyticsTypeCount(teamId, model.CHANNEL_PRIVATE)
|
||||
postChan := Srv.Store.Post().AnalyticsPostCount(teamId)
|
||||
|
||||
if r := <-openChan; r.Err != nil {
|
||||
c.Err = r.Err
|
||||
return
|
||||
} else {
|
||||
rows[0].Value = float64(r.Data.(int64))
|
||||
}
|
||||
|
||||
if r := <-privateChan; r.Err != nil {
|
||||
c.Err = r.Err
|
||||
return
|
||||
} else {
|
||||
rows[1].Value = float64(r.Data.(int64))
|
||||
}
|
||||
|
||||
if r := <-postChan; r.Err != nil {
|
||||
c.Err = r.Err
|
||||
return
|
||||
} else {
|
||||
rows[2].Value = float64(r.Data.(int64))
|
||||
}
|
||||
|
||||
w.Write([]byte(rows.ToJson()))
|
||||
} else if name == "post_counts_day" {
|
||||
if r := <-Srv.Store.Post().AnalyticsPostCountsByDay(teamId); r.Err != nil {
|
||||
c.Err = r.Err
|
||||
return
|
||||
} else {
|
||||
w.Write([]byte(r.Data.(model.AnalyticsRows).ToJson()))
|
||||
}
|
||||
} else if name == "user_counts_with_posts_day" {
|
||||
if r := <-Srv.Store.Post().AnalyticsUserCountsWithPostsByDay(teamId); r.Err != nil {
|
||||
c.Err = r.Err
|
||||
return
|
||||
} else {
|
||||
w.Write([]byte(r.Data.(model.AnalyticsRows).ToJson()))
|
||||
}
|
||||
} else {
|
||||
c.SetInvalidParam("getAnalytics", "name")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -150,3 +150,151 @@ func TestEmailTest(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAnalyticsStandard(t *testing.T) {
|
||||
Setup()
|
||||
|
||||
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
|
||||
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
|
||||
|
||||
user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
|
||||
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
|
||||
store.Must(Srv.Store.User().VerifyEmail(user.Id))
|
||||
|
||||
Client.LoginByEmail(team.Name, user.Email, "pwd")
|
||||
|
||||
channel1 := &model.Channel{DisplayName: "TestGetPosts", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id}
|
||||
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
|
||||
|
||||
post1 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"}
|
||||
post1 = Client.Must(Client.CreatePost(post1)).Data.(*model.Post)
|
||||
|
||||
if _, err := Client.GetAnalytics(team.Id, "standard"); err == nil {
|
||||
t.Fatal("Shouldn't have permissions")
|
||||
}
|
||||
|
||||
c := &Context{}
|
||||
c.RequestId = model.NewId()
|
||||
c.IpAddress = "cmd_line"
|
||||
UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
|
||||
|
||||
Client.LoginByEmail(team.Name, user.Email, "pwd")
|
||||
|
||||
if result, err := Client.GetAnalytics(team.Id, "standard"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
rows := result.Data.(model.AnalyticsRows)
|
||||
|
||||
if rows[0].Name != "channel_open_count" {
|
||||
t.Log(rows.ToJson())
|
||||
t.Fatal()
|
||||
}
|
||||
|
||||
if rows[0].Value != 2 {
|
||||
t.Log(rows.ToJson())
|
||||
t.Fatal()
|
||||
}
|
||||
|
||||
if rows[1].Name != "channel_private_count" {
|
||||
t.Log(rows.ToJson())
|
||||
t.Fatal()
|
||||
}
|
||||
|
||||
if rows[1].Value != 1 {
|
||||
t.Log(rows.ToJson())
|
||||
t.Fatal()
|
||||
}
|
||||
|
||||
if rows[2].Name != "post_count" {
|
||||
t.Log(rows.ToJson())
|
||||
t.Fatal()
|
||||
}
|
||||
|
||||
if rows[2].Value != 1 {
|
||||
t.Log(rows.ToJson())
|
||||
t.Fatal()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPostCount(t *testing.T) {
|
||||
Setup()
|
||||
|
||||
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
|
||||
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
|
||||
|
||||
user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
|
||||
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
|
||||
store.Must(Srv.Store.User().VerifyEmail(user.Id))
|
||||
|
||||
Client.LoginByEmail(team.Name, user.Email, "pwd")
|
||||
|
||||
channel1 := &model.Channel{DisplayName: "TestGetPosts", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id}
|
||||
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
|
||||
|
||||
post1 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"}
|
||||
post1 = Client.Must(Client.CreatePost(post1)).Data.(*model.Post)
|
||||
|
||||
if _, err := Client.GetAnalytics(team.Id, "post_counts_day"); err == nil {
|
||||
t.Fatal("Shouldn't have permissions")
|
||||
}
|
||||
|
||||
c := &Context{}
|
||||
c.RequestId = model.NewId()
|
||||
c.IpAddress = "cmd_line"
|
||||
UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
|
||||
|
||||
Client.LoginByEmail(team.Name, user.Email, "pwd")
|
||||
|
||||
if result, err := Client.GetAnalytics(team.Id, "post_counts_day"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
rows := result.Data.(model.AnalyticsRows)
|
||||
|
||||
if rows[0].Value != 1 {
|
||||
t.Log(rows.ToJson())
|
||||
t.Fatal()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserCountsWithPostsByDay(t *testing.T) {
|
||||
Setup()
|
||||
|
||||
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
|
||||
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
|
||||
|
||||
user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
|
||||
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
|
||||
store.Must(Srv.Store.User().VerifyEmail(user.Id))
|
||||
|
||||
Client.LoginByEmail(team.Name, user.Email, "pwd")
|
||||
|
||||
channel1 := &model.Channel{DisplayName: "TestGetPosts", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id}
|
||||
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
|
||||
|
||||
post1 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"}
|
||||
post1 = Client.Must(Client.CreatePost(post1)).Data.(*model.Post)
|
||||
|
||||
if _, err := Client.GetAnalytics(team.Id, "user_counts_with_posts_day"); err == nil {
|
||||
t.Fatal("Shouldn't have permissions")
|
||||
}
|
||||
|
||||
c := &Context{}
|
||||
c.RequestId = model.NewId()
|
||||
c.IpAddress = "cmd_line"
|
||||
UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
|
||||
|
||||
Client.LoginByEmail(team.Name, user.Email, "pwd")
|
||||
|
||||
if result, err := Client.GetAnalytics(team.Id, "user_counts_with_posts_day"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
rows := result.Data.(model.AnalyticsRows)
|
||||
|
||||
if rows[0].Value != 1 {
|
||||
t.Log(rows.ToJson())
|
||||
t.Fatal()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
55
model/analytics_row.go
Normal file
55
model/analytics_row.go
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
)
|
||||
|
||||
type AnalyticsRow struct {
|
||||
Name string `json:"name"`
|
||||
Value float64 `json:"value"`
|
||||
}
|
||||
|
||||
type AnalyticsRows []*AnalyticsRow
|
||||
|
||||
func (me *AnalyticsRow) ToJson() string {
|
||||
b, err := json.Marshal(me)
|
||||
if err != nil {
|
||||
return ""
|
||||
} else {
|
||||
return string(b)
|
||||
}
|
||||
}
|
||||
|
||||
func AnalyticsRowFromJson(data io.Reader) *AnalyticsRow {
|
||||
decoder := json.NewDecoder(data)
|
||||
var me AnalyticsRow
|
||||
err := decoder.Decode(&me)
|
||||
if err == nil {
|
||||
return &me
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (me AnalyticsRows) ToJson() string {
|
||||
if b, err := json.Marshal(me); err != nil {
|
||||
return "[]"
|
||||
} else {
|
||||
return string(b)
|
||||
}
|
||||
}
|
||||
|
||||
func AnalyticsRowsFromJson(data io.Reader) AnalyticsRows {
|
||||
decoder := json.NewDecoder(data)
|
||||
var me AnalyticsRows
|
||||
err := decoder.Decode(&me)
|
||||
if err == nil {
|
||||
return me
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
37
model/analytics_row_test.go
Normal file
37
model/analytics_row_test.go
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAnalyticsRowJson(t *testing.T) {
|
||||
a1 := AnalyticsRow{}
|
||||
a1.Name = "2015-10-12"
|
||||
a1.Value = 12345.0
|
||||
json := a1.ToJson()
|
||||
ra1 := AnalyticsRowFromJson(strings.NewReader(json))
|
||||
|
||||
if a1.Name != ra1.Name {
|
||||
t.Fatal("days didn't match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnalyticsRowsJson(t *testing.T) {
|
||||
a1 := AnalyticsRow{}
|
||||
a1.Name = "2015-10-12"
|
||||
a1.Value = 12345.0
|
||||
|
||||
var a1s AnalyticsRows = make([]*AnalyticsRow, 1)
|
||||
a1s[0] = &a1
|
||||
|
||||
ljson := a1s.ToJson()
|
||||
results := AnalyticsRowsFromJson(strings.NewReader(ljson))
|
||||
|
||||
if a1s[0].Name != results[0].Name {
|
||||
t.Fatal("Ids do not match")
|
||||
}
|
||||
}
|
||||
@@ -414,6 +414,15 @@ func (c *Client) TestEmail(config *Config) (*Result, *AppError) {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) GetAnalytics(teamId, name string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet("/admin/analytics/"+teamId+"/"+name, "", ""); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), AnalyticsRowsFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) CreateChannel(channel *Channel) (*Result, *AppError) {
|
||||
if r, err := c.DoApiPost("/channels/create", channel.ToJson()); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -744,3 +744,31 @@ func (s SqlChannelStore) GetForExport(teamId string) StoreChannel {
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlChannelStore) AnalyticsTypeCount(teamId string, channelType string) StoreChannel {
|
||||
storeChannel := make(StoreChannel)
|
||||
|
||||
go func() {
|
||||
result := StoreResult{}
|
||||
|
||||
v, err := s.GetReplica().SelectInt(
|
||||
`SELECT
|
||||
COUNT(Id) AS Value
|
||||
FROM
|
||||
Channels
|
||||
WHERE
|
||||
TeamId = :TeamId
|
||||
AND Type = :ChannelType`,
|
||||
map[string]interface{}{"TeamId": teamId, "ChannelType": channelType})
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlChannelStore.AnalyticsTypeCount", "We couldn't get channel type counts", err.Error())
|
||||
} else {
|
||||
result.Data = v
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
@@ -460,6 +460,24 @@ func TestChannelStoreGetMoreChannels(t *testing.T) {
|
||||
if list.Channels[0].Name != o3.Name {
|
||||
t.Fatal("missing channel")
|
||||
}
|
||||
|
||||
if r1 := <-store.Channel().AnalyticsTypeCount(o1.TeamId, model.CHANNEL_OPEN); r1.Err != nil {
|
||||
t.Fatal(r1.Err)
|
||||
} else {
|
||||
if r1.Data.(int64) != 2 {
|
||||
t.Log(r1.Data)
|
||||
t.Fatal("wrong value")
|
||||
}
|
||||
}
|
||||
|
||||
if r1 := <-store.Channel().AnalyticsTypeCount(o1.TeamId, model.CHANNEL_PRIVATE); r1.Err != nil {
|
||||
t.Fatal(r1.Err)
|
||||
} else {
|
||||
if r1.Data.(int64) != 2 {
|
||||
t.Log(r1.Data)
|
||||
t.Fatal("wrong value")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestChannelStoreGetChannelCounts(t *testing.T) {
|
||||
|
||||
@@ -571,3 +571,105 @@ func (s SqlPostStore) GetForExport(channelId string) StoreChannel {
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlPostStore) AnalyticsUserCountsWithPostsByDay(teamId string) StoreChannel {
|
||||
storeChannel := make(StoreChannel)
|
||||
|
||||
go func() {
|
||||
result := StoreResult{}
|
||||
|
||||
var rows model.AnalyticsRows
|
||||
_, err := s.GetReplica().Select(
|
||||
&rows,
|
||||
`SELECT
|
||||
t1.Name, COUNT(t1.UserId) AS Value
|
||||
FROM
|
||||
(SELECT DISTINCT
|
||||
DATE(FROM_UNIXTIME(Posts.CreateAt / 1000)) AS Name,
|
||||
Posts.UserId
|
||||
FROM
|
||||
Posts, Channels
|
||||
WHERE
|
||||
Posts.ChannelId = Channels.Id
|
||||
AND Channels.TeamId = :TeamId
|
||||
ORDER BY Name DESC) AS t1
|
||||
GROUP BY Name
|
||||
ORDER BY Name DESC
|
||||
LIMIT 30`,
|
||||
map[string]interface{}{"TeamId": teamId})
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlPostStore.AnalyticsUserCountsWithPostsByDay", "We couldn't get user counts with posts", err.Error())
|
||||
} else {
|
||||
result.Data = rows
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlPostStore) AnalyticsPostCountsByDay(teamId string) StoreChannel {
|
||||
storeChannel := make(StoreChannel)
|
||||
|
||||
go func() {
|
||||
result := StoreResult{}
|
||||
|
||||
var rows model.AnalyticsRows
|
||||
_, err := s.GetReplica().Select(
|
||||
&rows,
|
||||
`SELECT
|
||||
DATE(FROM_UNIXTIME(Posts.CreateAt / 1000)) AS Name,
|
||||
COUNT(Posts.Id) AS Value
|
||||
FROM
|
||||
Posts,
|
||||
Channels
|
||||
WHERE
|
||||
Posts.ChannelId = Channels.Id
|
||||
AND Channels.TeamId = :TeamId
|
||||
GROUP BY Name
|
||||
ORDER BY Name DESC
|
||||
LIMIT 30`,
|
||||
map[string]interface{}{"TeamId": teamId})
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlPostStore.AnalyticsPostCountsByDay", "We couldn't get post counts by day", err.Error())
|
||||
} else {
|
||||
result.Data = rows
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlPostStore) AnalyticsPostCount(teamId string) StoreChannel {
|
||||
storeChannel := make(StoreChannel)
|
||||
|
||||
go func() {
|
||||
result := StoreResult{}
|
||||
|
||||
v, err := s.GetReplica().SelectInt(
|
||||
`SELECT
|
||||
COUNT(Posts.Id) AS Value
|
||||
FROM
|
||||
Posts,
|
||||
Channels
|
||||
WHERE
|
||||
Posts.ChannelId = Channels.Id
|
||||
AND Channels.TeamId = :TeamId`,
|
||||
map[string]interface{}{"TeamId": teamId})
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlPostStore.AnalyticsPostCount", "We couldn't get post counts", err.Error())
|
||||
} else {
|
||||
result.Data = v
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
@@ -580,3 +580,131 @@ func TestPostStoreSearch(t *testing.T) {
|
||||
t.Fatal("returned wrong search result")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserCountsWithPostsByDay(t *testing.T) {
|
||||
Setup()
|
||||
|
||||
t1 := &model.Team{}
|
||||
t1.DisplayName = "DisplayName"
|
||||
t1.Name = "a" + model.NewId() + "b"
|
||||
t1.Email = model.NewId() + "@nowhere.com"
|
||||
t1.Type = model.TEAM_OPEN
|
||||
t1 = Must(store.Team().Save(t1)).(*model.Team)
|
||||
|
||||
c1 := &model.Channel{}
|
||||
c1.TeamId = t1.Id
|
||||
c1.DisplayName = "Channel2"
|
||||
c1.Name = "a" + model.NewId() + "b"
|
||||
c1.Type = model.CHANNEL_OPEN
|
||||
c1 = Must(store.Channel().Save(c1)).(*model.Channel)
|
||||
|
||||
o1 := &model.Post{}
|
||||
o1.ChannelId = c1.Id
|
||||
o1.UserId = model.NewId()
|
||||
o1.CreateAt = model.GetMillis()
|
||||
o1.Message = "a" + model.NewId() + "b"
|
||||
o1 = Must(store.Post().Save(o1)).(*model.Post)
|
||||
|
||||
o1a := &model.Post{}
|
||||
o1a.ChannelId = c1.Id
|
||||
o1a.UserId = model.NewId()
|
||||
o1a.CreateAt = o1.CreateAt
|
||||
o1a.Message = "a" + model.NewId() + "b"
|
||||
o1a = Must(store.Post().Save(o1a)).(*model.Post)
|
||||
|
||||
o2 := &model.Post{}
|
||||
o2.ChannelId = c1.Id
|
||||
o2.UserId = model.NewId()
|
||||
o2.CreateAt = o1.CreateAt - (1000 * 60 * 60 * 24)
|
||||
o2.Message = "a" + model.NewId() + "b"
|
||||
o2 = Must(store.Post().Save(o2)).(*model.Post)
|
||||
|
||||
o2a := &model.Post{}
|
||||
o2a.ChannelId = c1.Id
|
||||
o2a.UserId = o2.UserId
|
||||
o2a.CreateAt = o1.CreateAt - (1000 * 60 * 60 * 24)
|
||||
o2a.Message = "a" + model.NewId() + "b"
|
||||
o2a = Must(store.Post().Save(o2a)).(*model.Post)
|
||||
|
||||
if r1 := <-store.Post().AnalyticsUserCountsWithPostsByDay(t1.Id); r1.Err != nil {
|
||||
t.Fatal(r1.Err)
|
||||
} else {
|
||||
row1 := r1.Data.(model.AnalyticsRows)[0]
|
||||
if row1.Value != 2 {
|
||||
t.Fatal("wrong value")
|
||||
}
|
||||
|
||||
row2 := r1.Data.(model.AnalyticsRows)[1]
|
||||
if row2.Value != 1 {
|
||||
t.Fatal("wrong value")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostCountsByDay(t *testing.T) {
|
||||
Setup()
|
||||
|
||||
t1 := &model.Team{}
|
||||
t1.DisplayName = "DisplayName"
|
||||
t1.Name = "a" + model.NewId() + "b"
|
||||
t1.Email = model.NewId() + "@nowhere.com"
|
||||
t1.Type = model.TEAM_OPEN
|
||||
t1 = Must(store.Team().Save(t1)).(*model.Team)
|
||||
|
||||
c1 := &model.Channel{}
|
||||
c1.TeamId = t1.Id
|
||||
c1.DisplayName = "Channel2"
|
||||
c1.Name = "a" + model.NewId() + "b"
|
||||
c1.Type = model.CHANNEL_OPEN
|
||||
c1 = Must(store.Channel().Save(c1)).(*model.Channel)
|
||||
|
||||
o1 := &model.Post{}
|
||||
o1.ChannelId = c1.Id
|
||||
o1.UserId = model.NewId()
|
||||
o1.CreateAt = model.GetMillis()
|
||||
o1.Message = "a" + model.NewId() + "b"
|
||||
o1 = Must(store.Post().Save(o1)).(*model.Post)
|
||||
|
||||
o1a := &model.Post{}
|
||||
o1a.ChannelId = c1.Id
|
||||
o1a.UserId = model.NewId()
|
||||
o1a.CreateAt = o1.CreateAt
|
||||
o1a.Message = "a" + model.NewId() + "b"
|
||||
o1a = Must(store.Post().Save(o1a)).(*model.Post)
|
||||
|
||||
o2 := &model.Post{}
|
||||
o2.ChannelId = c1.Id
|
||||
o2.UserId = model.NewId()
|
||||
o2.CreateAt = o1.CreateAt - (1000 * 60 * 60 * 24)
|
||||
o2.Message = "a" + model.NewId() + "b"
|
||||
o2 = Must(store.Post().Save(o2)).(*model.Post)
|
||||
|
||||
o2a := &model.Post{}
|
||||
o2a.ChannelId = c1.Id
|
||||
o2a.UserId = o2.UserId
|
||||
o2a.CreateAt = o1.CreateAt - (1000 * 60 * 60 * 24)
|
||||
o2a.Message = "a" + model.NewId() + "b"
|
||||
o2a = Must(store.Post().Save(o2a)).(*model.Post)
|
||||
|
||||
if r1 := <-store.Post().AnalyticsPostCountsByDay(t1.Id); r1.Err != nil {
|
||||
t.Fatal(r1.Err)
|
||||
} else {
|
||||
row1 := r1.Data.(model.AnalyticsRows)[0]
|
||||
if row1.Value != 2 {
|
||||
t.Fatal("wrong value")
|
||||
}
|
||||
|
||||
row2 := r1.Data.(model.AnalyticsRows)[1]
|
||||
if row2.Value != 2 {
|
||||
t.Fatal("wrong value")
|
||||
}
|
||||
}
|
||||
|
||||
if r1 := <-store.Post().AnalyticsPostCount(t1.Id); r1.Err != nil {
|
||||
t.Fatal(r1.Err)
|
||||
} else {
|
||||
if r1.Data.(int64) != 4 {
|
||||
t.Fatal("wrong value")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@ type ChannelStore interface {
|
||||
CheckPermissionsToByName(teamId string, channelName string, userId string) StoreChannel
|
||||
UpdateLastViewedAt(channelId string, userId string) StoreChannel
|
||||
IncrementMentionCount(channelId string, userId string) StoreChannel
|
||||
AnalyticsTypeCount(teamId string, channelType string) StoreChannel
|
||||
}
|
||||
|
||||
type PostStore interface {
|
||||
@@ -86,6 +87,9 @@ type PostStore interface {
|
||||
GetEtag(channelId string) StoreChannel
|
||||
Search(teamId string, userId string, params *model.SearchParams) StoreChannel
|
||||
GetForExport(channelId string) StoreChannel
|
||||
AnalyticsUserCountsWithPostsByDay(teamId string) StoreChannel
|
||||
AnalyticsPostCountsByDay(teamId string) StoreChannel
|
||||
AnalyticsPostCount(teamId string) StoreChannel
|
||||
}
|
||||
|
||||
type UserStore interface {
|
||||
|
||||
@@ -18,6 +18,7 @@ var SqlSettingsTab = require('./sql_settings.jsx');
|
||||
var TeamSettingsTab = require('./team_settings.jsx');
|
||||
var ServiceSettingsTab = require('./service_settings.jsx');
|
||||
var TeamUsersTab = require('./team_users.jsx');
|
||||
var TeamAnalyticsTab = require('./team_analytics.jsx');
|
||||
|
||||
export default class AdminController extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -149,6 +150,10 @@ export default class AdminController extends React.Component {
|
||||
if (this.state.teams) {
|
||||
tab = <TeamUsersTab team={this.state.teams[this.state.selectedTeam]} />;
|
||||
}
|
||||
} else if (this.state.selected === 'team_analytics') {
|
||||
if (this.state.teams) {
|
||||
tab = <TeamAnalyticsTab team={this.state.teams[this.state.selectedTeam]} />;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ export default class AdminSidebar extends React.Component {
|
||||
handleClick(name, teamId, e) {
|
||||
e.preventDefault();
|
||||
this.props.selectTab(name, teamId);
|
||||
history.pushState({name: name, teamId: teamId}, null, `/admin_console/${name}/${teamId || ''}`);
|
||||
history.pushState({name, teamId}, null, `/admin_console/${name}/${teamId || ''}`);
|
||||
}
|
||||
|
||||
isSelected(name, teamId) {
|
||||
@@ -121,6 +121,15 @@ export default class AdminSidebar extends React.Component {
|
||||
{'- Users'}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href='#'
|
||||
className={this.isSelected('team_analytics', team.id)}
|
||||
onClick={this.handleClick.bind(this, 'team_analytics', team.id)}
|
||||
>
|
||||
{'- Analytics'}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
144
web/react/components/admin_console/team_analytics.jsx
Normal file
144
web/react/components/admin_console/team_analytics.jsx
Normal file
@@ -0,0 +1,144 @@
|
||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
var Client = require('../../utils/client.jsx');
|
||||
var LoadingScreen = require('../loading_screen.jsx');
|
||||
|
||||
export default class UserList extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.getData = this.getData.bind(this);
|
||||
|
||||
this.state = {
|
||||
users: null,
|
||||
serverError: null,
|
||||
channel_open_count: null,
|
||||
channel_private_count: null,
|
||||
post_count: null
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getData(this.props.team.id);
|
||||
}
|
||||
|
||||
getData(teamId) {
|
||||
Client.getAnalytics(
|
||||
teamId,
|
||||
'standard',
|
||||
(data) => {
|
||||
for (var index in data) {
|
||||
if (data[index].name === 'channel_open_count') {
|
||||
this.setState({channel_open_count: data[index].value});
|
||||
}
|
||||
|
||||
if (data[index].name === 'channel_private_count') {
|
||||
this.setState({channel_private_count: data[index].value});
|
||||
}
|
||||
|
||||
if (data[index].name === 'post_count') {
|
||||
this.setState({post_count: data[index].value});
|
||||
}
|
||||
}
|
||||
},
|
||||
(err) => {
|
||||
this.setState({serverError: err.message});
|
||||
}
|
||||
);
|
||||
|
||||
Client.getProfilesForTeam(
|
||||
teamId,
|
||||
(users) => {
|
||||
this.setState({users});
|
||||
|
||||
// var memberList = [];
|
||||
// for (var id in users) {
|
||||
// if (users.hasOwnProperty(id)) {
|
||||
// memberList.push(users[id]);
|
||||
// }
|
||||
// }
|
||||
|
||||
// memberList.sort((a, b) => {
|
||||
// if (a.username < b.username) {
|
||||
// return -1;
|
||||
// }
|
||||
|
||||
// if (a.username > b.username) {
|
||||
// return 1;
|
||||
// }
|
||||
|
||||
// return 0;
|
||||
// });
|
||||
},
|
||||
(err) => {
|
||||
this.setState({serverError: err.message});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(newProps) {
|
||||
this.setState({
|
||||
users: null,
|
||||
serverError: null,
|
||||
channel_open_count: null,
|
||||
channel_private_count: null,
|
||||
post_count: null
|
||||
});
|
||||
|
||||
this.getData(newProps.team.id);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
}
|
||||
|
||||
render() {
|
||||
var serverError = '';
|
||||
if (this.state.serverError) {
|
||||
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
|
||||
}
|
||||
|
||||
var totalCount = (
|
||||
<div className='total-count text-center'>
|
||||
<div>{'Total Users'}</div>
|
||||
<div>{this.state.users == null ? 'Loading...' : Object.keys(this.state.users).length}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
var openChannelCount = (
|
||||
<div className='total-count text-center'>
|
||||
<div>{'Public Groups'}</div>
|
||||
<div>{this.state.channel_open_count == null ? 'Loading...' : this.state.channel_open_count}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
var openPrivateCount = (
|
||||
<div className='total-count text-center'>
|
||||
<div>{'Private Groups'}</div>
|
||||
<div>{this.state.channel_private_count == null ? 'Loading...' : this.state.channel_private_count}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
var postCount = (
|
||||
<div className='total-count text-center'>
|
||||
<div>{'Total Posts'}</div>
|
||||
<div>{this.state.post_count == null ? 'Loading...' : this.state.post_count}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className='wrapper--fixed'>
|
||||
<h2>{'Analytics for ' + this.props.team.name}</h2>
|
||||
{serverError}
|
||||
{totalCount}
|
||||
{postCount}
|
||||
{openChannelCount}
|
||||
{openPrivateCount}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
UserList.propTypes = {
|
||||
team: React.PropTypes.object
|
||||
};
|
||||
@@ -327,6 +327,20 @@ export function getConfig(success, error) {
|
||||
});
|
||||
}
|
||||
|
||||
export function getAnalytics(teamId, name, success, error) {
|
||||
$.ajax({
|
||||
url: '/api/v1/admin/analytics/' + teamId + '/' + name,
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
type: 'GET',
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('getAnalytics', xhr, status, err);
|
||||
error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function saveConfig(config, success, error) {
|
||||
$.ajax({
|
||||
url: '/api/v1/admin/save_config',
|
||||
|
||||
@@ -5,6 +5,22 @@
|
||||
.table {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.total-count {
|
||||
width: 175px;
|
||||
height: 100px;
|
||||
border: 1px solid #ddd;
|
||||
padding: 22px 10px 10px 10px;
|
||||
margin: 10px 10px 10px 10px;
|
||||
background: #fff;
|
||||
float: left;
|
||||
|
||||
> div {
|
||||
font-size: 18px;
|
||||
font-weight: 300;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar--left {
|
||||
&.sidebar--collapsable {
|
||||
background: #333;
|
||||
|
||||
Reference in New Issue
Block a user