summaryrefslogtreecommitdiffstats
path: root/webapp
diff options
context:
space:
mode:
authorHarrison Healey <harrisonmhealey@gmail.com>2016-08-16 14:41:47 -0400
committerChristopher Speller <crspeller@gmail.com>2016-08-16 14:41:47 -0400
commit8203fd16ce3356d69b0cc51287d0a1fc25318b2d (patch)
treea25893649505d0a75fc1d0aac16790b3e07981c4 /webapp
parentdde158c57f24e6da6ad5d05eebc104fccec855e8 (diff)
downloadchat-8203fd16ce3356d69b0cc51287d0a1fc25318b2d.tar.gz
chat-8203fd16ce3356d69b0cc51287d0a1fc25318b2d.tar.bz2
chat-8203fd16ce3356d69b0cc51287d0a1fc25318b2d.zip
PLT-3647 Email Batching (#3718)
* PLT-3647 Added config settings for email batching * PLT-3647 Refactored generation of email notification * PLT-3647 Added serverside code for email batching * PLT-3647 Updated settings UI to enable email batching * PLT-3647 Removed debug code * PLT-3647 Fixed 0-padding of minutes in batched notification * PLT-3647 Updated clientside UI for when email batching is disabled * Go fmt * PLT-3647 Changed email batching to be disabled by default * Updated batched email message * Added email batching toggle to system console * Changed Email Notifications > Immediate setting to a 30 second batch interval * Go fmt * Fixed link to Mattermost icon in batched email notification * Updated users to use 30 second email batching by default * Fully disabled email batching when clustering is enabled * Fixed email batching setting in the system console * Fixed casing of 'Send Email notifications' -> 'Send email notifications' * Updating UI Improvements for email batching (#3736) * Updated text for notification settings and SiteURL. * Prevented enabling email batching when SiteURL isn't set in the system console * Re-added a couple debug messages * Added warning text when clustering is enabled
Diffstat (limited to 'webapp')
-rw-r--r--webapp/components/admin_console/configuration_settings.jsx2
-rw-r--r--webapp/components/admin_console/connection_security_dropdown_setting.jsx12
-rw-r--r--webapp/components/admin_console/email_settings.jsx50
-rw-r--r--webapp/components/admin_console/log_settings.jsx18
-rw-r--r--webapp/components/admin_console/webserver_mode_dropdown_setting.jsx12
-rw-r--r--webapp/components/user_settings/email_notification_setting.jsx211
-rw-r--r--webapp/components/user_settings/user_settings_notifications.jsx197
-rw-r--r--webapp/i18n/en.json16
-rw-r--r--webapp/sass/routes/_admin-console.scss1
-rw-r--r--webapp/utils/constants.jsx4
10 files changed, 349 insertions, 174 deletions
diff --git a/webapp/components/admin_console/configuration_settings.jsx b/webapp/components/admin_console/configuration_settings.jsx
index 6a07e31cd..1207f1f79 100644
--- a/webapp/components/admin_console/configuration_settings.jsx
+++ b/webapp/components/admin_console/configuration_settings.jsx
@@ -69,7 +69,7 @@ export default class ConfigurationSettings extends AdminSettings {
helpText={
<FormattedMessage
id='admin.service.siteURLDescription'
- defaultMessage='The URL, including port number and protocol, from which users will access Mattermost. Leave blank to automatically configure based on incoming traffic.'
+ defaultMessage='The URL, including post number and protocol, that users will use to access Mattermost. Leave blank to automatically configure based on incoming traffic.'
/>
}
value={this.state.siteURL}
diff --git a/webapp/components/admin_console/connection_security_dropdown_setting.jsx b/webapp/components/admin_console/connection_security_dropdown_setting.jsx
index b3e9ac31c..09768049e 100644
--- a/webapp/components/admin_console/connection_security_dropdown_setting.jsx
+++ b/webapp/components/admin_console/connection_security_dropdown_setting.jsx
@@ -14,13 +14,13 @@ const CONNECTION_SECURITY_HELP_TEXT = (
>
<tbody>
<tr>
- <td className='help-text'>
+ <td>
<FormattedMessage
id='admin.connectionSecurityNone'
defaultMessage='None'
/>
</td>
- <td className='help-text'>
+ <td>
<FormattedMessage
id='admin.connectionSecurityNoneDescription'
defaultMessage='Mattermost will connect over an unsecure connection.'
@@ -28,13 +28,13 @@ const CONNECTION_SECURITY_HELP_TEXT = (
</td>
</tr>
<tr>
- <td className='help-text'>
+ <td>
<FormattedMessage
id='admin.connectionSecurityTls'
defaultMessage='TLS'
/>
</td>
- <td className='help-text'>
+ <td>
<FormattedMessage
id='admin.connectionSecurityTlsDescription'
defaultMessage='Encrypts the communication between Mattermost and your server.'
@@ -42,13 +42,13 @@ const CONNECTION_SECURITY_HELP_TEXT = (
</td>
</tr>
<tr>
- <td className='help-text'>
+ <td>
<FormattedMessage
id='admin.connectionSecurityStart'
defaultMessage='STARTTLS'
/>
</td>
- <td className='help-text'>
+ <td>
<FormattedMessage
id='admin.connectionSecurityStartDescription'
defaultMessage='Takes an existing insecure connection and attempts to upgrade it to a secure connection using TLS.'
diff --git a/webapp/components/admin_console/email_settings.jsx b/webapp/components/admin_console/email_settings.jsx
index 01f38dc21..b05a3f905 100644
--- a/webapp/components/admin_console/email_settings.jsx
+++ b/webapp/components/admin_console/email_settings.jsx
@@ -32,6 +32,7 @@ export default class EmailSettings extends AdminSettings {
config.EmailSettings.SMTPServer = this.state.smtpServer;
config.EmailSettings.SMTPPort = this.state.smtpPort;
config.EmailSettings.ConnectionSecurity = this.state.connectionSecurity;
+ config.EmailSettings.EnableEmailBatching = this.state.enableEmailBatching;
config.ServiceSettings.EnableSecurityFixAlert = this.state.enableSecurityFixAlert;
return config;
@@ -48,6 +49,7 @@ export default class EmailSettings extends AdminSettings {
smtpServer: config.EmailSettings.SMTPServer,
smtpPort: config.EmailSettings.SMTPPort,
connectionSecurity: config.EmailSettings.ConnectionSecurity,
+ enableEmailBatching: config.EmailSettings.EnableEmailBatching,
enableSecurityFixAlert: config.ServiceSettings.EnableSecurityFixAlert
};
}
@@ -64,6 +66,34 @@ export default class EmailSettings extends AdminSettings {
}
renderSettings() {
+ let enableEmailBatchingDisabledText = null;
+
+ if (this.props.config.ClusterSettings.Enable) {
+ enableEmailBatchingDisabledText = (
+ <span
+ key='admin.email.enableEmailBatching.clusterEnabled'
+ className='help-text'
+ >
+ <FormattedHTMLMessage
+ id='admin.email.enableEmailBatching.clusterEnabled'
+ defaultMessage='Email batching cannot be enabled unless the SiteURL is configured in <b>Configuration > SiteURL</b>.'
+ />
+ </span>
+ );
+ } else if (!this.props.config.ServiceSettings.SiteURL) {
+ enableEmailBatchingDisabledText = (
+ <span
+ key='admin.email.enableEmailBatching.siteURL'
+ className='help-text'
+ >
+ <FormattedHTMLMessage
+ id='admin.email.enableEmailBatching.siteURL'
+ defaultMessage='Email batching cannot be enabled unless the SiteURL is configured in <b>Configuration > SiteURL</b>.'
+ />
+ </span>
+ );
+ }
+
return (
<SettingsGroup>
<BooleanSetting
@@ -83,6 +113,26 @@ export default class EmailSettings extends AdminSettings {
value={this.state.sendEmailNotifications}
onChange={this.handleChange}
/>
+ <BooleanSetting
+ id='enableEmailBatching'
+ label={
+ <FormattedMessage
+ id='admin.email.enableEmailBatchingTitle'
+ defaultMessage='Enable Email Batching: '
+ />
+ }
+ helpText={[
+ <FormattedHTMLMessage
+ key='admin.email.enableEmailBatchingDesc'
+ id='admin.email.enableEmailBatchingDesc'
+ defaultMessage='When true, users can have email notifications for multiple direct messages and mentions combined into a single email, configurable in <b>Account Settings > Notifications</b>.'
+ />,
+ enableEmailBatchingDisabledText
+ ]}
+ value={this.state.enableEmailBatching && !this.props.config.ClusterSettings.Enable && this.props.config.ServiceSettings.SiteURL}
+ onChange={this.handleChange}
+ disabled={!this.state.sendEmailNotifications || this.props.config.ClusterSettings.Enable || !this.props.config.ServiceSettings.SiteURL}
+ />
<TextSetting
id='feedbackName'
label={
diff --git a/webapp/components/admin_console/log_settings.jsx b/webapp/components/admin_console/log_settings.jsx
index efd1bf342..31abca316 100644
--- a/webapp/components/admin_console/log_settings.jsx
+++ b/webapp/components/admin_console/log_settings.jsx
@@ -204,7 +204,8 @@ export default class LogSettings extends AdminSettings {
>
<tbody>
<tr>
- <td className='help-text'>{'%T'}</td><td className='help-text'>
+ <td>{'%T'}</td>
+ <td>
<FormattedMessage
id='admin.log.formatTime'
defaultMessage='Time (15:04:05 MST)'
@@ -212,7 +213,8 @@ export default class LogSettings extends AdminSettings {
</td>
</tr>
<tr>
- <td className='help-text'>{'%D'}</td><td className='help-text'>
+ <td>{'%D'}</td>
+ <td>
<FormattedMessage
id='admin.log.formatDateLong'
defaultMessage='Date (2006/01/02)'
@@ -220,7 +222,8 @@ export default class LogSettings extends AdminSettings {
</td>
</tr>
<tr>
- <td className='help-text'>{'%d'}</td><td className='help-text'>
+ <td>{'%d'}</td>
+ <td>
<FormattedMessage
id='admin.log.formatDateShort'
defaultMessage='Date (01/02/06)'
@@ -228,7 +231,8 @@ export default class LogSettings extends AdminSettings {
</td>
</tr>
<tr>
- <td className='help-text'>{'%L'}</td><td className='help-text'>
+ <td>{'%L'}</td>
+ <td>
<FormattedMessage
id='admin.log.formatLevel'
defaultMessage='Level (DEBG, INFO, EROR)'
@@ -236,7 +240,8 @@ export default class LogSettings extends AdminSettings {
</td>
</tr>
<tr>
- <td className='help-text'>{'%S'}</td><td className='help-text'>
+ <td>{'%S'}</td>
+ <td>
<FormattedMessage
id='admin.log.formatSource'
defaultMessage='Source'
@@ -244,7 +249,8 @@ export default class LogSettings extends AdminSettings {
</td>
</tr>
<tr>
- <td className='help-text'>{'%M'}</td><td className='help-text'>
+ <td>{'%M'}</td>
+ <td>
<FormattedMessage
id='admin.log.formatMessage'
defaultMessage='Message'
diff --git a/webapp/components/admin_console/webserver_mode_dropdown_setting.jsx b/webapp/components/admin_console/webserver_mode_dropdown_setting.jsx
index 9581816f1..1616ced23 100644
--- a/webapp/components/admin_console/webserver_mode_dropdown_setting.jsx
+++ b/webapp/components/admin_console/webserver_mode_dropdown_setting.jsx
@@ -15,13 +15,13 @@ const WEBSERVER_MODE_HELP_TEXT = (
>
<tbody>
<tr>
- <td className='help-text'>
+ <td>
<FormattedMessage
id='admin.webserverModeGzip'
defaultMessage='gzip'
/>
</td>
- <td className='help-text'>
+ <td>
<FormattedMessage
id='admin.webserverModeGzipDescription'
defaultMessage='The Mattermost server will serve static files compressed with gzip.'
@@ -29,13 +29,13 @@ const WEBSERVER_MODE_HELP_TEXT = (
</td>
</tr>
<tr>
- <td className='help-text'>
+ <td>
<FormattedMessage
id='admin.webserverModeUncompressed'
defaultMessage='Uncompressed'
/>
</td>
- <td className='help-text'>
+ <td>
<FormattedMessage
id='admin.webserverModeUncompressedDescription'
defaultMessage='The Mattermost server will serve static files uncompressed.'
@@ -43,13 +43,13 @@ const WEBSERVER_MODE_HELP_TEXT = (
</td>
</tr>
<tr>
- <td className='help-text'>
+ <td>
<FormattedMessage
id='admin.webserverModeDisabled'
defaultMessage='Disabled'
/>
</td>
- <td className='help-text'>
+ <td>
<FormattedMessage
id='admin.webserverModeDisabledDescription'
defaultMessage='The Mattermost server will not serve static files.'
diff --git a/webapp/components/user_settings/email_notification_setting.jsx b/webapp/components/user_settings/email_notification_setting.jsx
new file mode 100644
index 000000000..df5f80c64
--- /dev/null
+++ b/webapp/components/user_settings/email_notification_setting.jsx
@@ -0,0 +1,211 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React from 'react';
+
+import {savePreference} from 'utils/async_client.jsx';
+import PreferenceStore from 'stores/preference_store.jsx';
+import {localizeMessage} from 'utils/utils.jsx';
+
+import {FormattedMessage} from 'react-intl';
+import SettingItemMin from 'components/setting_item_min.jsx';
+import SettingItemMax from 'components/setting_item_max.jsx';
+
+import {Preferences} from 'utils/constants.jsx';
+
+const INTERVAL_IMMEDIATE = 30; // "immediate" is a 30 second interval
+const INTERVAL_FIFTEEN_MINUTES = 15 * 60;
+const INTERVAL_HOUR = 60 * 60;
+
+export default class EmailNotificationSetting extends React.Component {
+ static propTypes = {
+ activeSection: React.PropTypes.string.isRequired,
+ updateSection: React.PropTypes.func.isRequired,
+ enableEmail: React.PropTypes.bool.isRequired,
+ onChange: React.PropTypes.func.isRequired,
+ onSubmit: React.PropTypes.func.isRequired,
+ serverError: React.PropTypes.string.isRequired
+ };
+
+ constructor(props) {
+ super(props);
+
+ this.submit = this.submit.bind(this);
+
+ this.expand = this.expand.bind(this);
+ this.collapse = this.collapse.bind(this);
+
+ this.state = {
+ emailInterval: PreferenceStore.getInt(Preferences.CATEGORY_NOTIFICATIONS, Preferences.EMAIL_INTERVAL, INTERVAL_IMMEDIATE)
+ };
+ }
+
+ handleChange(enableEmail, emailInterval) {
+ this.props.onChange(enableEmail);
+ this.setState({emailInterval});
+ }
+
+ submit() {
+ // until the rest of the notification settings are moved to preferences, we have to do this separately
+ savePreference(Preferences.CATEGORY_NOTIFICATIONS, Preferences.EMAIL_INTERVAL, this.state.emailInterval.toString());
+
+ this.props.onSubmit();
+ }
+
+ expand() {
+ this.props.updateSection('email');
+ }
+
+ collapse() {
+ this.props.updateSection('');
+ }
+
+ render() {
+ if (this.props.activeSection !== 'email') {
+ let description;
+
+ if (this.props.enableEmail === 'true') {
+ switch (this.state.emailInterval) {
+ case INTERVAL_IMMEDIATE:
+ description = (
+ <FormattedMessage
+ id='user.settings.notifications.email.immediately'
+ defaultMessage='Immediately'
+ />
+ );
+ break;
+ case INTERVAL_HOUR:
+ description = (
+ <FormattedMessage
+ id='user.settings.notifications.email.everyHour'
+ defaultMessage='Every hour'
+ />
+ );
+ break;
+ default:
+ description = (
+ <FormattedMessage
+ id='user.settings.notifications.email.everyXMinutes'
+ defaultMessage='Every {count, plural, one {minute} other {{count, number} minutes}}'
+ values={{count: this.state.emailInterval / 60}}
+ />
+ );
+ }
+ } else {
+ description = (
+ <FormattedMessage
+ id='user.settings.notifications.email.never'
+ defaultMessage='Never'
+ />
+ );
+ }
+
+ return (
+ <SettingItemMin
+ title={localizeMessage('user.settings.notifications.emailNotifications', 'Send Email notifications')}
+ describe={description}
+ updateSection={this.expand}
+ />
+ );
+ }
+
+ let batchingOptions = null;
+ let batchingInfo = null;
+ if (window.mm_config.EnableEmailBatching === 'true') {
+ batchingOptions = (
+ <div>
+ <div className='radio'>
+ <label>
+ <input
+ type='radio'
+ name='emailNotifications'
+ checked={this.props.enableEmail === 'true' && this.state.emailInterval === INTERVAL_FIFTEEN_MINUTES}
+ onChange={this.handleChange.bind(this, 'true', INTERVAL_FIFTEEN_MINUTES)}
+ />
+ <FormattedMessage
+ id='user.settings.notifications.email.everyXMinutes'
+ defaultMessage='Every {count} minutes'
+ values={{count: INTERVAL_FIFTEEN_MINUTES / 60}}
+ />
+ </label>
+ </div>
+ <div className='radio'>
+ <label>
+ <input
+ type='radio'
+ name='emailNotifications'
+ checked={this.props.enableEmail === 'true' && this.state.emailInterval === INTERVAL_HOUR}
+ onChange={this.handleChange.bind(this, 'true', INTERVAL_HOUR)}
+ />
+ <FormattedMessage
+ id='user.settings.notifications.everyHour'
+ defaultMessage='Every hour'
+ />
+ </label>
+ </div>
+ </div>
+ );
+
+ batchingInfo = (
+ <FormattedMessage
+ id='user.settings.notifications.emailBatchingInfo'
+ defaultMessage='Notifications are combined into a single email and sent at the maximum frequency selected here.'
+ />
+ );
+ }
+
+ return (
+ <SettingItemMax
+ title={localizeMessage('user.settings.notifications.emailNotifications', 'Send email notifications')}
+ inputs={[
+ <div key='userNotificationEmailOptions'>
+ <div className='radio'>
+ <label>
+ <input
+ type='radio'
+ name='emailNotifications'
+ checked={this.props.enableEmail === 'true' && this.state.emailInterval === INTERVAL_IMMEDIATE}
+ onChange={this.handleChange.bind(this, 'true', INTERVAL_IMMEDIATE)}
+ />
+ <FormattedMessage
+ id='user.settings.notifications.email.immediately'
+ defaultMessage='Immediately'
+ />
+ </label>
+ </div>
+ {batchingOptions}
+ <div className='radio'>
+ <label>
+ <input
+ type='radio'
+ name='emailNotifications'
+ checked={this.props.enableEmail === 'false'}
+ onChange={this.handleChange.bind(this, 'false', INTERVAL_IMMEDIATE)}
+ />
+ <FormattedMessage
+ id='user.settings.notifications.never'
+ defaultMessage='Never'
+ />
+ </label>
+ </div>
+ <br/>
+ <div>
+ <FormattedMessage
+ id='user.settings.notifications.emailInfo'
+ defaultMessage='Email notifications that are sent for mentions and direct messages when you are offline or away from {siteName} for more than 5 minutes.'
+ values={{
+ siteName: global.window.mm_config.SiteName
+ }}
+ />
+ {' '}
+ {batchingInfo}
+ </div>
+ </div>
+ ]}
+ submit={this.submit}
+ server_error={this.props.serverError}
+ updateSection={this.collapse}
+ />
+ );
+ }
+} \ No newline at end of file
diff --git a/webapp/components/user_settings/user_settings_notifications.jsx b/webapp/components/user_settings/user_settings_notifications.jsx
index e0e3bf979..17abfa555 100644
--- a/webapp/components/user_settings/user_settings_notifications.jsx
+++ b/webapp/components/user_settings/user_settings_notifications.jsx
@@ -2,8 +2,8 @@
// See License.txt for license information.
import $ from 'jquery';
-import SettingItemMin from '../setting_item_min.jsx';
-import SettingItemMax from '../setting_item_max.jsx';
+import SettingItemMin from 'components/setting_item_min.jsx';
+import SettingItemMax from 'components/setting_item_max.jsx';
import UserStore from 'stores/user_store.jsx';
@@ -12,7 +12,8 @@ import * as AsyncClient from 'utils/async_client.jsx';
import * as UserAgent from 'utils/user_agent.jsx';
import * as Utils from 'utils/utils.jsx';
-import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl';
+import EmailNotificationSetting from './email_notification_setting.jsx';
+import {FormattedMessage} from 'react-intl';
function getNotificationsStateFromStores() {
var user = UserStore.getCurrentUser();
@@ -30,9 +31,9 @@ function getNotificationsStateFromStores() {
if (user.notify_props && user.notify_props.comments) {
comments = user.notify_props.comments;
}
- var email = 'true';
+ var enableEmail = 'true';
if (user.notify_props && user.notify_props.email) {
- email = user.notify_props.email;
+ enableEmail = user.notify_props.email;
}
var push = 'mention';
if (user.notify_props && user.notify_props.push) {
@@ -78,7 +79,7 @@ function getNotificationsStateFromStores() {
return {
notifyLevel: desktop,
notifyPushLevel: push,
- enableEmail: email,
+ enableEmail,
soundNeeded,
enableSound: sound,
usernameKey,
@@ -91,36 +92,9 @@ function getNotificationsStateFromStores() {
};
}
-const holders = defineMessages({
- desktop: {
- id: 'user.settings.notifications.desktop',
- defaultMessage: 'Send desktop notifications'
- },
- desktopSounds: {
- id: 'user.settings.notifications.desktopSounds',
- defaultMessage: 'Desktop notification sounds'
- },
- emailNotifications: {
- id: 'user.settings.notifications.emailNotifications',
- defaultMessage: 'Email notifications'
- },
- wordsTrigger: {
- id: 'user.settings.notifications.wordsTrigger',
- defaultMessage: 'Words that trigger mentions'
- },
- comments: {
- id: 'user.settings.notifications.comments',
- defaultMessage: 'Comment threads notifications'
- },
- close: {
- id: 'user.settings.notifications.close',
- defaultMessage: 'Close'
- }
-});
-
import React from 'react';
-class NotificationsTab extends React.Component {
+export default class NotificationsTab extends React.Component {
constructor(props) {
super(props);
@@ -142,6 +116,7 @@ class NotificationsTab extends React.Component {
this.state = getNotificationsStateFromStores();
}
+
handleSubmit() {
var data = {};
data.user_id = this.props.user.id;
@@ -178,31 +153,38 @@ class NotificationsTab extends React.Component {
}
);
}
+
handleCancel(e) {
this.updateState();
this.props.updateSection('');
e.preventDefault();
$('.settings-modal .modal-body').scrollTop(0).perfectScrollbar('update');
}
+
updateSection(section) {
this.updateState();
this.props.updateSection(section);
}
+
updateState() {
const newState = getNotificationsStateFromStores();
if (!Utils.areObjectsEqual(newState, this.state)) {
this.setState(newState);
}
}
+
componentDidMount() {
UserStore.addChangeListener(this.onListenerChange);
}
+
componentWillUnmount() {
UserStore.removeChangeListener(this.onListenerChange);
}
+
onListenerChange() {
this.updateState();
}
+
handleNotifyRadio(notifyLevel) {
this.setState({notifyLevel});
this.refs.wrapper.focus();
@@ -222,22 +204,28 @@ class NotificationsTab extends React.Component {
this.setState({enableEmail});
this.refs.wrapper.focus();
}
+
handleSoundRadio(enableSound) {
this.setState({enableSound});
this.refs.wrapper.focus();
}
+
updateUsernameKey(val) {
this.setState({usernameKey: val});
}
+
updateMentionKey(val) {
this.setState({mentionKey: val});
}
+
updateFirstNameKey(val) {
this.setState({firstNameKey: val});
}
+
updateChannelKey(val) {
this.setState({channelKey: val});
}
+
updateCustomMentionKeys() {
var checked = this.refs.customcheck.checked;
@@ -250,10 +238,12 @@ class NotificationsTab extends React.Component {
this.setState({customKeys: '', customKeysChecked: false});
}
}
+
onCustomChange() {
this.refs.customcheck.checked = true;
this.updateCustomMentionKeys();
}
+
createPushNotificationSection() {
var handleUpdateDesktopSection;
if (this.props.activeSection === 'push') {
@@ -312,8 +302,8 @@ class NotificationsTab extends React.Component {
onChange={this.handlePushRadio.bind(this, 'none')}
/>
<FormattedMessage
- id='user.settings.push_notification.off'
- defaultMessage='Off'
+ id='user.settings.notifications.never'
+ defaultMessage='Never'
/>
</label>
</div>
@@ -346,7 +336,7 @@ class NotificationsTab extends React.Component {
return (
<SettingItemMax
- title={Utils.localizeMessage('user.settings.notifications.push', 'Mobile push notifications')}
+ title={Utils.localizeMessage('user.settings.notifications.push', 'Send mobile push notifications')}
extraInfo={extraInfo}
inputs={inputs}
submit={submit}
@@ -367,8 +357,8 @@ class NotificationsTab extends React.Component {
} else if (this.state.notifyPushLevel === 'none') {
describe = (
<FormattedMessage
- id='user.settings.push_notification.off'
- defaultMessage='Off'
+ id='user.settings.notifications.never'
+ defaultMessage='Never'
/>
);
} else if (global.window.mm_config.SendPushNotifications === 'false') {
@@ -393,14 +383,14 @@ class NotificationsTab extends React.Component {
return (
<SettingItemMin
- title={Utils.localizeMessage('user.settings.notifications.push', 'Mobile push notifications')}
+ title={Utils.localizeMessage('user.settings.notifications.push', 'Send mobile push notifications')}
describe={describe}
updateSection={handleUpdateDesktopSection}
/>
);
}
+
render() {
- const {formatMessage} = this.props.intl;
const serverError = this.state.serverError;
var user = this.props.user;
@@ -479,7 +469,7 @@ class NotificationsTab extends React.Component {
desktopSection = (
<SettingItemMax
- title={formatMessage(holders.desktop)}
+ title={Utils.localizeMessage('user.settings.notifications.desktop', 'Send desktop notifications')}
extraInfo={extraInfo}
inputs={inputs}
submit={this.handleSubmit}
@@ -518,7 +508,7 @@ class NotificationsTab extends React.Component {
desktopSection = (
<SettingItemMin
- title={formatMessage(holders.desktop)}
+ title={Utils.localizeMessage('user.settings.notifications.desktop', 'Send desktop notifications')}
describe={describe}
updateSection={handleUpdateDesktopSection}
/>
@@ -583,7 +573,7 @@ class NotificationsTab extends React.Component {
soundSection = (
<SettingItemMax
- title={formatMessage(holders.desktopSounds)}
+ title={Utils.localizeMessage('user.settings.notifications.desktopSounds', 'Desktop notification sounds')}
extraInfo={extraInfo}
inputs={inputs}
submit={this.handleSubmit}
@@ -622,7 +612,7 @@ class NotificationsTab extends React.Component {
soundSection = (
<SettingItemMin
- title={formatMessage(holders.desktopSounds)}
+ title={Utils.localizeMessage('user.settings.notifications.desktopSounds', 'Desktop notification sounds')}
describe={describe}
updateSection={handleUpdateSoundSection}
disableOpen={!this.state.soundNeeded}
@@ -630,102 +620,6 @@ class NotificationsTab extends React.Component {
);
}
- var emailSection;
- var handleUpdateEmailSection;
- if (this.props.activeSection === 'email') {
- var emailActive = [false, false];
- if (this.state.enableEmail === 'false') {
- emailActive[1] = true;
- } else {
- emailActive[0] = true;
- }
-
- let inputs = [];
-
- inputs.push(
- <div key='userNotificationEmailOptions'>
- <div className='radio'>
- <label>
- <input
- type='radio'
- name='emailNotifications'
- checked={emailActive[0]}
- onChange={this.handleEmailRadio.bind(this, 'true')}
- />
- <FormattedMessage
- id='user.settings.notifications.on'
- defaultMessage='On'
- />
- </label>
- <br/>
- </div>
- <div className='radio'>
- <label>
- <input
- type='radio'
- name='emailNotifications'
- checked={emailActive[1]}
- onChange={this.handleEmailRadio.bind(this, 'false')}
- />
- <FormattedMessage
- id='user.settings.notifications.off'
- defaultMessage='Off'
- />
- </label>
- <br/>
- </div>
- <div><br/>
- <FormattedMessage
- id='user.settings.notifications.emailInfo'
- defaultMessage='Email notifications are sent for mentions and direct messages after you’ve been offline for more than 60 seconds or away from {siteName} for more than 5 minutes.'
- values={{
- siteName: global.window.mm_config.SiteName
- }}
- />
- </div>
- </div>
- );
-
- emailSection = (
- <SettingItemMax
- title={formatMessage(holders.emailNotifications)}
- inputs={inputs}
- submit={this.handleSubmit}
- server_error={serverError}
- updateSection={this.handleCancel}
- />
- );
- } else {
- let describe = '';
- if (this.state.enableEmail === 'false') {
- describe = (
- <FormattedMessage
- id='user.settings.notifications.off'
- defaultMessage='Off'
- />
- );
- } else {
- describe = (
- <FormattedMessage
- id='user.settings.notifications.on'
- defaultMessage='On'
- />
- );
- }
-
- handleUpdateEmailSection = function updateEmailSection() {
- this.props.updateSection('email');
- }.bind(this);
-
- emailSection = (
- <SettingItemMin
- title={formatMessage(holders.emailNotifications)}
- describe={describe}
- updateSection={handleUpdateEmailSection}
- />
- );
- }
-
var keysSection;
var handleUpdateKeysSection;
if (this.props.activeSection === 'keys') {
@@ -859,7 +753,7 @@ class NotificationsTab extends React.Component {
keysSection = (
<SettingItemMax
- title={formatMessage(holders.wordsTrigger)}
+ title={Utils.localizeMessage('user.settings.notifications.wordsTrigger', 'Words that trigger mentions')}
inputs={inputs}
submit={this.handleSubmit}
server_error={serverError}
@@ -910,7 +804,7 @@ class NotificationsTab extends React.Component {
keysSection = (
<SettingItemMin
- title={formatMessage(holders.wordsTrigger)}
+ title={Utils.localizeMessage('user.settings.notifications.wordsTrigger', 'Words that trigger mentions')}
describe={describe}
updateSection={handleUpdateKeysSection}
/>
@@ -991,7 +885,7 @@ class NotificationsTab extends React.Component {
commentsSection = (
<SettingItemMax
- title={formatMessage(holders.comments)}
+ title={Utils.localizeMessage('user.settings.notifications.comments', 'Comment threads notifications')}
extraInfo={extraInfo}
inputs={inputs}
submit={this.handleSubmit}
@@ -1030,7 +924,7 @@ class NotificationsTab extends React.Component {
commentsSection = (
<SettingItemMin
- title={formatMessage(holders.comments)}
+ title={Utils.localizeMessage('user.settings.notifications.comments', 'Comment threads notifications')}
describe={describe}
updateSection={handleUpdateCommentsSection}
/>
@@ -1046,7 +940,6 @@ class NotificationsTab extends React.Component {
type='button'
className='close'
data-dismiss='modal'
- aria-label={formatMessage(holders.close)}
onClick={this.props.closeModal}
>
<span aria-hidden='true'>{'×'}</span>
@@ -1082,7 +975,14 @@ class NotificationsTab extends React.Component {
<div className='divider-light'/>
{soundSection}
<div className='divider-light'/>
- {emailSection}
+ <EmailNotificationSetting
+ activeSection={this.props.activeSection}
+ updateSection={this.props.updateSection}
+ enableEmail={this.state.enableEmail}
+ onChange={this.handleEmailRadio}
+ onSubmit={this.handleSubmit}
+ serverError={this.state.serverError}
+ />
<div className='divider-light'/>
{pushNotificationSection}
<div className='divider-light'/>
@@ -1103,7 +1003,6 @@ NotificationsTab.defaultProps = {
activeTab: ''
};
NotificationsTab.propTypes = {
- intl: intlShape.isRequired,
user: React.PropTypes.object,
updateSection: React.PropTypes.func,
updateTab: React.PropTypes.func,
@@ -1112,5 +1011,3 @@ NotificationsTab.propTypes = {
closeModal: React.PropTypes.func.isRequired,
collapseModal: React.PropTypes.func.isRequired
};
-
-export default injectIntl(NotificationsTab);
diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json
index 3922f41bb..e51d9b43a 100644
--- a/webapp/i18n/en.json
+++ b/webapp/i18n/en.json
@@ -234,6 +234,10 @@
"admin.email.easHelp": "Learn more about compiling and deploying your own mobile apps from an <a href=\"http://docs.mattermost.com/deployment/push.html#enterprise-app-store-eas\" target=\"_blank\">Enterprise App Store</a>.",
"admin.email.emailFail": "Connection unsuccessful: {error}",
"admin.email.emailSuccess": "No errors were reported while sending an email. Please check your inbox to make sure.",
+ "admin.email.enableEmailBatching.clusterEnabled": "Email batching cannot be enabled when High Availability mode is enabled.",
+ "admin.email.enableEmailBatching.siteURL": "Email batching cannot be enabled unless the SiteURL is configured in <b>Configuration > SiteURL</b>.",
+ "admin.email.enableEmailBatchingTitle": "Enable Email Batching:",
+ "admin.email.enableEmailBatchingDesc": "When true, users can have email notifications for multiple direct messages and mentions combined into a single email, configurable in <b>Account Settings > Notifications</b>.",
"admin.email.fullPushNotification": "Send full message snippet",
"admin.email.genericPushNotification": "Send generic description with user and channel names",
"admin.email.inviteSaltDescription": "32-character salt added to signing of email invites. Randomly generated on install. Click \"Regenerate\" to create new salt.",
@@ -649,7 +653,7 @@
"admin.service.sessionCacheDesc": "The number of minutes to cache a session in memory.",
"admin.service.sessionDaysEx": "Ex \"30\"",
"admin.service.siteURL": "Site URL:",
- "admin.service.siteURLDescription": "The URL, including port number and protocol, from which users will access Mattermost. Leave blank to automatically configure based on incoming traffic.",
+ "admin.service.siteURLDescription": "The URL, including post number and protocol, that users will use to access Mattermost. Leave blank to automatically configure based on incoming traffic.",
"admin.service.siteURLExample": "Ex \"https://mattermost.example.com:1234\"",
"admin.service.ssoSessionDays": "Session length SSO (days):",
"admin.service.ssoSessionDaysDesc": "The number of days from the last time a user entered their credentials to the expiry of the user's session. If the authentication method is SAML or GitLab, the user may automatically be logged back in to Mattermost if they are already logged in to SAML or GitLab. After changing this setting, the setting will take effect after the next time the user enters their credentials.",
@@ -1764,16 +1768,20 @@
"user.settings.notifications.commentsRoot": "Mention any comments on your post",
"user.settings.notifications.desktop": "Send desktop notifications",
"user.settings.notifications.desktopSounds": "Desktop notification sounds",
- "user.settings.notifications.emailInfo": "Email notifications are sent for mentions and direct messages after you’ve been offline for more than 60 seconds or away from {siteName} for more than 5 minutes.",
- "user.settings.notifications.emailNotifications": "Email notifications",
+ "user.settings.notifications.emailBatchingInfo": "Notifications are combined into a single email and sent at the maximum frequency selected here.",
+ "user.settings.notifications.emailInfo": "Email notifications that are sent for mentions and direct messages when you are offline or away from {siteName} for more than 5 minutes.",
+ "user.settings.notifications.emailNotifications": "Send email notifications",
+ "user.settings.notifications.everyXMinutes": "Every {count, plural, one {minute} other {{count, number} minutes}}",
+ "user.settings.notifications.everyHour": "Every hour",
"user.settings.notifications.header": "Notifications",
+ "user.settings.notifications.immediately": "Immediately",
"user.settings.notifications.info": "Desktop notifications are available on Firefox, Safari, Chrome, Internet Explorer, and Edge.",
"user.settings.notifications.never": "Never",
"user.settings.notifications.noWords": "No words configured",
"user.settings.notifications.off": "Off",
"user.settings.notifications.on": "On",
"user.settings.notifications.onlyMentions": "Only for mentions and direct messages",
- "user.settings.notifications.push": "Mobile push notifications",
+ "user.settings.notifications.push": "Send mobile push notifications",
"user.settings.notifications.sensitiveName": "Your case sensitive first name \"{first_name}\"",
"user.settings.notifications.sensitiveUsername": "Your non-case sensitive username \"{username}\"",
"user.settings.notifications.sensitiveWords": "Other non-case sensitive words, separated by commas:",
diff --git a/webapp/sass/routes/_admin-console.scss b/webapp/sass/routes/_admin-console.scss
index fdf4d270a..09040cc78 100644
--- a/webapp/sass/routes/_admin-console.scss
+++ b/webapp/sass/routes/_admin-console.scss
@@ -110,6 +110,7 @@
.help-text {
color: alpha-color($black, .5);
+ display: block;
margin: 10px 0 0;
&.no-margin {
diff --git a/webapp/utils/constants.jsx b/webapp/utils/constants.jsx
index 05068f4ea..8a31f6bfa 100644
--- a/webapp/utils/constants.jsx
+++ b/webapp/utils/constants.jsx
@@ -52,7 +52,9 @@ export const Preferences = {
COLLAPSE_DISPLAY_DEFAULT: 'false',
USE_MILITARY_TIME: 'use_military_time',
CATEGORY_THEME: 'theme',
- CATEGORY_FLAGGED_POST: 'flagged_post'
+ CATEGORY_FLAGGED_POST: 'flagged_post',
+ CATEGORY_NOTIFICATIONS: 'notifications',
+ EMAIL_INTERVAL: 'email_interval'
};
export const ActionTypes = keyMirror({