FEATURE: add more granular user option levels for email notifications (#7143)

approved

#1

FEATURE: add more granular user option levels for email notifications (#7143)

Migrates email user options to a new data structure, where email_always, email_direct and email_private_messages are replace by

  • email_messages_level, with options: always, only_when_away and never (defaults to always)
  • email_level, with options: always, only_when_away and never (defaults to only_when_away)
diff --git a/app/assets/javascripts/discourse/controllers/preferences/emails.js.es6 b/app/assets/javascripts/discourse/controllers/preferences/emails.js.es6
index a8ee915..a54e905 100644
--- a/app/assets/javascripts/discourse/controllers/preferences/emails.js.es6
+++ b/app/assets/javascripts/discourse/controllers/preferences/emails.js.es6
@@ -2,15 +2,29 @@ import PreferencesTabController from "discourse/mixins/preferences-tab-controlle
 import { default as computed } from "ember-addons/ember-computed-decorators";
 import { popupAjaxError } from "discourse/lib/ajax-error";
 
+const EMAIL_LEVELS = {
+  ALWAYS: 0,
+  ONLY_WHEN_AWAY: 1,
+  NEVER: 2
+};
+
 export default Ember.Controller.extend(PreferencesTabController, {
+  emailMessagesLevelAway: Ember.computed.equal(
+    "model.user_option.email_messages_level",
+    EMAIL_LEVELS.ONLY_WHEN_AWAY
+  ),
+  emailLevelAway: Ember.computed.equal(
+    "model.user_option.email_level",
+    EMAIL_LEVELS.ONLY_WHEN_AWAY
+  ),
+
   saveAttrNames: [
-    "email_always",
+    "email_level",
+    "email_messages_level",
     "mailing_list_mode",
     "mailing_list_mode_frequency",
     "email_digests",
-    "email_direct",
     "email_in_reply_to",
-    "email_private_messages",
     "email_previous_replies",
     "digest_after_minutes",
     "include_tl0_in_digests"
@@ -42,6 +56,15 @@ export default Ember.Controller.extend(PreferencesTabController, {
     { name: I18n.t("user.email_previous_replies.never"), value: 2 }
   ],
 
+  emailLevelOptions: [
+    { name: I18n.t("user.email_level.always"), value: EMAIL_LEVELS.ALWAYS },
+    {
+      name: I18n.t("user.email_level.only_when_away"),
+      value: EMAIL_LEVELS.ONLY_WHEN_AWAY
+    },
+    { name: I18n.t("user.email_level.never"), value: EMAIL_LEVELS.NEVER }
+  ],
+
   digestFrequencies: [
     { name: I18n.t("user.email_digests.every_30_minutes"), value: 30 },
     { name: I18n.t("user.email_digests.every_hour"), value: 60 },
@@ -51,6 +74,17 @@ export default Ember.Controller.extend(PreferencesTabController, {
     { name: I18n.t("user.email_digests.every_two_weeks"), value: 20160 }
   ],
 
+  @computed()
+  emailFrequencyInstructions() {
+    if (this.siteSettings.email_time_window_mins) {
+      return I18n.t("user.email.frequency", {
+        count: this.siteSettings.email_time_window_mins
+      });
+    } else {
+      return I18n.t("user.email.frequency_immediately");
+    }
+  },
+
   actions: {
     save() {
       this.set("saved", false);
diff --git a/app/assets/javascripts/discourse/models/user.js.es6 b/app/assets/javascripts/discourse/models/user.js.es6
index 2e5fa5d..8335d62 100644
--- a/app/assets/javascripts/discourse/models/user.js.es6
+++ b/app/assets/javascripts/discourse/models/user.js.es6
@@ -264,14 +264,13 @@ const User = RestModel.extend({
     );
 
     let userOptionFields = [
-      "email_always",
       "mailing_list_mode",
       "mailing_list_mode_frequency",
       "external_links_in_new_tab",
       "email_digests",
-      "email_direct",
       "email_in_reply_to",
-      "email_private_messages",
+      "email_messages_level",
+      "email_level",
       "email_previous_replies",
       "dynamic_favicon",
       "enable_quoting",
diff --git a/app/assets/javascripts/discourse/templates/preferences/emails.hbs b/app/assets/javascripts/discourse/templates/preferences/emails.hbs
index b705f0a..780a64d 100644
--- a/app/assets/javascripts/discourse/templates/preferences/emails.hbs
+++ b/app/assets/javascripts/discourse/templates/preferences/emails.hbs
@@ -7,23 +7,35 @@
 {{/unless}}
 <div class="control-group pref-email-settings">
   <label class="control-label">{{i18n 'user.email_settings'}}</label>
+
+  <div class='controls controls-dropdown'>
+    <label for="user-email-messages-level">{{i18n 'user.email_messages_level'}}</label>
+    {{combo-box valueAttribute="value"
+                content=emailLevelOptions
+                value=model.user_option.email_messages_level
+                id="user-email-messages-level"}}
+    {{#if emailMessagesLevelAway}}
+      <div class='instructions'>{{emailFrequencyInstructions}}</div>
+    {{/if}}
+  </div>
+
+  <div class='controls controls-dropdown'>
+    <label for="user-email-level">{{i18n 'user.email_level.title'}}</label>
+    {{combo-box valueAttribute="value"
+                content=emailLevelOptions
+                value=model.user_option.email_level
+                id="user-email-level"}}
+    {{#if emailLevelAway}}
+      <div class='instructions'>{{emailFrequencyInstructions}}</div>
+    {{/if}}
+  </div>
+
   <div class='controls controls-dropdown'>
     <label>{{i18n 'user.email_previous_replies.title'}}</label>
     {{combo-box valueAttribute="value" content=previousRepliesOptions value=model.user_option.email_previous_replies}}
   </div>
   {{preference-checkbox labelKey="user.email_in_reply_to" checked=model.user_option.email_in_reply_to}}
-  {{preference-checkbox labelKey="user.email_private_messages" checked=model.user_option.email_private_messages}}
-  {{preference-checkbox labelKey="user.email_direct" checked=model.user_option.email_direct}}
-  {{preference-checkbox labelKey="user.email_always" checked=model.user_option.email_always}}
-  {{#unless model.user_option.email_always}}
-  <div class='instructions'>
-    {{#if siteSettings.email_time_window_mins}}
-      {{i18n 'user.email.frequency' count=siteSettings.email_time_window_mins}}
-    {{else}}
-      {{i18n 'user.email.frequency_immediately'}}
-    {{/if}}
-  </div>
-  {{/unless}}
+
   {{plugin-outlet name="user-preferences-emails-pref-email-settings" args=(hash model=model save=(action "save"))}}
 </div>
 
diff --git a/app/controllers/email_controller.rb b/app/controllers/email_controller.rb
index 6448762..b4c8f0b 100644
--- a/app/controllers/email_controller.rb
+++ b/app/controllers/email_controller.rb
@@ -91,10 +91,9 @@ class EmailController < ApplicationController
     end
 
     if params["unsubscribe_all"]
-      user.user_option.update_columns(email_always: false,
-                                      email_digests: false,
-                                      email_direct: false,
-                                      email_private_messages: false)
+      user.user_option.update_columns(email_digests: false,
+                                      email_level: UserOption.email_level_types[:never],
+                                      email_messages_level: UserOption.email_level_types[:never])
       updated = true
     end
 
diff --git a/app/jobs/regular/user_email.rb b/app/jobs/regular/user_email.rb
index 322d9c0..7a3d089 100644
--- a/app/jobs/regular/user_email.rb
+++ b/app/jobs/regular/user_email.rb
@@ -102,7 +102,7 @@ module Jobs
       return if user.staged && type.to_s == "digest"
 
       seen_recently = (user.last_seen_at.present? && user.last_seen_at > SiteSetting.email_time_window_mins.minutes.ago)
-      seen_recently = false if user.user_option.email_always || user.staged
+      seen_recently = false if always_email_regular?(user, type) || always_email_private_message?(user, type) || user.staged
 
       email_args = {}
 
@@ -133,7 +133,7 @@ module Jobs
           return [nil, nil]
         end
 
-        unless user.user_option.email_always?
+        unless always_email_regular?(user, type) || always_email_private_message?(user, type)
           if (notification && notification.read?) || (post && post.seen?(user))
             return skip_message(SkippedEmailLog.reason_types[:user_email_notification_already_read])
           end
@@ -217,7 +217,7 @@ module Jobs
           return SkippedEmailLog.reason_types[:user_email_user_suspended]
         end
 
-        already_read = !user.user_option.email_always? && PostTiming.exists?(topic_id: post.topic_id, post_number: post.post_number, user_id: user.id)
+        already_read = user.user_option.email_level != UserOption.email_level_types[:always] && PostTiming.exists?(topic_id: post.topic_id, post_number: post.post_number, user_id: user.id)
         if already_read

[... diff too long, it was truncated ...]

GitHub sha: 9334d2f4


Approved #2