From 2a26d857574f2160e3ee5538ad3a84ec47082f86 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Thu, 21 Jan 2016 12:14:17 -0500 Subject: Generalize analytics server functions and begin componentizing client analytics controls --- web/react/components/admin_console/analytics.jsx | 277 +++++++++++++++++++++ .../components/admin_console/team_analytics.jsx | 256 +++---------------- 2 files changed, 306 insertions(+), 227 deletions(-) create mode 100644 web/react/components/admin_console/analytics.jsx (limited to 'web/react/components') diff --git a/web/react/components/admin_console/analytics.jsx b/web/react/components/admin_console/analytics.jsx new file mode 100644 index 000000000..4349719c1 --- /dev/null +++ b/web/react/components/admin_console/analytics.jsx @@ -0,0 +1,277 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import * as Utils from '../../utils/utils.jsx'; +import Constants from '../../utils/constants.jsx'; +import LineChart from './line_chart.jsx'; + +var Tooltip = ReactBootstrap.Tooltip; +var OverlayTrigger = ReactBootstrap.OverlayTrigger; + +export default class Analytics extends React.Component { + constructor(props) { + super(props); + + this.state = {}; + } + + render() { // in the future, break down these into smaller components + var serverError = ''; + if (this.props.serverError) { + serverError =
; + } + + var totalCount = ( +
+
+
{'Total Users'}
+
{this.props.uniqueUserCount == null ? 'Loading...' : this.props.uniqueUserCount}
+
+
+ ); + + var openChannelCount = ( +
+
+
{'Public Channels'}
+
{this.props.channelOpenCount == null ? 'Loading...' : this.props.channelOpenCount}
+
+
+ ); + + var openPrivateCount = ( +
+
+
{'Private Groups'}
+
{this.props.channelPrivateCount == null ? 'Loading...' : this.props.channelPrivateCount}
+
+
+ ); + + var postCount = ( +
+
+
{'Total Posts'}
+
{this.props.postCount == null ? 'Loading...' : this.props.postCount}
+
+
+ ); + + var postCountsByDay = ( +
+
+
{'Total Posts'}
+
{'Loading...'}
+
+
+ ); + + if (this.props.postCountsDay != null) { + let content; + if (this.props.postCountsDay.labels.length === 0) { + content = 'Not enough data for a meaningful representation.'; + } else { + content = ( + + ); + } + postCountsByDay = ( +
+
+
{'Total Posts'}
+
+ {content} +
+
+
+ ); + } + + var usersWithPostsByDay = ( +
+
+
{'Total Posts'}
+
{'Loading...'}
+
+
+ ); + + if (this.props.userCountsWithPostsDay != null) { + let content; + if (this.props.userCountsWithPostsDay.labels.length === 0) { + content = 'Not enough data for a meaningful representation.'; + } else { + content = ( + + ); + } + usersWithPostsByDay = ( +
+
+
{'Active Users With Posts'}
+
+ {content} +
+
+
+ ); + } + + var recentActiveUser = ( +
+
{'Recent Active Users'}
+
{'Loading...'}
+
+ ); + + if (this.props.recentActiveUsers != null) { + recentActiveUser = ( +
+
+
{'Recent Active Users'}
+
+ + + { + this.props.recentActiveUsers.map((user) => { + const tooltip = ( + + {user.email} + + ); + + return ( + + + + + ); + }) + } + +
+ + + + {Utils.displayDateTime(user.last_activity_at)}
+
+
+
+ ); + } + + var newUsers = ( +
+
{'Newly Created Users'}
+
{'Loading...'}
+
+ ); + + if (this.props.newlyCreatedUsers != null) { + newUsers = ( +
+
+
{'Newly Created Users'}
+
+ + + { + this.props.newlyCreatedUsers.map((user) => { + const tooltip = ( + + {user.email} + + ); + + return ( + + + + + ); + }) + } + +
+ + + + {Utils.displayDateTime(user.create_at)}
+
+
+
+ ); + } + + return ( +
+

{'Statistics for ' + this.props.title}

+
+ {totalCount} + {postCount} + {openChannelCount} + {openPrivateCount} +
+
+ {postCountsByDay} +
+
+ {usersWithPostsByDay} +
+
+ {recentActiveUser} + {newUsers} +
+
+ ); + } +} + + +Analytics.defaultProps = { + title: null, + users: null, + channelOpenCount: null, + channelPrivateCount: null, + postCount: null, + postCountsDay: null, + userCountsWithPostsDay: null, + recentActiveUsers: null, + newlyCreatedUsers: null, + uniqueUserCount: null, + serverError: null +}; + +Analytics.propTypes = { + title: React.PropTypes.string, + users: React.PropTypes.object, + channelOpenCount: React.PropTypes.number, + channelPrivateCount: React.PropTypes.number, + postCount: 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 +}; diff --git a/web/react/components/admin_console/team_analytics.jsx b/web/react/components/admin_console/team_analytics.jsx index fe7230946..baa041bac 100644 --- a/web/react/components/admin_console/team_analytics.jsx +++ b/web/react/components/admin_console/team_analytics.jsx @@ -1,13 +1,8 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// 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 * as Utils from '../../utils/utils.jsx'; -import Constants from '../../utils/constants.jsx'; -import LineChart from './line_chart.jsx'; - -var Tooltip = ReactBootstrap.Tooltip; -var OverlayTrigger = ReactBootstrap.OverlayTrigger; export default class TeamAnalytics extends React.Component { constructor(props) { @@ -15,7 +10,7 @@ export default class TeamAnalytics extends React.Component { this.getData = this.getData.bind(this); - this.state = { + this.state = { // most of this state should be from a store in the future users: null, serverError: null, channel_open_count: null, @@ -24,7 +19,8 @@ export default class TeamAnalytics extends React.Component { post_counts_day: null, user_counts_with_posts_day: null, recent_active_users: null, - newly_created_users: null + newly_created_users: null, + unique_user_count: null }; } @@ -32,8 +28,8 @@ export default class TeamAnalytics extends React.Component { this.getData(this.props.team.id); } - getData(teamId) { - Client.getAnalytics( + getData(teamId) { // should be moved to an action creator eventually + Client.getTeamAnalytics( teamId, 'standard', (data) => { @@ -49,6 +45,10 @@ export default class TeamAnalytics extends React.Component { 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) => { @@ -56,7 +56,7 @@ export default class TeamAnalytics extends React.Component { } ); - Client.getAnalytics( + Client.getTeamAnalytics( teamId, 'post_counts_day', (data) => { @@ -91,7 +91,7 @@ export default class TeamAnalytics extends React.Component { } ); - Client.getAnalytics( + Client.getTeamAnalytics( teamId, 'user_counts_with_posts_day', (data) => { @@ -198,227 +198,29 @@ export default class TeamAnalytics extends React.Component { post_counts_day: null, user_counts_with_posts_day: null, recent_active_users: null, - newly_created_users: null + newly_created_users: null, + unique_user_count: null }); this.getData(newProps.team.id); } - componentWillUnmount() { - } - render() { - var serverError = ''; - if (this.state.serverError) { - serverError =
; - } - - var totalCount = ( -
-
-
{'Total Users'}
-
{this.state.users == null ? 'Loading...' : Object.keys(this.state.users).length}
-
-
- ); - - var openChannelCount = ( -
-
-
{'Public Channels'}
-
{this.state.channel_open_count == null ? 'Loading...' : this.state.channel_open_count}
-
-
- ); - - var openPrivateCount = ( -
-
-
{'Private Groups'}
-
{this.state.channel_private_count == null ? 'Loading...' : this.state.channel_private_count}
-
-
- ); - - var postCount = ( -
-
-
{'Total Posts'}
-
{this.state.post_count == null ? 'Loading...' : this.state.post_count}
-
-
- ); - - var postCountsByDay = ( -
-
-
{'Total Posts'}
-
{'Loading...'}
-
-
- ); - - if (this.state.post_counts_day != null) { - postCountsByDay = ( -
-
-
{'Total Posts'}
-
- -
-
-
- ); - } - - var usersWithPostsByDay = ( -
-
-
{'Total Posts'}
-
{'Loading...'}
-
-
- ); - - if (this.state.user_counts_with_posts_day != null) { - usersWithPostsByDay = ( -
-
-
{'Active Users With Posts'}
-
- -
-
-
- ); - } - - var recentActiveUser = ( -
-
{'Recent Active Users'}
-
{'Loading...'}
-
- ); - - if (this.state.recent_active_users != null) { - recentActiveUser = ( -
-
-
{'Recent Active Users'}
-
- - - { - this.state.recent_active_users.map((user) => { - const tooltip = ( - - {user.email} - - ); - - return ( - - - - - ); - }) - } - -
- - - - {Utils.displayDateTime(user.last_activity_at)}
-
-
-
- ); - } - - var newUsers = ( -
-
{'Newly Created Users'}
-
{'Loading...'}
-
- ); - - if (this.state.newly_created_users != null) { - newUsers = ( -
-
-
{'Newly Created Users'}
-
- - - { - this.state.newly_created_users.map((user) => { - const tooltip = ( - - {user.email} - - ); - - return ( - - - - - ); - }) - } - -
- - - - {Utils.displayDateTime(user.create_at)}
-
-
-
- ); - } - return ( -
-

{'Statistics for ' + this.props.team.name}

- {serverError} -
- {totalCount} - {postCount} - {openChannelCount} - {openPrivateCount} -
-
- {postCountsByDay} -
-
- {usersWithPostsByDay} -
-
- {recentActiveUser} - {newUsers} -
+
+
); } -- cgit v1.2.3-1-g7c22 From cefdad6d8c71ff6adf0ae919bd9f9139e02a6caa Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Thu, 21 Jan 2016 12:59:32 -0500 Subject: Add system analytics page --- .../components/admin_console/admin_controller.jsx | 5 +- .../components/admin_console/admin_sidebar.jsx | 19 +++ web/react/components/admin_console/analytics.jsx | 158 ++++++++++---------- .../components/admin_console/system_analytics.jsx | 161 +++++++++++++++++++++ 4 files changed, 264 insertions(+), 79 deletions(-) create mode 100644 web/react/components/admin_console/system_analytics.jsx (limited to 'web/react/components') 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 = ; } + } else if (this.state.selected === 'system_analytics') { + tab = ; } } 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 @@ -192,6 +192,25 @@ export default class AdminSidebar extends React.Component {
  • +
      +
    • +

      + + {'SITE REPORTS'} +

      +
    • +
    +
    • 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 = (
      -
      {'Total Posts'}
      -
      {'Loading...'}
      +
      {'Active Users With Posts'}
      +
      {'Loading...'}
      ); @@ -125,98 +125,102 @@ export default class Analytics extends React.Component { ); } - var recentActiveUser = ( -
      -
      {'Recent Active Users'}
      -
      {'Loading...'}
      -
      - ); - + let recentActiveUser; if (this.props.recentActiveUsers != null) { + let content; + if (this.props.recentActiveUsers.length === 0) { + content = 'Loading...'; + } else { + content = ( + + + { + this.props.recentActiveUsers.map((user) => { + const tooltip = ( + + {user.email} + + ); + + return ( + + + + + ); + }) + } + +
      + + + + {Utils.displayDateTime(user.last_activity_at)}
      + ); + } recentActiveUser = (
      {'Recent Active Users'}
      - - - { - this.props.recentActiveUsers.map((user) => { - const tooltip = ( - - {user.email} - - ); - - return ( - - - - - ); - }) - } - -
      - - - - {Utils.displayDateTime(user.last_activity_at)}
      + {content}
      ); } - var newUsers = ( -
      -
      {'Newly Created Users'}
      -
      {'Loading...'}
      -
      - ); - + let newUsers; if (this.props.newlyCreatedUsers != null) { + let content; + if (this.props.newlyCreatedUsers.length === 0) { + content = 'Loading...'; + } else { + content = ( + + + { + this.props.newlyCreatedUsers.map((user) => { + const tooltip = ( + + {user.email} + + ); + + return ( + + + + + ); + }) + } + +
      + + + + {Utils.displayDateTime(user.create_at)}
      + ); + } newUsers = (
      {'Newly Created Users'}
      - - - { - this.props.newlyCreatedUsers.map((user) => { - const tooltip = ( - - {user.email} - - ); - - return ( - - - - - ); - }) - } - -
      - - - - {Utils.displayDateTime(user.create_at)}
      + {content}
      @@ -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 ( +
      + +
      + ); + } +} + +SystemAnalytics.propTypes = { + team: React.PropTypes.object +}; -- cgit v1.2.3-1-g7c22 From c408e09a0418c77e1f1f0a91a1c85bf5f20145e7 Mon Sep 17 00:00:00 2001 From: Reed Garmsen Date: Wed, 20 Jan 2016 13:16:53 -0800 Subject: Fixed issue with enter key not working in delete post modal --- web/react/components/delete_post_modal.jsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'web/react/components') diff --git a/web/react/components/delete_post_modal.jsx b/web/react/components/delete_post_modal.jsx index 827654e1b..4cde5feed 100644 --- a/web/react/components/delete_post_modal.jsx +++ b/web/react/components/delete_post_modal.jsx @@ -23,7 +23,7 @@ export default class DeletePostModal extends React.Component { this.selectedList = null; this.state = { - show: true, + show: false, post: null, commentCount: 0, error: '' @@ -40,6 +40,14 @@ export default class DeletePostModal extends React.Component { ModalStore.removeModalListener(ActionTypes.TOGGLE_DELETE_POST_MODAL, this.handleToggle); } + componentDidUpdate(prevProps, prevState) { + if (this.state.show && !prevState.show) { + setTimeout(() => { + $(ReactDOM.findDOMNode(this.refs.deletePostBtn)).focus(); + }, 0); + } + } + handleDelete() { Client.deletePost( this.state.post.channel_id, @@ -149,10 +157,10 @@ export default class DeletePostModal extends React.Component { {'Cancel'} -- cgit v1.2.3-1-g7c22 From bbcf00f02e469bcf04a45c1cdf8a7932e30ccfc0 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Fri, 22 Jan 2016 09:53:17 -0500 Subject: Add create_at back to profile fields to fix analytics --- web/react/components/admin_console/system_analytics.jsx | 2 +- web/react/components/admin_console/team_analytics.jsx | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) (limited to 'web/react/components') diff --git a/web/react/components/admin_console/system_analytics.jsx b/web/react/components/admin_console/system_analytics.jsx index 640f17ff0..fffe7cc53 100644 --- a/web/react/components/admin_console/system_analytics.jsx +++ b/web/react/components/admin_console/system_analytics.jsx @@ -142,7 +142,7 @@ export default class SystemAnalytics extends React.Component { return (
      19) { break; -- cgit v1.2.3-1-g7c22 From 0d239a1a9e82a1279685b8962920eaf5c1b8f571 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Fri, 22 Jan 2016 10:19:02 -0500 Subject: Added unit test and fixed errors --- web/react/components/admin_console/analytics.jsx | 4 ++-- web/react/components/admin_console/system_analytics.jsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'web/react/components') diff --git a/web/react/components/admin_console/analytics.jsx b/web/react/components/admin_console/analytics.jsx index 8fdf538f8..70ef1ecab 100644 --- a/web/react/components/admin_console/analytics.jsx +++ b/web/react/components/admin_console/analytics.jsx @@ -180,7 +180,7 @@ export default class Analytics extends React.Component { if (this.props.newlyCreatedUsers != null) { let content; if (this.props.newlyCreatedUsers.length === 0) { - content = 'Loading...'; + content = 'Loading...'; } else { content = ( @@ -230,6 +230,7 @@ export default class Analytics extends React.Component { return (

      {'Statistics for ' + this.props.title}

      + {serverError}
      {totalCount} {postCount} @@ -251,7 +252,6 @@ export default class Analytics extends React.Component { } } - Analytics.defaultProps = { title: null, channelOpenCount: null, diff --git a/web/react/components/admin_console/system_analytics.jsx b/web/react/components/admin_console/system_analytics.jsx index fffe7cc53..f54813a94 100644 --- a/web/react/components/admin_console/system_analytics.jsx +++ b/web/react/components/admin_console/system_analytics.jsx @@ -124,7 +124,7 @@ export default class SystemAnalytics extends React.Component { ); } - componentWillReceiveProps(newProps) { + componentWillReceiveProps() { this.setState({ serverError: null, channel_open_count: null, -- cgit v1.2.3-1-g7c22 From 60a73ebabba6798d2b45fa8c8ac0f2bfa6144689 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Fri, 22 Jan 2016 10:24:37 -0500 Subject: Change default system console page to statistics --- web/react/components/admin_console/admin_controller.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'web/react/components') diff --git a/web/react/components/admin_console/admin_controller.jsx b/web/react/components/admin_console/admin_controller.jsx index db98d8f35..efd163017 100644 --- a/web/react/components/admin_console/admin_controller.jsx +++ b/web/react/components/admin_console/admin_controller.jsx @@ -46,7 +46,7 @@ export default class AdminController extends React.Component { config: AdminStore.getConfig(), teams: AdminStore.getAllTeams(), selectedTeams, - selected: props.tab || 'service_settings', + selected: props.tab || 'system_analytics', selectedTeam: props.teamId || null }; -- cgit v1.2.3-1-g7c22 From 329a2a12946e65f3ab095efdf76f95e1fc4c763a Mon Sep 17 00:00:00 2001 From: Reed Garmsen Date: Fri, 22 Jan 2016 14:53:22 -0800 Subject: Fixed duplicate email team signup error --- web/react/components/team_signup_welcome_page.jsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'web/react/components') diff --git a/web/react/components/team_signup_welcome_page.jsx b/web/react/components/team_signup_welcome_page.jsx index aa91a1329..a374dd363 100644 --- a/web/react/components/team_signup_welcome_page.jsx +++ b/web/react/components/team_signup_welcome_page.jsx @@ -59,7 +59,13 @@ export default class TeamSignupWelcomePage extends React.Component { } }.bind(this), function error(err) { - this.setState({serverError: err.message}); + let errorMsg = err.message; + + if (err.detailed_error.indexOf('Invalid RCPT TO address provided') >= 0) { + errorMsg = 'Please enter a valid email address'; + } + + this.setState({emailError: '', serverError: errorMsg}); }.bind(this) ); } -- cgit v1.2.3-1-g7c22 From 43c19981cf67c6b1b3c4f99a1a95fce82d743584 Mon Sep 17 00:00:00 2001 From: hmhealey Date: Sat, 23 Jan 2016 12:15:28 -0500 Subject: Added a limit to the number of suggestions when autocompleting at mentions --- web/react/components/suggestion/at_mention_provider.jsx | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'web/react/components') diff --git a/web/react/components/suggestion/at_mention_provider.jsx b/web/react/components/suggestion/at_mention_provider.jsx index 8c2893448..e502c981d 100644 --- a/web/react/components/suggestion/at_mention_provider.jsx +++ b/web/react/components/suggestion/at_mention_provider.jsx @@ -5,6 +5,8 @@ import SuggestionStore from '../../stores/suggestion_store.jsx'; import UserStore from '../../stores/user_store.jsx'; import * as Utils from '../../utils/utils.jsx'; +const MaxUserSuggestions = 40; + class AtMentionSuggestion extends React.Component { render() { const {item, isSelection, onClick} = this.props; @@ -78,6 +80,10 @@ export default class AtMentionProvider { if (user.username.startsWith(usernamePrefix)) { filtered.push(user); } + + if (filtered.length >= MaxUserSuggestions) { + break; + } } // add dummy users to represent the @all and @channel special mentions -- cgit v1.2.3-1-g7c22 From 3ec816cc0ecacbb88a1bb829650bc615b1e3afa5 Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Sun, 24 Jan 2016 23:49:01 -0500 Subject: PLT-1576 display iOS device in session history --- web/react/components/activity_log_modal.jsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'web/react/components') diff --git a/web/react/components/activity_log_modal.jsx b/web/react/components/activity_log_modal.jsx index f5341c0bc..6a880f0ee 100644 --- a/web/react/components/activity_log_modal.jsx +++ b/web/react/components/activity_log_modal.jsx @@ -100,8 +100,12 @@ export default class ActivityLogModal extends React.Component { if (currentSession.props.platform === 'Windows') { devicePicture = 'fa fa-windows'; - } else if (currentSession.props.platform === 'Macintosh' || currentSession.props.platform === 'iPhone') { + } else if (currentSession.props.platform === 'Macintosh' || + currentSession.props.platform === 'iPhone') { devicePicture = 'fa fa-apple'; + } else if (currentSession.props.platform.browser.indexOf('Mattermost/') === 0) { + devicePicture = 'fa fa-apple'; + devicePlatform = 'iPhone'; } else if (currentSession.props.platform === 'Linux') { if (currentSession.props.os.indexOf('Android') >= 0) { devicePlatform = 'Android'; -- cgit v1.2.3-1-g7c22 From 68285cf1a9d5b37e857298a84e863cad6ffcdc7a Mon Sep 17 00:00:00 2001 From: hmhealey Date: Mon, 25 Jan 2016 09:49:25 -0500 Subject: Fixed info displayed for files with 0 size or with no extension --- web/react/components/file_info_preview.jsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'web/react/components') diff --git a/web/react/components/file_info_preview.jsx b/web/react/components/file_info_preview.jsx index 4b76cd162..45d89007f 100644 --- a/web/react/components/file_info_preview.jsx +++ b/web/react/components/file_info_preview.jsx @@ -5,11 +5,16 @@ import * as Utils from '../utils/utils.jsx'; export default function FileInfoPreview({filename, fileUrl, fileInfo}) { // non-image files include a section providing details about the file - let infoString = 'File type ' + fileInfo.extension.toUpperCase(); - if (fileInfo.size > 0) { - infoString += ', Size ' + Utils.fileSizeToString(fileInfo.size); + const infoParts = []; + + if (fileInfo.extension !== '') { + infoParts.push('File type ' + fileInfo.extension.toUpperCase()); } + infoParts.push('Size ' + Utils.fileSizeToString(fileInfo.size)); + + const infoString = infoParts.join(', '); + const name = decodeURIComponent(Utils.getFileName(filename)); return ( -- cgit v1.2.3-1-g7c22