* improve performance on sendNotifications * Fix SQL queries * Remove get direct profiles, not needed anymore * Add raw data to error details if AppError fails to decode * men * Fix decode (#4052) * Fixing json decode * Adding unit test * Initial work for client scaling (#4051) * Begin adding paging to profiles API * Added more paging functionality * Finish hooking up admin console user lists * Add API for searching users and add searching to all user lists * Add lazy loading of profiles * Revert config.json * Fix unit tests and some style issues * Add GetProfilesFromList to Go driver and fix web unit test * Update etag for GetProfiles * Updating ui for filters and pagination (#4044) * Updating UI for pagination * Adjusting margins for filter row * Adjusting margin for specific modals * Adding relative padding to system console * Adjusting responsive view * Update client user tests * Minor fixes for direct messages modal (#4056) * Remove some unneeded initial load calls (#4057) * UX updates to user lists, added smart counts and bug fixes (#4059) * Improved getExplicitMentions and unit tests (#4064) * Refactor getting posts to lazy load profiles correctly (#4062) * Comment out SetActiveChannel test (#4066) * Profiler cpu, block, and memory profiler. (#4081) * Fix TestSetActiveChannel unit test (#4071) * Fixing build failure caused by dependancies updating (#4076) * Adding profiler * Fix admin_team_member_dropdown eslint errors * Bumping session cache size (#4077) * Bumping session cache size * Bumping status cache * Refactor how the client handles channel members to be large team friendly (#4106) * Refactor how the client handles channel members to be large team friendly * Change Id to ChannelId in ChannelStats model * Updated getChannelMember and getProfilesByIds routes to match proposal * Performance improvements (#4100) * Performance improvements * Fixing re-connect issue * Fixing error message * Some other minor perf tweaks * Some other minor perf tweaks * Fixing config file * Fixing buffer size * Fixing web socket send message * adding some error logging * fix getMe to be user required * Fix websocket event for new user * Fixing shutting down * Reverting web socket changes * Fixing logging lvl * Adding caching to GetMember * Adding some logging * Fixing caching * Fixing caching invalidate * Fixing direct message caching * Fixing caching * Fixing caching * Remove GetDirectProfiles from initial load * Adding logging and fixing websocket client * Adding back caching from bad merge. * Explicitly close go driver requests (#4162) * Refactored how the client handles team members to be more large team friendly (#4159) * Refactor getProfilesForDirectMessageList API into getAllProfiles API * Refactored how the client handles team members to be more large team friendly * Fix js error when receiving a notification * Fix JS error caused by current user being overwritten with sanitized version (#4165) * Adding error message to status failure (#4167) * Fix a few bugs caused by client scaling refactoring (#4170) * When there is no read replica, don't open a second set of connections to the master database (#4173) * Adding connection tacking to stats (#4174) * Reduce DB writes for statuses and other status related changes (#4175) * Fix bug preventing opening of DM channels from more modal (#4181) * Fixing socket timing error (#4183) * Fixing ping/pong handler * Fixing socket timing error * Commenting out status broadcasting * Removing user status changes * Removing user status changes * Removing user status changes * Removing user status changes * Adding DoPreComputeJson() * Performance improvements (#4194) * * Fix System Console Analytics queries * Add db.SetConnMaxLifetime to 15 minutes * Add "net/http/pprof" for profiling * Add FreeOSMemory() to manually release memory on reload config * Add flag to enable http profiler * Fix memory leak (#4197) * Fix memory leak * removed unneeded nil assignment * Fixing go routine leak (#4208) * Merge fixes * Merge fix * Refactored statuses to be queried by the client rather than broadcast by the server (#4212) * Refactored server code to reduce status broadcasts and to allow getting statuses by IDs * Refactor client code to periodically fetch statuses * Add store unit test for getting statuses by ids * Fix status unit test * Add getStatusesByIds REST API and move the client over to use that instead of the WebSocket * Adding multiple threads to websocket hub (#4230) * Adding multiple threads to websocket hub * Fixing unit tests * Fixing so websocket connections from the same user end up in the same… (#4240) * Fixing so websocket connections from the same user end up in the same list * Removing old comment * Refactor user autocomplete to query the server (#4239) * Add API for autocompleting users * Converted at mention autocomplete to query server * Converted user search autocomplete to query server * Switch autocomplete API naming to use term instead of username * Split autocomplete API into two, one for channels and for teams * Fix copy/paste error * Some final client scaling fixes (#4246) * Add lazy loading of profiles to integration pages * Add lazy loading of profiles to emoji page * Fix JS error when receiving post in select team menu and also clean up channel store
412 строки
15 KiB
JavaScript
412 строки
15 KiB
JavaScript
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
|
// See License.txt for license information.
|
|
|
|
import Banner from 'components/admin_console/banner.jsx';
|
|
import LineChart from './line_chart.jsx';
|
|
import DoughnutChart from './doughnut_chart.jsx';
|
|
import StatisticCount from './statistic_count.jsx';
|
|
|
|
import AnalyticsStore from 'stores/analytics_store.jsx';
|
|
|
|
import * as Utils from 'utils/utils.jsx';
|
|
import {isLicenseExpired, isLicenseExpiring, displayExpiryDate} from 'utils/license_utils.jsx';
|
|
import * as AsyncClient from 'utils/async_client.jsx';
|
|
import Constants from 'utils/constants.jsx';
|
|
const StatTypes = Constants.StatTypes;
|
|
|
|
import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'react-intl';
|
|
|
|
const holders = defineMessages({
|
|
analyticsPublicChannels: {
|
|
id: 'analytics.system.publicChannels',
|
|
defaultMessage: 'Public Channels'
|
|
},
|
|
analyticsPrivateGroups: {
|
|
id: 'analytics.system.privateGroups',
|
|
defaultMessage: 'Private Groups'
|
|
},
|
|
analyticsFilePosts: {
|
|
id: 'analytics.system.totalFilePosts',
|
|
defaultMessage: 'Posts with Files'
|
|
},
|
|
analyticsHashtagPosts: {
|
|
id: 'analytics.system.totalHashtagPosts',
|
|
defaultMessage: 'Posts with Hashtags'
|
|
},
|
|
analyticsTextPosts: {
|
|
id: 'analytics.system.textPosts',
|
|
defaultMessage: 'Posts with Text-only'
|
|
}
|
|
});
|
|
|
|
import React from 'react';
|
|
|
|
class SystemAnalytics extends React.Component {
|
|
constructor(props) {
|
|
super(props);
|
|
|
|
this.onChange = this.onChange.bind(this);
|
|
|
|
this.state = {stats: AnalyticsStore.getAllSystem()};
|
|
}
|
|
|
|
componentDidMount() {
|
|
AnalyticsStore.addChangeListener(this.onChange);
|
|
|
|
AsyncClient.getStandardAnalytics();
|
|
AsyncClient.getPostsPerDayAnalytics();
|
|
AsyncClient.getUsersPerDayAnalytics();
|
|
|
|
if (global.window.mm_license.IsLicensed === 'true') {
|
|
AsyncClient.getAdvancedAnalytics();
|
|
}
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
AnalyticsStore.removeChangeListener(this.onChange);
|
|
}
|
|
|
|
shouldComponentUpdate(nextProps, nextState) {
|
|
if (!Utils.areObjectsEqual(nextState.stats, this.state.stats)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
onChange() {
|
|
this.setState({stats: AnalyticsStore.getAllSystem()});
|
|
}
|
|
|
|
render() {
|
|
const stats = this.state.stats;
|
|
|
|
let advancedCounts;
|
|
let advancedStats;
|
|
let advancedGraphs;
|
|
let banner;
|
|
if (global.window.mm_license.IsLicensed === 'true') {
|
|
advancedCounts = (
|
|
<div className='row'>
|
|
<StatisticCount
|
|
title={
|
|
<FormattedMessage
|
|
id='analytics.system.totalSessions'
|
|
defaultMessage='Total Sessions'
|
|
/>
|
|
}
|
|
icon='fa-signal'
|
|
count={stats[StatTypes.TOTAL_SESSIONS]}
|
|
/>
|
|
<StatisticCount
|
|
title={
|
|
<FormattedMessage
|
|
id='analytics.system.totalCommands'
|
|
defaultMessage='Total Commands'
|
|
/>
|
|
}
|
|
icon='fa-terminal'
|
|
count={stats[StatTypes.TOTAL_COMMANDS]}
|
|
/>
|
|
<StatisticCount
|
|
title={
|
|
<FormattedMessage
|
|
id='analytics.system.totalIncomingWebhooks'
|
|
defaultMessage='Incoming Webhooks'
|
|
/>
|
|
}
|
|
icon='fa-arrow-down'
|
|
count={stats[StatTypes.TOTAL_IHOOKS]}
|
|
/>
|
|
<StatisticCount
|
|
title={
|
|
<FormattedMessage
|
|
id='analytics.system.totalOutgoingWebhooks'
|
|
defaultMessage='Outgoing Webhooks'
|
|
/>
|
|
}
|
|
icon='fa-arrow-up'
|
|
count={stats[StatTypes.TOTAL_OHOOKS]}
|
|
/>
|
|
</div>
|
|
);
|
|
|
|
advancedStats = (
|
|
<div className='row'>
|
|
<StatisticCount
|
|
title={
|
|
<FormattedMessage
|
|
id='analytics.system.totalWebsockets'
|
|
defaultMessage='Websocket Conns'
|
|
/>
|
|
}
|
|
icon='fa-user'
|
|
count={stats[StatTypes.TOTAL_WEBSOCKET_CONNECTIONS]}
|
|
/>
|
|
<StatisticCount
|
|
title={
|
|
<FormattedMessage
|
|
id='analytics.system.totalMasterDbConnections'
|
|
defaultMessage='Master DB Conns'
|
|
/>
|
|
}
|
|
icon='fa-terminal'
|
|
count={stats[StatTypes.TOTAL_MASTER_DB_CONNECTIONS]}
|
|
/>
|
|
<StatisticCount
|
|
title={
|
|
<FormattedMessage
|
|
id='analytics.system.totalReadDbConnections'
|
|
defaultMessage='Replica DB Conns'
|
|
/>
|
|
}
|
|
icon='fa-terminal'
|
|
count={stats[StatTypes.TOTAL_READ_DB_CONNECTIONS]}
|
|
/>
|
|
</div>
|
|
);
|
|
|
|
const channelTypeData = formatChannelDoughtnutData(stats[StatTypes.TOTAL_PUBLIC_CHANNELS], stats[StatTypes.TOTAL_PRIVATE_GROUPS], this.props.intl);
|
|
const postTypeData = formatPostDoughtnutData(stats[StatTypes.TOTAL_FILE_POSTS], stats[StatTypes.TOTAL_HASHTAG_POSTS], stats[StatTypes.TOTAL_POSTS], this.props.intl);
|
|
|
|
advancedGraphs = (
|
|
<div className='row'>
|
|
<DoughnutChart
|
|
title={
|
|
<FormattedMessage
|
|
id='analytics.system.channelTypes'
|
|
defaultMessage='Channel Types'
|
|
/>
|
|
}
|
|
data={channelTypeData}
|
|
width='300'
|
|
height='225'
|
|
/>
|
|
<DoughnutChart
|
|
title={
|
|
<FormattedMessage
|
|
id='analytics.system.postTypes'
|
|
defaultMessage='Posts, Files and Hashtags'
|
|
/>
|
|
}
|
|
data={postTypeData}
|
|
width='300'
|
|
height='225'
|
|
/>
|
|
</div>
|
|
);
|
|
|
|
if (isLicenseExpired()) {
|
|
banner = (
|
|
<Banner
|
|
description={
|
|
<FormattedHTMLMessage
|
|
id='analytics.system.expiredBanner'
|
|
defaultMessage='The Enterprise license expired on {date}. You have 15 days from this date to renew the license, please contact <a href="mailto:commercial@mattermost.com">commercial@mattermost.com</a>.'
|
|
values={{
|
|
date: displayExpiryDate()
|
|
}}
|
|
/>
|
|
}
|
|
/>
|
|
);
|
|
} else if (isLicenseExpiring()) {
|
|
banner = (
|
|
<Banner
|
|
description={
|
|
<FormattedHTMLMessage
|
|
id='analytics.system.expiringBanner'
|
|
defaultMessage='The Enterprise license is expiring on {date}. To renew your license, please contact <a href="mailto:commercial@mattermost.com">commercial@mattermost.com</a>.'
|
|
values={{
|
|
date: displayExpiryDate()
|
|
}}
|
|
/>
|
|
}
|
|
/>
|
|
);
|
|
}
|
|
}
|
|
|
|
const postCountsDay = formatPostsPerDayData(stats[StatTypes.POST_PER_DAY]);
|
|
const userCountsWithPostsDay = formatUsersWithPostsPerDayData(stats[StatTypes.USERS_WITH_POSTS_PER_DAY]);
|
|
|
|
return (
|
|
<div className='wrapper--fixed team_statistics'>
|
|
<h3>
|
|
<FormattedMessage
|
|
id='analytics.system.title'
|
|
defaultMessage='System Statistics'
|
|
/>
|
|
</h3>
|
|
{banner}
|
|
<div className='row'>
|
|
<StatisticCount
|
|
title={
|
|
<FormattedMessage
|
|
id='analytics.system.totalUsers'
|
|
defaultMessage='Total Users'
|
|
/>
|
|
}
|
|
icon='fa-user'
|
|
count={stats[StatTypes.TOTAL_USERS]}
|
|
/>
|
|
<StatisticCount
|
|
title={
|
|
<FormattedMessage
|
|
id='analytics.system.totalTeams'
|
|
defaultMessage='Total Teams'
|
|
/>
|
|
}
|
|
icon='fa-users'
|
|
count={stats[StatTypes.TOTAL_TEAMS]}
|
|
/>
|
|
<StatisticCount
|
|
title={
|
|
<FormattedMessage
|
|
id='analytics.system.totalPosts'
|
|
defaultMessage='Total Posts'
|
|
/>
|
|
}
|
|
icon='fa-comment'
|
|
count={stats[StatTypes.TOTAL_POSTS]}
|
|
/>
|
|
<StatisticCount
|
|
title={
|
|
<FormattedMessage
|
|
id='analytics.system.totalChannels'
|
|
defaultMessage='Total Channels'
|
|
/>
|
|
}
|
|
icon='fa-globe'
|
|
count={stats[StatTypes.TOTAL_PUBLIC_CHANNELS] + stats[StatTypes.TOTAL_PRIVATE_GROUPS]}
|
|
/>
|
|
</div>
|
|
{advancedCounts}
|
|
{advancedStats}
|
|
{advancedGraphs}
|
|
<div className='row'>
|
|
<LineChart
|
|
title={
|
|
<FormattedMessage
|
|
id='analytics.system.totalPosts'
|
|
defaultMessage='Total Posts'
|
|
/>
|
|
}
|
|
data={postCountsDay}
|
|
options={{
|
|
legend: {
|
|
display: false
|
|
}
|
|
}}
|
|
width='740'
|
|
height='225'
|
|
/>
|
|
</div>
|
|
<div className='row'>
|
|
<LineChart
|
|
title={
|
|
<FormattedMessage
|
|
id='analytics.system.activeUsers'
|
|
defaultMessage='Active Users With Posts'
|
|
/>
|
|
}
|
|
data={userCountsWithPostsDay}
|
|
options={{
|
|
legend: {
|
|
display: false
|
|
}
|
|
}}
|
|
width='740'
|
|
height='225'
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
SystemAnalytics.propTypes = {
|
|
intl: intlShape.isRequired
|
|
};
|
|
|
|
export default injectIntl(SystemAnalytics);
|
|
|
|
export function formatChannelDoughtnutData(totalPublic, totalPrivate, intl) {
|
|
const {formatMessage} = intl;
|
|
const channelTypeData = {
|
|
labels: [formatMessage(holders.analyticsPublicChannels), formatMessage(holders.analyticsPrivateGroups)],
|
|
datasets: [{
|
|
data: [totalPublic, totalPrivate],
|
|
backgroundColor: ['#46BFBD', '#FDB45C'],
|
|
hoverBackgroundColor: ['#5AD3D1', '#FFC870']
|
|
}]
|
|
};
|
|
|
|
return channelTypeData;
|
|
}
|
|
|
|
export function formatPostDoughtnutData(filePosts, hashtagPosts, totalPosts, intl) {
|
|
const {formatMessage} = intl;
|
|
const postTypeData = {
|
|
labels: [formatMessage(holders.analyticsFilePosts), formatMessage(holders.analyticsHashtagPosts), formatMessage(holders.analyticsTextPosts)],
|
|
datasets: [{
|
|
data: [filePosts, hashtagPosts, (totalPosts - filePosts - hashtagPosts)],
|
|
backgroundColor: ['#46BFBD', '#F7464A', '#FDB45C'],
|
|
hoverBackgroundColor: ['#5AD3D1', '#FF5A5E', '#FFC870']
|
|
}]
|
|
};
|
|
|
|
return postTypeData;
|
|
}
|
|
|
|
export function formatPostsPerDayData(data) {
|
|
var chartData = {
|
|
labels: [],
|
|
datasets: [{
|
|
fillColor: 'rgba(151,187,205,0.2)',
|
|
borderColor: 'rgba(151,187,205,1)',
|
|
pointBackgroundColor: 'rgba(151,187,205,1)',
|
|
pointBorderColor: '#fff',
|
|
pointHoverBackgroundColor: '#fff',
|
|
pointHoverBorderColor: '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);
|
|
}
|
|
}
|
|
|
|
return chartData;
|
|
}
|
|
|
|
export function formatUsersWithPostsPerDayData(data) {
|
|
var chartData = {
|
|
labels: [],
|
|
datasets: [{
|
|
label: '',
|
|
fillColor: 'rgba(151,187,205,0.2)',
|
|
borderColor: 'rgba(151,187,205,1)',
|
|
pointBackgroundColor: 'rgba(151,187,205,1)',
|
|
pointBorderColor: '#fff',
|
|
pointHoverBackgroundColor: '#fff',
|
|
pointHoverBorderColor: '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);
|
|
}
|
|
}
|
|
|
|
return chartData;
|
|
}
|