diff options
author | JoramWilander <jwawilander@gmail.com> | 2016-01-21 12:59:32 -0500 |
---|---|---|
committer | JoramWilander <jwawilander@gmail.com> | 2016-01-21 12:59:32 -0500 |
commit | cefdad6d8c71ff6adf0ae919bd9f9139e02a6caa (patch) | |
tree | fee35b2add76a66172c846bcec1e5cbe753a43fd /web | |
parent | 2a26d857574f2160e3ee5538ad3a84ec47082f86 (diff) | |
download | chat-cefdad6d8c71ff6adf0ae919bd9f9139e02a6caa.tar.gz chat-cefdad6d8c71ff6adf0ae919bd9f9139e02a6caa.tar.bz2 chat-cefdad6d8c71ff6adf0ae919bd9f9139e02a6caa.zip |
Add system analytics page
Diffstat (limited to 'web')
-rw-r--r-- | web/react/components/admin_console/admin_controller.jsx | 5 | ||||
-rw-r--r-- | web/react/components/admin_console/admin_sidebar.jsx | 19 | ||||
-rw-r--r-- | web/react/components/admin_console/analytics.jsx | 158 | ||||
-rw-r--r-- | web/react/components/admin_console/system_analytics.jsx | 161 | ||||
-rw-r--r-- | web/react/utils/client.jsx | 4 |
5 files changed, 266 insertions, 81 deletions
diff --git a/web/react/components/admin_console/admin_controller.jsx b/web/react/components/admin_console/admin_controller.jsx index 0f85c238d..db98d8f35 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 = <TeamAnalyticsTab team={this.state.teams[this.state.selectedTeam]} />; } + } else if (this.state.selected === 'system_analytics') { + tab = <SystemAnalyticsTab />; } } diff --git a/web/react/components/admin_console/admin_sidebar.jsx b/web/react/components/admin_console/admin_sidebar.jsx index 5a5eaa055..66f82c55b 100644 --- a/web/react/components/admin_console/admin_sidebar.jsx +++ b/web/react/components/admin_console/admin_sidebar.jsx @@ -196,6 +196,25 @@ export default class AdminSidebar extends React.Component { <li> <h4> <span className='icon fa fa-gear'></span> + <span>{'SITE REPORTS'}</span> + </h4> + </li> + </ul> + <ul className='nav nav__sub-menu padded'> + <li> + <a + href='#' + className={this.isSelected('system_analytics')} + onClick={this.handleClick.bind(this, 'system_analytics', null)} + > + {'View Statistics'} + </a> + </li> + </ul> + <ul className='nav nav__sub-menu'> + <li> + <h4> + <span className='icon fa fa-gear'></span> <span>{'SETTINGS'}</span> </h4> </li> diff --git a/web/react/components/admin_console/analytics.jsx b/web/react/components/admin_console/analytics.jsx index 4349719c1..8fdf538f8 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 = ( <div className='col-sm-12'> <div className='total-count by-day'> - <div className='title'>{'Total Posts'}</div> - <div>{'Loading...'}</div> + <div className='title'>{'Active Users With Posts'}</div> + <div className='content'>{'Loading...'}</div> </div> </div> ); @@ -125,98 +125,102 @@ export default class Analytics extends React.Component { ); } - var recentActiveUser = ( - <div className='recent-active-users'> - <div>{'Recent Active Users'}</div> - <div>{'Loading...'}</div> - </div> - ); - + let recentActiveUser; if (this.props.recentActiveUsers != null) { + let content; + if (this.props.recentActiveUsers.length === 0) { + content = 'Loading...'; + } else { + content = ( + <table> + <tbody> + { + this.props.recentActiveUsers.map((user) => { + const tooltip = ( + <Tooltip id={'recent-user-email-tooltip-' + user.id}> + {user.email} + </Tooltip> + ); + + return ( + <tr key={'recent-user-table-entry-' + user.id}> + <td> + <OverlayTrigger + delayShow={Constants.OVERLAY_TIME_DELAY} + placement='top' + overlay={tooltip} + > + <time> + {user.username} + </time> + </OverlayTrigger> + </td> + <td>{Utils.displayDateTime(user.last_activity_at)}</td> + </tr> + ); + }) + } + </tbody> + </table> + ); + } recentActiveUser = ( <div className='col-sm-6'> <div className='total-count recent-active-users'> <div className='title'>{'Recent Active Users'}</div> <div className='content'> - <table> - <tbody> - { - this.props.recentActiveUsers.map((user) => { - const tooltip = ( - <Tooltip id={'recent-user-email-tooltip-' + user.id}> - {user.email} - </Tooltip> - ); - - return ( - <tr key={'recent-user-table-entry-' + user.id}> - <td> - <OverlayTrigger - delayShow={Constants.OVERLAY_TIME_DELAY} - placement='top' - overlay={tooltip} - > - <time> - {user.username} - </time> - </OverlayTrigger> - </td> - <td>{Utils.displayDateTime(user.last_activity_at)}</td> - </tr> - ); - }) - } - </tbody> - </table> + {content} </div> </div> </div> ); } - var newUsers = ( - <div className='recent-active-users'> - <div>{'Newly Created Users'}</div> - <div>{'Loading...'}</div> - </div> - ); - + let newUsers; if (this.props.newlyCreatedUsers != null) { + let content; + if (this.props.newlyCreatedUsers.length === 0) { + content = 'Loading...'; + } else { + content = ( + <table> + <tbody> + { + this.props.newlyCreatedUsers.map((user) => { + const tooltip = ( + <Tooltip id={'new-user-email-tooltip-' + user.id}> + {user.email} + </Tooltip> + ); + + return ( + <tr key={'new-user-table-entry-' + user.id}> + <td> + <OverlayTrigger + delayShow={Constants.OVERLAY_TIME_DELAY} + placement='top' + overlay={tooltip} + > + <time> + {user.username} + </time> + </OverlayTrigger> + </td> + <td>{Utils.displayDateTime(user.create_at)}</td> + </tr> + ); + }) + } + </tbody> + </table> + ); + } newUsers = ( <div className='col-sm-6'> <div className='total-count recent-active-users'> <div className='title'>{'Newly Created Users'}</div> <div className='content'> - <table> - <tbody> - { - this.props.newlyCreatedUsers.map((user) => { - const tooltip = ( - <Tooltip id={'new-user-email-tooltip-' + user.id}> - {user.email} - </Tooltip> - ); - - return ( - <tr key={'new-user-table-entry-' + user.id}> - <td> - <OverlayTrigger - delayShow={Constants.OVERLAY_TIME_DELAY} - placement='top' - overlay={tooltip} - > - <time> - {user.username} - </time> - </OverlayTrigger> - </td> - <td>{Utils.displayDateTime(user.create_at)}</td> - </tr> - ); - }) - } - </tbody> - </table> + {content} </div> </div> </div> @@ -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 000000000..640f17ff0 --- /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 ( + <div> + <Analytics + title={'the system'} + channelOpenCount={this.state.channel_open_count} + channelPrivateCount={this.state.channel_private_count} + postCount={this.state.post_count} + postCountsDay={this.state.post_counts_day} + userCountsWithPostsDay={this.state.user_counts_with_posts_day} + uniqueUserCount={this.state.unique_user_count} + serverError={this.state.serverError} + /> + </div> + ); + } +} + +SystemAnalytics.propTypes = { + team: React.PropTypes.object +}; diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx index 8484f3cce..96fa342a3 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); } }); |