Added extra system-wide statistics for EE
Этот коммит содержится в:
@@ -4,11 +4,60 @@
|
||||
import * as Utils from '../../utils/utils.jsx';
|
||||
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 {FormattedMessage} from 'mm-intl';
|
||||
import {injectIntl, intlShape, defineMessages, FormattedMessage} 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) {
|
||||
@@ -18,6 +67,8 @@ export default class Analytics extends React.Component {
|
||||
}
|
||||
|
||||
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>;
|
||||
@@ -30,77 +81,129 @@ export default class Analytics extends React.Component {
|
||||
/>
|
||||
);
|
||||
|
||||
var totalCount = (
|
||||
<div className='col-sm-3'>
|
||||
<div className='total-count'>
|
||||
<div className='title'>
|
||||
<FormattedMessage
|
||||
id='admin.analytics.totalUsers'
|
||||
defaultMessage='Total Users'
|
||||
/>
|
||||
<i className='fa fa-users'/></div>
|
||||
<div className='content'>{this.props.uniqueUserCount == null ? loading : this.props.uniqueUserCount}</div>
|
||||
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>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
|
||||
var openChannelCount = (
|
||||
<div className='col-sm-3'>
|
||||
<div className='total-count'>
|
||||
<div className='title'>
|
||||
<FormattedMessage
|
||||
id='admin.analytics.publicChannels'
|
||||
defaultMessage='Public Channels'
|
||||
/>
|
||||
<i className='fa fa-globe'/></div>
|
||||
<div className='content'>{this.props.channelOpenCount == null ? loading : this.props.channelOpenCount}</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>
|
||||
</div>
|
||||
);
|
||||
|
||||
var openPrivateCount = (
|
||||
<div className='col-sm-3'>
|
||||
<div className='total-count'>
|
||||
<div className='title'>
|
||||
<FormattedMessage
|
||||
id='admin.analytics.privateGroups'
|
||||
defaultMessage='Private Groups'
|
||||
/>
|
||||
<i className='fa fa-lock'/></div>
|
||||
<div className='content'>{this.props.channelPrivateCount == null ? loading : this.props.channelPrivateCount}</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>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
var postCount = (
|
||||
<div className='col-sm-3'>
|
||||
<div className='total-count'>
|
||||
<div className='title'>
|
||||
<FormattedMessage
|
||||
id='admin.analytics.totalPosts'
|
||||
defaultMessage='Total Posts'
|
||||
/>
|
||||
<i className='fa fa-comment'/></div>
|
||||
<div className='content'>{this.props.postCount == null ? loading : this.props.postCount}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
var postCountsByDay = (
|
||||
<div className='col-sm-12'>
|
||||
<div className='total-count by-day'>
|
||||
<div className='title'>
|
||||
<FormattedMessage
|
||||
id='admin.analytics.totalPosts'
|
||||
defaultMessage='Total Posts'
|
||||
/>
|
||||
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 className='content'>{loading}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (this.props.postCountsDay != null) {
|
||||
);
|
||||
} else {
|
||||
let content;
|
||||
if (this.props.postCountsDay.labels.length === 0) {
|
||||
content = (
|
||||
@@ -137,21 +240,22 @@ export default class Analytics extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
var 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'
|
||||
/>
|
||||
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 className='content'>{loading}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (this.props.userCountsWithPostsDay != null) {
|
||||
);
|
||||
} else {
|
||||
let content;
|
||||
if (this.props.userCountsWithPostsDay.labels.length === 0) {
|
||||
content = (
|
||||
@@ -312,12 +416,8 @@ export default class Analytics extends React.Component {
|
||||
/>
|
||||
</h3>
|
||||
{serverError}
|
||||
<div className='row'>
|
||||
{totalCount}
|
||||
{postCount}
|
||||
{openChannelCount}
|
||||
{openPrivateCount}
|
||||
</div>
|
||||
{firstRow}
|
||||
{extraGraphs}
|
||||
<div className='row'>
|
||||
{postCountsByDay}
|
||||
</div>
|
||||
@@ -347,10 +447,16 @@ Analytics.defaultProps = {
|
||||
};
|
||||
|
||||
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,
|
||||
@@ -358,3 +464,5 @@ Analytics.propTypes = {
|
||||
uniqueUserCount: React.PropTypes.number,
|
||||
serverError: React.PropTypes.string
|
||||
};
|
||||
|
||||
export default injectIntl(Analytics);
|
||||
|
||||
77
web/react/components/admin_console/doughnut_chart.jsx
Обычный файл
77
web/react/components/admin_console/doughnut_chart.jsx
Обычный файл
@@ -0,0 +1,77 @@
|
||||
// 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
|
||||
};
|
||||
37
web/react/components/admin_console/statistic_count.jsx
Обычный файл
37
web/react/components/admin_console/statistic_count.jsx
Обычный файл
@@ -0,0 +1,37 @@
|
||||
// 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 {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
@@ -140,6 +140,34 @@ class SystemAnalytics extends React.Component {
|
||||
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() {
|
||||
@@ -160,10 +188,16 @@ class SystemAnalytics extends React.Component {
|
||||
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}
|
||||
@@ -179,4 +213,4 @@ SystemAnalytics.propTypes = {
|
||||
team: React.PropTypes.object
|
||||
};
|
||||
|
||||
export default injectIntl(SystemAnalytics);
|
||||
export default injectIntl(SystemAnalytics);
|
||||
|
||||
@@ -227,6 +227,7 @@ class TeamAnalytics extends React.Component {
|
||||
return (
|
||||
<div>
|
||||
<Analytics
|
||||
intl={this.props.intl}
|
||||
title={this.props.team.name}
|
||||
users={this.state.users}
|
||||
channelOpenCount={this.state.channel_open_count}
|
||||
@@ -249,4 +250,4 @@ TeamAnalytics.propTypes = {
|
||||
team: React.PropTypes.object
|
||||
};
|
||||
|
||||
export default injectIntl(TeamAnalytics);
|
||||
export default injectIntl(TeamAnalytics);
|
||||
|
||||
Ссылка в новой задаче
Block a user