Refactor and modularize analytics on the client
Этот коммит содержится в:
@@ -21,10 +21,10 @@ import TeamSettingsTab from './team_settings.jsx';
|
||||
import ServiceSettingsTab from './service_settings.jsx';
|
||||
import LegalAndSupportSettingsTab from './legal_and_support_settings.jsx';
|
||||
import TeamUsersTab from './team_users.jsx';
|
||||
import TeamAnalyticsTab from './team_analytics.jsx';
|
||||
import TeamAnalyticsTab from '../analytics/team_analytics.jsx';
|
||||
import LdapSettingsTab from './ldap_settings.jsx';
|
||||
import LicenseSettingsTab from './license_settings.jsx';
|
||||
import SystemAnalyticsTab from './system_analytics.jsx';
|
||||
import SystemAnalyticsTab from '../analytics/system_analytics.jsx';
|
||||
|
||||
export default class AdminController extends React.Component {
|
||||
constructor(props) {
|
||||
|
||||
@@ -1,489 +0,0 @@
|
||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import Constants from '../../utils/constants.jsx';
|
||||
import LineChart from './line_chart.jsx';
|
||||
import DoughnutChart from './doughnut_chart.jsx';
|
||||
import StatisticCount from './statistic_count.jsx';
|
||||
|
||||
var Tooltip = ReactBootstrap.Tooltip;
|
||||
var OverlayTrigger = ReactBootstrap.OverlayTrigger;
|
||||
|
||||
import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedDate} from 'mm-intl';
|
||||
|
||||
const holders = defineMessages({
|
||||
analyticsTotalUsers: {
|
||||
id: 'admin.analytics.totalUsers',
|
||||
defaultMessage: 'Total Users'
|
||||
},
|
||||
analyticsPublicChannels: {
|
||||
id: 'admin.analytics.publicChannels',
|
||||
defaultMessage: 'Public Channels'
|
||||
},
|
||||
analyticsPrivateGroups: {
|
||||
id: 'admin.analytics.privateGroups',
|
||||
defaultMessage: 'Private Groups'
|
||||
},
|
||||
analyticsTotalPosts: {
|
||||
id: 'admin.analytics.totalPosts',
|
||||
defaultMessage: 'Total Posts'
|
||||
},
|
||||
analyticsFilePosts: {
|
||||
id: 'admin.analytics.totalFilePosts',
|
||||
defaultMessage: 'Posts with Files'
|
||||
},
|
||||
analyticsHashtagPosts: {
|
||||
id: 'admin.analytics.totalHashtagPosts',
|
||||
defaultMessage: 'Posts with Hashtags'
|
||||
},
|
||||
analyticsIncomingHooks: {
|
||||
id: 'admin.analytics.totalIncomingWebhooks',
|
||||
defaultMessage: 'Incoming Webhooks'
|
||||
},
|
||||
analyticsOutgoingHooks: {
|
||||
id: 'admin.analytics.totalOutgoingWebhooks',
|
||||
defaultMessage: 'Outgoing Webhooks'
|
||||
},
|
||||
analyticsChannelTypes: {
|
||||
id: 'admin.analytics.channelTypes',
|
||||
defaultMessage: 'Channel Types'
|
||||
},
|
||||
analyticsTextPosts: {
|
||||
id: 'admin.analytics.textPosts',
|
||||
defaultMessage: 'Posts with Text-only'
|
||||
},
|
||||
analyticsPostTypes: {
|
||||
id: 'admin.analytics.postTypes',
|
||||
defaultMessage: 'Posts, Files and Hashtags'
|
||||
}
|
||||
});
|
||||
|
||||
export default class Analytics extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
render() { // in the future, break down these into smaller components
|
||||
const {formatMessage} = this.props.intl;
|
||||
|
||||
var serverError = '';
|
||||
if (this.props.serverError) {
|
||||
serverError = <div className='form-group has-error'><label className='control-label'>{this.props.serverError}</label></div>;
|
||||
}
|
||||
|
||||
let loading = (
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
id='admin.analytics.loading'
|
||||
defaultMessage='Loading...'
|
||||
/>
|
||||
</h5>
|
||||
);
|
||||
|
||||
let firstRow;
|
||||
let extraGraphs;
|
||||
if (this.props.showAdvanced) {
|
||||
firstRow = (
|
||||
<div className='row'>
|
||||
<StatisticCount
|
||||
title={formatMessage(holders.analyticsTotalUsers)}
|
||||
icon='fa-users'
|
||||
count={this.props.uniqueUserCount}
|
||||
/>
|
||||
<StatisticCount
|
||||
title={formatMessage(holders.analyticsTotalPosts)}
|
||||
icon='fa-comment'
|
||||
count={this.props.postCount}
|
||||
/>
|
||||
<StatisticCount
|
||||
title={formatMessage(holders.analyticsIncomingHooks)}
|
||||
icon='fa-arrow-down'
|
||||
count={this.props.incomingWebhookCount}
|
||||
/>
|
||||
<StatisticCount
|
||||
title={formatMessage(holders.analyticsOutgoingHooks)}
|
||||
icon='fa-arrow-up'
|
||||
count={this.props.outgoingWebhookCount}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
const channelTypeData = [
|
||||
{
|
||||
value: this.props.channelOpenCount,
|
||||
color: '#46BFBD',
|
||||
highlight: '#5AD3D1',
|
||||
label: formatMessage(holders.analyticsPublicChannels)
|
||||
},
|
||||
{
|
||||
value: this.props.channelPrivateCount,
|
||||
color: '#FDB45C',
|
||||
highlight: '#FFC870',
|
||||
label: formatMessage(holders.analyticsPrivateGroups)
|
||||
}
|
||||
];
|
||||
|
||||
const postTypeData = [
|
||||
{
|
||||
value: this.props.filePostCount,
|
||||
color: '#46BFBD',
|
||||
highlight: '#5AD3D1',
|
||||
label: formatMessage(holders.analyticsFilePosts)
|
||||
},
|
||||
{
|
||||
value: this.props.filePostCount,
|
||||
color: '#F7464A',
|
||||
highlight: '#FF5A5E',
|
||||
label: formatMessage(holders.analyticsHashtagPosts)
|
||||
},
|
||||
{
|
||||
value: this.props.postCount - this.props.filePostCount - this.props.hashtagPostCount,
|
||||
color: '#FDB45C',
|
||||
highlight: '#FFC870',
|
||||
label: formatMessage(holders.analyticsTextPosts)
|
||||
}
|
||||
];
|
||||
|
||||
extraGraphs = (
|
||||
<div className='row'>
|
||||
<DoughnutChart
|
||||
title={formatMessage(holders.analyticsChannelTypes)}
|
||||
data={channelTypeData}
|
||||
width='300'
|
||||
height='225'
|
||||
/>
|
||||
<DoughnutChart
|
||||
title={formatMessage(holders.analyticsPostTypes)}
|
||||
data={postTypeData}
|
||||
width='300'
|
||||
height='225'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
firstRow = (
|
||||
<div className='row'>
|
||||
<StatisticCount
|
||||
title={formatMessage(holders.analyticsTotalUsers)}
|
||||
icon='fa-users'
|
||||
count={this.props.uniqueUserCount}
|
||||
/>
|
||||
<StatisticCount
|
||||
title={formatMessage(holders.analyticsPublicChannels)}
|
||||
icon='fa-globe'
|
||||
count={this.props.channelOpenCount}
|
||||
/>
|
||||
<StatisticCount
|
||||
title={formatMessage(holders.analyticsPrivateGroups)}
|
||||
icon='fa-lock'
|
||||
count={this.props.channelPrivateCount}
|
||||
/>
|
||||
<StatisticCount
|
||||
title={formatMessage(holders.analyticsTotalPosts)}
|
||||
icon='fa-comment'
|
||||
count={this.props.postCount}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let postCountsByDay;
|
||||
if (this.props.postCountsDay == null) {
|
||||
postCountsByDay = (
|
||||
<div className='col-sm-12'>
|
||||
<div className='total-count by-day'>
|
||||
<div className='title'>
|
||||
<FormattedMessage
|
||||
id='admin.analytics.totalPosts'
|
||||
defaultMessage='Total Posts'
|
||||
/>
|
||||
</div>
|
||||
<div className='content'>{loading}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
let content;
|
||||
if (this.props.postCountsDay.labels.length === 0) {
|
||||
content = (
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
id='admin.analytics.meaningful'
|
||||
defaultMessage='Not enough data for a meaningful representation.'
|
||||
/>
|
||||
</h5>
|
||||
);
|
||||
} else {
|
||||
content = (
|
||||
<LineChart
|
||||
data={this.props.postCountsDay}
|
||||
width='740'
|
||||
height='225'
|
||||
/>
|
||||
);
|
||||
}
|
||||
postCountsByDay = (
|
||||
<div className='col-sm-12'>
|
||||
<div className='total-count by-day'>
|
||||
<div className='title'>
|
||||
<FormattedMessage
|
||||
id='admin.analytics.totalPosts'
|
||||
defaultMessage='Total Posts'
|
||||
/>
|
||||
</div>
|
||||
<div className='content'>
|
||||
{content}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let usersWithPostsByDay;
|
||||
if (this.props.userCountsWithPostsDay == null) {
|
||||
usersWithPostsByDay = (
|
||||
<div className='col-sm-12'>
|
||||
<div className='total-count by-day'>
|
||||
<div className='title'>
|
||||
<FormattedMessage
|
||||
id='admin.analytics.activeUsers'
|
||||
defaultMessage='Active Users With Posts'
|
||||
/>
|
||||
</div>
|
||||
<div className='content'>{loading}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
let content;
|
||||
if (this.props.userCountsWithPostsDay.labels.length === 0) {
|
||||
content = (
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
id='admin.analytics.meaningful'
|
||||
defaultMessage='Not enough data for a meaningful representation.'
|
||||
/>
|
||||
</h5>
|
||||
);
|
||||
} else {
|
||||
content = (
|
||||
<LineChart
|
||||
data={this.props.userCountsWithPostsDay}
|
||||
width='740'
|
||||
height='225'
|
||||
/>
|
||||
);
|
||||
}
|
||||
usersWithPostsByDay = (
|
||||
<div className='col-sm-12'>
|
||||
<div className='total-count by-day'>
|
||||
<div className='title'>
|
||||
<FormattedMessage
|
||||
id='admin.analytics.activeUsers'
|
||||
defaultMessage='Active Users With Posts'
|
||||
/>
|
||||
</div>
|
||||
<div className='content'>
|
||||
{content}
|
||||
</div>
|
||||
</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>
|
||||
<FormattedDate
|
||||
value={user.last_activity_at}
|
||||
day='numeric'
|
||||
month='long'
|
||||
year='numeric'
|
||||
hour12={true}
|
||||
hour='2-digit'
|
||||
minute='2-digit'
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
recentActiveUser = (
|
||||
<div className='col-sm-6'>
|
||||
<div className='total-count recent-active-users'>
|
||||
<div className='title'>
|
||||
<FormattedMessage
|
||||
id='admin.analytics.recentActive'
|
||||
defaultMessage='Recent Active Users'
|
||||
/>
|
||||
</div>
|
||||
<div className='content'>
|
||||
{content}
|
||||
</div>
|
||||
</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>
|
||||
<FormattedDate
|
||||
value={user.create_at}
|
||||
day='numeric'
|
||||
month='long'
|
||||
year='numeric'
|
||||
hour12={true}
|
||||
hour='2-digit'
|
||||
minute='2-digit'
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
newUsers = (
|
||||
<div className='col-sm-6'>
|
||||
<div className='total-count recent-active-users'>
|
||||
<div className='title'>
|
||||
<FormattedMessage
|
||||
id='admin.analytics.newlyCreated'
|
||||
defaultMessage='Newly Created Users'
|
||||
/>
|
||||
</div>
|
||||
<div className='content'>
|
||||
{content}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='wrapper--fixed team_statistics'>
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id='admin.analytics.title'
|
||||
defaultMessage='Statistics for {title}'
|
||||
values={{
|
||||
title: this.props.title
|
||||
}}
|
||||
/>
|
||||
</h3>
|
||||
{serverError}
|
||||
{firstRow}
|
||||
{extraGraphs}
|
||||
<div className='row'>
|
||||
{postCountsByDay}
|
||||
</div>
|
||||
<div className='row'>
|
||||
{usersWithPostsByDay}
|
||||
</div>
|
||||
<div className='row'>
|
||||
{recentActiveUser}
|
||||
{newUsers}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Analytics.defaultProps = {
|
||||
title: null,
|
||||
channelOpenCount: null,
|
||||
channelPrivateCount: null,
|
||||
postCount: null,
|
||||
postCountsDay: null,
|
||||
userCountsWithPostsDay: null,
|
||||
recentActiveUsers: null,
|
||||
newlyCreatedUsers: null,
|
||||
uniqueUserCount: null,
|
||||
serverError: null
|
||||
};
|
||||
|
||||
Analytics.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
title: React.PropTypes.string,
|
||||
channelOpenCount: React.PropTypes.number,
|
||||
channelPrivateCount: React.PropTypes.number,
|
||||
postCount: React.PropTypes.number,
|
||||
showAdvanced: React.PropTypes.bool,
|
||||
filePostCount: React.PropTypes.number,
|
||||
hashtagPostCount: React.PropTypes.number,
|
||||
incomingWebhookCount: React.PropTypes.number,
|
||||
outgoingWebhookCount: React.PropTypes.number,
|
||||
postCountsDay: React.PropTypes.object,
|
||||
userCountsWithPostsDay: React.PropTypes.object,
|
||||
recentActiveUsers: React.PropTypes.array,
|
||||
newlyCreatedUsers: React.PropTypes.array,
|
||||
uniqueUserCount: React.PropTypes.number,
|
||||
serverError: React.PropTypes.string
|
||||
};
|
||||
|
||||
export default injectIntl(Analytics);
|
||||
@@ -1,77 +0,0 @@
|
||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {FormattedMessage} from 'mm-intl';
|
||||
|
||||
export default class DoughnutChart extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.initChart = this.initChart.bind(this);
|
||||
this.chart = null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.initChart(this.props);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.chart) {
|
||||
this.chart.destroy();
|
||||
this.initChart(nextProps);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.chart) {
|
||||
this.chart.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
initChart(props) {
|
||||
var el = ReactDOM.findDOMNode(this.refs.canvas);
|
||||
var ctx = el.getContext('2d');
|
||||
this.chart = new Chart(ctx).Doughnut(props.data, props.options || {}); //eslint-disable-line new-cap
|
||||
}
|
||||
|
||||
render() {
|
||||
let content;
|
||||
if (this.props.data == null) {
|
||||
content = (
|
||||
<FormattedMessage
|
||||
id='admin.analytics.loading'
|
||||
defaultMessage='Loading...'
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
content = (
|
||||
<canvas
|
||||
ref='canvas'
|
||||
width={this.props.width}
|
||||
height={this.props.height}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='col-sm-6'>
|
||||
<div className='total-count'>
|
||||
<div className='title'>
|
||||
{this.props.title}
|
||||
</div>
|
||||
<div className='content'>
|
||||
{content}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DoughnutChart.propTypes = {
|
||||
title: React.PropTypes.string,
|
||||
width: React.PropTypes.string,
|
||||
height: React.PropTypes.string,
|
||||
data: React.PropTypes.array,
|
||||
options: React.PropTypes.object
|
||||
};
|
||||
@@ -1,50 +0,0 @@
|
||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
export default class LineChart extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.initChart = this.initChart.bind(this);
|
||||
this.chart = null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.initChart(this.props);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.chart) {
|
||||
this.chart.destroy();
|
||||
this.initChart(nextProps);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.chart) {
|
||||
this.chart.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
initChart(props) {
|
||||
var el = ReactDOM.findDOMNode(this);
|
||||
var ctx = el.getContext('2d');
|
||||
this.chart = new Chart(ctx).Line(props.data, props.options || {}); //eslint-disable-line new-cap
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<canvas
|
||||
width={this.props.width}
|
||||
height={this.props.height}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
LineChart.propTypes = {
|
||||
width: React.PropTypes.string,
|
||||
height: React.PropTypes.string,
|
||||
data: React.PropTypes.object,
|
||||
options: React.PropTypes.object
|
||||
};
|
||||
@@ -1,33 +0,0 @@
|
||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {FormattedMessage} from 'mm-intl';
|
||||
|
||||
export default class StatisticCount extends React.Component {
|
||||
render() {
|
||||
let loading = (
|
||||
<FormattedMessage
|
||||
id='admin.analytics.loading'
|
||||
defaultMessage='Loading...'
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className='col-sm-3'>
|
||||
<div className='total-count'>
|
||||
<div className='title'>
|
||||
{this.props.title}
|
||||
<i className={'fa ' + this.props.icon}/>
|
||||
</div>
|
||||
<div className='content'>{this.props.count == null ? loading : this.props.count}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
StatisticCount.propTypes = {
|
||||
title: React.PropTypes.string.isRequired,
|
||||
icon: React.PropTypes.string.isRequired,
|
||||
count: React.PropTypes.number
|
||||
};
|
||||
@@ -1,216 +0,0 @@
|
||||
// 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';
|
||||
|
||||
import {injectIntl, intlShape, defineMessages} from 'mm-intl';
|
||||
|
||||
const labels = defineMessages({
|
||||
totalPosts: {
|
||||
id: 'admin.system_analytics.totalPosts',
|
||||
defaultMessage: 'Total Posts'
|
||||
},
|
||||
activeUsers: {
|
||||
id: 'admin.system_analytics.activeUsers',
|
||||
defaultMessage: 'Active Users With Posts'
|
||||
},
|
||||
title: {
|
||||
id: 'admin.system_analytics.title',
|
||||
defaultMessage: 'the System'
|
||||
}
|
||||
});
|
||||
|
||||
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
|
||||
const {formatMessage} = this.props.intl;
|
||||
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: formatMessage(labels.totalPosts),
|
||||
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: formatMessage(labels.activeUsers),
|
||||
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});
|
||||
}
|
||||
);
|
||||
|
||||
if (global.window.mm_license.IsLicensed === 'true') {
|
||||
Client.getSystemAnalytics(
|
||||
'extra_counts',
|
||||
(data) => {
|
||||
for (var index in data) {
|
||||
if (data[index].name === 'file_post_count') {
|
||||
this.setState({file_post_count: data[index].value});
|
||||
}
|
||||
|
||||
if (data[index].name === 'hashtag_post_count') {
|
||||
this.setState({hashtag_post_count: data[index].value});
|
||||
}
|
||||
|
||||
if (data[index].name === 'incoming_webhook_count') {
|
||||
this.setState({incoming_webhook_count: data[index].value});
|
||||
}
|
||||
|
||||
if (data[index].name === 'outgoing_webhook_count') {
|
||||
this.setState({outgoing_webhook_count: data[index].value});
|
||||
}
|
||||
}
|
||||
},
|
||||
(err) => {
|
||||
this.setState({serverError: err.message});
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps() {
|
||||
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
|
||||
intl={this.props.intl}
|
||||
title={this.props.intl.formatMessage(labels.title)}
|
||||
channelOpenCount={this.state.channel_open_count}
|
||||
channelPrivateCount={this.state.channel_private_count}
|
||||
postCount={this.state.post_count}
|
||||
showAdvanced={global.window.mm_license.IsLicensed === 'true'}
|
||||
filePostCount={this.state.file_post_count}
|
||||
hashtagPostCount={this.state.hashtag_post_count}
|
||||
incomingWebhookCount={this.state.incoming_webhook_count}
|
||||
outgoingWebhookCount={this.state.outgoing_webhook_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 = {
|
||||
intl: intlShape.isRequired,
|
||||
team: React.PropTypes.object
|
||||
};
|
||||
|
||||
export default injectIntl(SystemAnalytics);
|
||||
@@ -1,253 +0,0 @@
|
||||
// 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';
|
||||
|
||||
import {injectIntl, intlShape, defineMessages} from 'mm-intl';
|
||||
|
||||
const labels = defineMessages({
|
||||
totalPosts: {
|
||||
id: 'admin.team_analytics.totalPosts',
|
||||
defaultMessage: 'Total Posts'
|
||||
},
|
||||
activeUsers: {
|
||||
id: 'admin.team_analytics.activeUsers',
|
||||
defaultMessage: 'Active Users With Posts'
|
||||
}
|
||||
});
|
||||
|
||||
class TeamAnalytics 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(this.props.team.id);
|
||||
}
|
||||
|
||||
getData(teamId) { // should be moved to an action creator eventually
|
||||
const {formatMessage} = this.props.intl;
|
||||
Client.getTeamAnalytics(
|
||||
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});
|
||||
}
|
||||
|
||||
if (data[index].name === 'unique_user_count') {
|
||||
this.setState({unique_user_count: data[index].value});
|
||||
}
|
||||
}
|
||||
},
|
||||
(err) => {
|
||||
this.setState({serverError: err.message});
|
||||
}
|
||||
);
|
||||
|
||||
Client.getTeamAnalytics(
|
||||
teamId,
|
||||
'post_counts_day',
|
||||
(data) => {
|
||||
data.reverse();
|
||||
|
||||
var chartData = {
|
||||
labels: [],
|
||||
datasets: [{
|
||||
label: formatMessage(labels.totalPosts),
|
||||
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.getTeamAnalytics(
|
||||
teamId,
|
||||
'user_counts_with_posts_day',
|
||||
(data) => {
|
||||
data.reverse();
|
||||
|
||||
var chartData = {
|
||||
labels: [],
|
||||
datasets: [{
|
||||
label: formatMessage(labels.activeUsers),
|
||||
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});
|
||||
}
|
||||
);
|
||||
|
||||
Client.getProfilesForTeam(
|
||||
teamId,
|
||||
(users) => {
|
||||
this.setState({users});
|
||||
|
||||
var usersList = [];
|
||||
for (var id in users) {
|
||||
if (users.hasOwnProperty(id)) {
|
||||
usersList.push(users[id]);
|
||||
}
|
||||
}
|
||||
|
||||
usersList.sort((a, b) => {
|
||||
if (a.last_activity_at < b.last_activity_at) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (a.last_activity_at > b.last_activity_at) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
var recentActive = [];
|
||||
for (let i = 0; i < usersList.length; i++) {
|
||||
if (usersList[i].last_activity_at == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
recentActive.push(usersList[i]);
|
||||
if (i > 19) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({recent_active_users: recentActive});
|
||||
|
||||
usersList.sort((a, b) => {
|
||||
if (a.create_at < b.create_at) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (a.create_at > b.create_at) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
var newlyCreated = [];
|
||||
for (let i = 0; i < usersList.length; i++) {
|
||||
newlyCreated.push(usersList[i]);
|
||||
if (i > 19) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({newly_created_users: newlyCreated});
|
||||
},
|
||||
(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,
|
||||
post_counts_day: null,
|
||||
user_counts_with_posts_day: null,
|
||||
recent_active_users: null,
|
||||
newly_created_users: null,
|
||||
unique_user_count: null
|
||||
});
|
||||
|
||||
this.getData(newProps.team.id);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Analytics
|
||||
intl={this.props.intl}
|
||||
title={this.props.team.name}
|
||||
users={this.state.users}
|
||||
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}
|
||||
recentActiveUsers={this.state.recent_active_users}
|
||||
newlyCreatedUsers={this.state.newly_created_users}
|
||||
uniqueUserCount={this.state.unique_user_count}
|
||||
serverError={this.state.serverError}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TeamAnalytics.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
team: React.PropTypes.object
|
||||
};
|
||||
|
||||
export default injectIntl(TeamAnalytics);
|
||||
Ссылка в новой задаче
Block a user