Adding analytics tab

This commit is contained in:
=Corey Hulen
2015-10-22 18:04:06 -07:00
parent 649f42e3fc
commit ae5d189803
15 changed files with 779 additions and 2 deletions

View File

@@ -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")
}
}

View File

@@ -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
View 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
}
}

View 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")
}
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -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) {

View File

@@ -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
}

View File

@@ -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")
}
}
}

View File

@@ -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 {

View File

@@ -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]} />;
}
}
}

View File

@@ -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>

View 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
};

View File

@@ -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',

View File

@@ -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;