diff --git a/web/react/components/admin_console/admin_controller.jsx b/web/react/components/admin_console/admin_controller.jsx
index 0f85c238da..db98d8f35b 100644
--- a/web/react/components/admin_console/admin_controller.jsx
+++ b/web/react/components/admin_console/admin_controller.jsx
@@ -1,4 +1,4 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import AdminSidebar from './admin_sidebar.jsx';
@@ -23,6 +23,7 @@ import TeamUsersTab from './team_users.jsx';
import TeamAnalyticsTab from './team_analytics.jsx';
import LdapSettingsTab from './ldap_settings.jsx';
import LicenseSettingsTab from './license_settings.jsx';
+import SystemAnalyticsTab from './system_analytics.jsx';
export default class AdminController extends React.Component {
constructor(props) {
@@ -165,6 +166,8 @@ export default class AdminController extends React.Component {
if (this.state.teams) {
tab = ;
}
+ } else if (this.state.selected === 'system_analytics') {
+ tab = ;
}
}
diff --git a/web/react/components/admin_console/admin_sidebar.jsx b/web/react/components/admin_console/admin_sidebar.jsx
index 5a5eaa055d..66f82c55b2 100644
--- a/web/react/components/admin_console/admin_sidebar.jsx
+++ b/web/react/components/admin_console/admin_sidebar.jsx
@@ -192,6 +192,25 @@ export default class AdminSidebar extends React.Component {
-
+
+ -
+
+
+ {'SITE REPORTS'}
+
+
+
+
-
diff --git a/web/react/components/admin_console/analytics.jsx b/web/react/components/admin_console/analytics.jsx
index 4349719c1f..8fdf538f8a 100644
--- a/web/react/components/admin_console/analytics.jsx
+++ b/web/react/components/admin_console/analytics.jsx
@@ -94,8 +94,8 @@ export default class Analytics extends React.Component {
var usersWithPostsByDay = (
-
{'Total Posts'}
-
{'Loading...'}
+
{'Active Users With Posts'}
+
{'Loading...'}
);
@@ -125,98 +125,102 @@ export default class Analytics extends React.Component {
);
}
- var recentActiveUser = (
-
-
{'Recent Active Users'}
-
{'Loading...'}
-
- );
-
+ let recentActiveUser;
if (this.props.recentActiveUsers != null) {
+ let content;
+ if (this.props.recentActiveUsers.length === 0) {
+ content = 'Loading...';
+ } else {
+ content = (
+
+
+ {
+ this.props.recentActiveUsers.map((user) => {
+ const tooltip = (
+
+ {user.email}
+
+ );
+
+ return (
+
+ |
+
+
+
+ |
+ {Utils.displayDateTime(user.last_activity_at)} |
+
+ );
+ })
+ }
+
+
+ );
+ }
recentActiveUser = (
{'Recent Active Users'}
-
-
- {
- this.props.recentActiveUsers.map((user) => {
- const tooltip = (
-
- {user.email}
-
- );
-
- return (
-
- |
-
-
-
- |
- {Utils.displayDateTime(user.last_activity_at)} |
-
- );
- })
- }
-
-
+ {content}
);
}
- var newUsers = (
-
-
{'Newly Created Users'}
-
{'Loading...'}
-
- );
-
+ let newUsers;
if (this.props.newlyCreatedUsers != null) {
+ let content;
+ if (this.props.newlyCreatedUsers.length === 0) {
+ content = 'Loading...';
+ } else {
+ content = (
+
+
+ {
+ this.props.newlyCreatedUsers.map((user) => {
+ const tooltip = (
+
+ {user.email}
+
+ );
+
+ return (
+
+ |
+
+
+
+ |
+ {Utils.displayDateTime(user.create_at)} |
+
+ );
+ })
+ }
+
+
+ );
+ }
newUsers = (
{'Newly Created Users'}
-
-
- {
- this.props.newlyCreatedUsers.map((user) => {
- const tooltip = (
-
- {user.email}
-
- );
-
- return (
-
- |
-
-
-
- |
- {Utils.displayDateTime(user.create_at)} |
-
- );
- })
- }
-
-
+ {content}
@@ -250,7 +254,6 @@ export default class Analytics extends React.Component {
Analytics.defaultProps = {
title: null,
- users: null,
channelOpenCount: null,
channelPrivateCount: null,
postCount: null,
@@ -264,7 +267,6 @@ Analytics.defaultProps = {
Analytics.propTypes = {
title: React.PropTypes.string,
- users: React.PropTypes.object,
channelOpenCount: React.PropTypes.number,
channelPrivateCount: React.PropTypes.number,
postCount: React.PropTypes.number,
diff --git a/web/react/components/admin_console/system_analytics.jsx b/web/react/components/admin_console/system_analytics.jsx
new file mode 100644
index 0000000000..640f17ff00
--- /dev/null
+++ b/web/react/components/admin_console/system_analytics.jsx
@@ -0,0 +1,161 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import Analytics from './analytics.jsx';
+import * as Client from '../../utils/client.jsx';
+
+export default class SystemAnalytics extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getData = this.getData.bind(this);
+
+ this.state = { // most of this state should be from a store in the future
+ users: null,
+ serverError: null,
+ channel_open_count: null,
+ channel_private_count: null,
+ post_count: null,
+ post_counts_day: null,
+ user_counts_with_posts_day: null,
+ recent_active_users: null,
+ newly_created_users: null,
+ unique_user_count: null
+ };
+ }
+
+ componentDidMount() {
+ this.getData();
+ }
+
+ getData() { // should be moved to an action creator eventually
+ Client.getSystemAnalytics(
+ '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});
+ }
+
+ if (data[index].name === 'unique_user_count') {
+ this.setState({unique_user_count: data[index].value});
+ }
+ }
+ },
+ (err) => {
+ this.setState({serverError: err.message});
+ }
+ );
+
+ Client.getSystemAnalytics(
+ 'post_counts_day',
+ (data) => {
+ data.reverse();
+
+ var chartData = {
+ labels: [],
+ datasets: [{
+ label: 'Total Posts',
+ fillColor: 'rgba(151,187,205,0.2)',
+ strokeColor: 'rgba(151,187,205,1)',
+ pointColor: 'rgba(151,187,205,1)',
+ pointStrokeColor: '#fff',
+ pointHighlightFill: '#fff',
+ pointHighlightStroke: 'rgba(151,187,205,1)',
+ data: []
+ }]
+ };
+
+ for (var index in data) {
+ if (data[index]) {
+ var row = data[index];
+ chartData.labels.push(row.name);
+ chartData.datasets[0].data.push(row.value);
+ }
+ }
+
+ this.setState({post_counts_day: chartData});
+ },
+ (err) => {
+ this.setState({serverError: err.message});
+ }
+ );
+
+ Client.getSystemAnalytics(
+ 'user_counts_with_posts_day',
+ (data) => {
+ data.reverse();
+
+ var chartData = {
+ labels: [],
+ datasets: [{
+ label: 'Active Users With Posts',
+ fillColor: 'rgba(151,187,205,0.2)',
+ strokeColor: 'rgba(151,187,205,1)',
+ pointColor: 'rgba(151,187,205,1)',
+ pointStrokeColor: '#fff',
+ pointHighlightFill: '#fff',
+ pointHighlightStroke: 'rgba(151,187,205,1)',
+ data: []
+ }]
+ };
+
+ for (var index in data) {
+ if (data[index]) {
+ var row = data[index];
+ chartData.labels.push(row.name);
+ chartData.datasets[0].data.push(row.value);
+ }
+ }
+
+ this.setState({user_counts_with_posts_day: chartData});
+ },
+ (err) => {
+ this.setState({serverError: err.message});
+ }
+ );
+ }
+
+ componentWillReceiveProps(newProps) {
+ this.setState({
+ serverError: null,
+ channel_open_count: null,
+ channel_private_count: null,
+ post_count: null,
+ post_counts_day: null,
+ user_counts_with_posts_day: null,
+ unique_user_count: null
+ });
+
+ this.getData();
+ }
+
+ render() {
+ return (
+
+ );
+ }
+}
+
+SystemAnalytics.propTypes = {
+ team: React.PropTypes.object
+};
diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx
index 8484f3ccee..96fa342a3b 100644
--- a/web/react/utils/client.jsx
+++ b/web/react/utils/client.jsx
@@ -401,7 +401,7 @@ export function getTeamAnalytics(teamId, name, success, error) {
});
}
-export function getServerAnalytics(name, success, error) {
+export function getSystemAnalytics(name, success, error) {
$.ajax({
url: '/api/v1/admin/analytics/' + name,
dataType: 'json',
@@ -409,7 +409,7 @@ export function getServerAnalytics(name, success, error) {
type: 'GET',
success,
error: (xhr, status, err) => {
- var e = handleError('getServerAnalytics', xhr, status, err);
+ var e = handleError('getSystemAnalytics', xhr, status, err);
error(e);
}
});