PLT-2713/PLT-6028 Added System Users list to System Console (#5882)
* PLT-2713 Added ability for admins to list users not in any team * Updated style of unit test * Split SearchableUserList to give better control over its properties * Added users without any teams to the user store * Added ManageUsers page * Renamed ManageUsers to SystemUsers * Added ability to search by user id in SystemUsers page * Added SystemUsersDropdown * Removed unnecessary injectIntl * Created TeamUtils * Reduced scope of system console heading CSS * Added team filter to TeamAnalytics page * Updated admin console sidebar * Removed unnecessary TODO * Removed unused reference to deleted modal * Fixed system console sidebar not scrolling on first load * Fixed TeamAnalytics page not rendering on first load * Fixed chart.js throwing an error when switching between teams * Changed TeamAnalytics header to show the team's display name * Fixed appearance of TeamAnalytics and SystemUsers on small screen widths * Fixed placement of 'No users found' message * Fixed teams not appearing in SystemUsers on first load * Updated user count text for SystemUsers * Changed search by id fallback to trigger less often * Fixed SystemUsers list items not updating when searching * Fixed localization strings for SystemUsers page
Этот коммит содержится в:
коммит произвёл
Corey Hulen
родитель
9a9729f22f
Коммит
689cac535e
@@ -21,14 +21,33 @@ export default class LineChart extends React.Component {
|
||||
this.initChart();
|
||||
}
|
||||
|
||||
componentWillUpdate(nextProps) {
|
||||
const willHaveData = nextProps.data && nextProps.data.labels.length > 0;
|
||||
const hasChart = Boolean(this.chart);
|
||||
|
||||
if (!willHaveData && hasChart) {
|
||||
// Clean up the rendered chart before we render and destroy its context
|
||||
this.chart.destroy();
|
||||
this.chart = null;
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (!Utils.areObjectsEqual(prevProps.data, this.props.data) || !Utils.areObjectsEqual(prevProps.options, this.props.options)) {
|
||||
this.initChart(true);
|
||||
if (Utils.areObjectsEqual(prevProps.data, this.props.data) && Utils.areObjectsEqual(prevProps.options, this.props.options)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hasData = this.props.data && this.props.data.labels.length > 0;
|
||||
const hasChart = Boolean(this.chart);
|
||||
|
||||
if (hasData) {
|
||||
// Update the rendered chart or initialize it as necessary
|
||||
this.initChart(hasChart);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.chart && this.refs.canvas) {
|
||||
if (this.chart) {
|
||||
this.chart.destroy();
|
||||
}
|
||||
}
|
||||
@@ -37,9 +56,11 @@ export default class LineChart extends React.Component {
|
||||
if (!this.refs.canvas) {
|
||||
return;
|
||||
}
|
||||
|
||||
var el = ReactDOM.findDOMNode(this.refs.canvas);
|
||||
var ctx = el.getContext('2d');
|
||||
this.chart = new Chart(ctx, {type: 'line', data: this.props.data, options: this.props.options || {}}); //eslint-disable-line new-cap
|
||||
this.chart = new Chart(ctx, {type: 'line', data: this.props.data, options: this.props.options || {}}); // eslint-disable-line new-cap
|
||||
|
||||
if (update) {
|
||||
this.chart.update();
|
||||
}
|
||||
|
||||
@@ -409,7 +409,7 @@ class SystemAnalytics extends React.Component {
|
||||
|
||||
return (
|
||||
<div className='wrapper--fixed team_statistics'>
|
||||
<h3>
|
||||
<h3 className='admin-console-header'>
|
||||
<FormattedMessage
|
||||
id='analytics.system.title'
|
||||
defaultMessage='System Statistics'
|
||||
|
||||
@@ -1,40 +1,43 @@
|
||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {FormattedDate, FormattedMessage, FormattedHTMLMessage} from 'react-intl';
|
||||
|
||||
import Banner from 'components/admin_console/banner.jsx';
|
||||
import LineChart from './line_chart.jsx';
|
||||
import StatisticCount from './statistic_count.jsx';
|
||||
import TableChart from './table_chart.jsx';
|
||||
import LoadingScreen from 'components/loading_screen.jsx';
|
||||
|
||||
import AdminStore from 'stores/admin_store.jsx';
|
||||
import AnalyticsStore from 'stores/analytics_store.jsx';
|
||||
import BrowserStore from 'stores/browser_store.jsx';
|
||||
|
||||
import * as Utils from 'utils/utils.jsx';
|
||||
import * as AsyncClient from 'utils/async_client.jsx';
|
||||
import Constants from 'utils/constants.jsx';
|
||||
const StatTypes = Constants.StatTypes;
|
||||
import {StatTypes} from 'utils/constants.jsx';
|
||||
import {convertTeamMapToList} from 'utils/team_utils.jsx';
|
||||
|
||||
import LineChart from './line_chart.jsx';
|
||||
import StatisticCount from './statistic_count.jsx';
|
||||
import TableChart from './table_chart.jsx';
|
||||
import {formatPostsPerDayData, formatUsersWithPostsPerDayData} from './system_analytics.jsx';
|
||||
import {FormattedMessage, FormattedDate, FormattedHTMLMessage} from 'react-intl';
|
||||
|
||||
import React from 'react';
|
||||
const LAST_ANALYTICS_TEAM = 'last_analytics_team';
|
||||
|
||||
export default class TeamAnalytics extends React.Component {
|
||||
static get propTypes() {
|
||||
return {
|
||||
params: React.PropTypes.object.isRequired
|
||||
};
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.onChange = this.onChange.bind(this);
|
||||
this.onAllTeamsChange = this.onAllTeamsChange.bind(this);
|
||||
this.handleTeamChange = this.handleTeamChange.bind(this);
|
||||
|
||||
const teams = convertTeamMapToList(AdminStore.getAllTeams());
|
||||
const teamId = BrowserStore.getGlobalItem(LAST_ANALYTICS_TEAM, teams.length > 0 ? teams[0].id : '');
|
||||
|
||||
this.state = {
|
||||
team: AdminStore.getTeam(this.props.params.team),
|
||||
stats: AnalyticsStore.getAllTeam(this.props.params.team)
|
||||
teams,
|
||||
teamId,
|
||||
team: AdminStore.getTeam(teamId),
|
||||
stats: AnalyticsStore.getAllTeam(teamId)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -42,7 +45,19 @@ export default class TeamAnalytics extends React.Component {
|
||||
AnalyticsStore.addChangeListener(this.onChange);
|
||||
AdminStore.addAllTeamsChangeListener(this.onAllTeamsChange);
|
||||
|
||||
this.getData(this.props.params.team);
|
||||
if (this.state.teamId !== '') {
|
||||
this.getData(this.state.teamId);
|
||||
}
|
||||
|
||||
if (this.state.teams.length === 0) {
|
||||
AsyncClient.getAllTeams();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUpdate(nextProps, nextState) {
|
||||
if (nextState.teamId !== this.state.teamId) {
|
||||
this.getData(nextState.teamId);
|
||||
}
|
||||
}
|
||||
|
||||
getData(id) {
|
||||
@@ -57,40 +72,60 @@ export default class TeamAnalytics extends React.Component {
|
||||
AdminStore.removeAllTeamsChangeListener(this.onAllTeamsChange);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.getData(nextProps.params.team);
|
||||
this.setState({
|
||||
stats: AnalyticsStore.getAllTeam(nextProps.params.team)
|
||||
});
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
if (!Utils.areObjectsEqual(nextState.stats, this.state.stats)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Utils.areObjectsEqual(nextProps.params.team, this.props.params.team)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
onChange() {
|
||||
this.setState({
|
||||
stats: AnalyticsStore.getAllTeam(this.props.params.team)
|
||||
stats: AnalyticsStore.getAllTeam(this.state.teamId)
|
||||
});
|
||||
}
|
||||
|
||||
onAllTeamsChange() {
|
||||
const teams = convertTeamMapToList(AdminStore.getAllTeams());
|
||||
|
||||
if (teams.length > 0) {
|
||||
if (this.state.teamId) {
|
||||
this.setState({
|
||||
team: AdminStore.getTeam(this.state.teamId)
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
teamId: teams[0].id,
|
||||
team: teams[0]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({
|
||||
team: AdminStore.getTeam(this.props.params.team)
|
||||
teams
|
||||
});
|
||||
}
|
||||
|
||||
handleTeamChange(e) {
|
||||
const teamId = e.target.value;
|
||||
|
||||
this.setState({
|
||||
teamId,
|
||||
team: AdminStore.getTeam(teamId)
|
||||
});
|
||||
|
||||
BrowserStore.setGlobalItem(LAST_ANALYTICS_TEAM, teamId);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.state.team || !this.state.stats) {
|
||||
return null;
|
||||
if (this.state.teams.length === 0 || !this.state.team || !this.state.stats) {
|
||||
return <LoadingScreen/>;
|
||||
}
|
||||
|
||||
if (this.state.teamId === '') {
|
||||
return (
|
||||
<Banner
|
||||
description={
|
||||
<FormattedMessage
|
||||
id='analytics.team.noTeams'
|
||||
defaultMessage='There are no teams on this server for which to view statistics.'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const stats = this.state.stats;
|
||||
@@ -129,6 +164,7 @@ export default class TeamAnalytics extends React.Component {
|
||||
postTotalGraph = (
|
||||
<div className='row'>
|
||||
<LineChart
|
||||
key={this.state.team.id}
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='analytics.team.totalPosts'
|
||||
@@ -150,6 +186,7 @@ export default class TeamAnalytics extends React.Component {
|
||||
userActiveGraph = (
|
||||
<div className='row'>
|
||||
<LineChart
|
||||
key={this.state.team.id}
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='analytics.team.activeUsers'
|
||||
@@ -172,17 +209,41 @@ export default class TeamAnalytics extends React.Component {
|
||||
const recentActiveUsers = formatRecentUsersData(stats[StatTypes.RECENTLY_ACTIVE_USERS]);
|
||||
const newlyCreatedUsers = formatNewUsersData(stats[StatTypes.NEWLY_CREATED_USERS]);
|
||||
|
||||
const teams = this.state.teams.map((team) => {
|
||||
return (
|
||||
<option
|
||||
key={team.id}
|
||||
value={team.id}
|
||||
>
|
||||
{team.display_name}
|
||||
</option>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className='wrapper--fixed team_statistics'>
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id='analytics.team.title'
|
||||
defaultMessage='Team Statistics for {team}'
|
||||
values={{
|
||||
team: this.state.team.name
|
||||
}}
|
||||
/>
|
||||
</h3>
|
||||
<div className='admin-console-header team-statistics__header-row'>
|
||||
<div className='team-statistics__header'>
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id='analytics.team.title'
|
||||
defaultMessage='Team Statistics for {team}'
|
||||
values={{
|
||||
team: this.state.team.display_name
|
||||
}}
|
||||
/>
|
||||
</h3>
|
||||
</div>
|
||||
<div className='team-statistics__team-filter'>
|
||||
<select
|
||||
className='form-control team-statistics__team-filter__dropdown'
|
||||
onChange={this.handleTeamChange}
|
||||
value={this.state.teamId}
|
||||
>
|
||||
{teams}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{banner}
|
||||
<div className='row'>
|
||||
<StatisticCount
|
||||
|
||||
Ссылка в новой задаче
Block a user