FEATURE: Consolidate likes notifications. (#6879)

FEATURE: Consolidate likes notifications. (#6879)

diff --git a/app/assets/javascripts/discourse-common/lib/icon-library.js.es6 b/app/assets/javascripts/discourse-common/lib/icon-library.js.es6
index 1fe28e8..5a1eb19 100644
--- a/app/assets/javascripts/discourse-common/lib/icon-library.js.es6
+++ b/app/assets/javascripts/discourse-common/lib/icon-library.js.es6
@@ -24,6 +24,7 @@ const REPLACEMENTS = {
   "notification.liked": "heart",
   "notification.liked_2": "heart",
   "notification.liked_many": "heart",
+  "notification.liked_consolidated": "heart",
   "notification.private_message": "far-envelope",
   "notification.invited_to_private_message": "far-envelope",
   "notification.invited_to_topic": "hand-point-right",
diff --git a/app/assets/javascripts/discourse/models/user-stream.js.es6 b/app/assets/javascripts/discourse/models/user-stream.js.es6
index 3f1650c..891b7f9 100644
--- a/app/assets/javascripts/discourse/models/user-stream.js.es6
+++ b/app/assets/javascripts/discourse/models/user-stream.js.es6
@@ -30,14 +30,16 @@ export default RestModel.extend({
     "/user_actions.json?offset=%@&username=%@"
   ),
 
-  filterBy(filter, noContentHelpKey) {
+  filterBy(filter, noContentHelpKey, actingUsername) {
     this.setProperties({
       filter,
       itemsLoaded: 0,
       content: [],
-      noContentHelpKey: noContentHelpKey,
-      lastLoadedUrl: null
+      noContentHelpKey,
+      lastLoadedUrl: null,
+      actingUsername
     });
+
     return this.findItems();
   },
 
@@ -77,6 +79,10 @@ export default RestModel.extend({
       findUrl += "&no_results_help_key=" + this.get("noContentHelpKey");
     }
 
+    if (this.get("actingUsername")) {
+      findUrl += `&acting_username=${this.get("actingUsername")}`;
+    }
+
     // Don't load the same stream twice. We're probably at the end.
     const lastLoadedUrl = this.get("lastLoadedUrl");
     if (lastLoadedUrl === findUrl) {
diff --git a/app/assets/javascripts/discourse/routes/user-activity-stream.js.es6 b/app/assets/javascripts/discourse/routes/user-activity-stream.js.es6
index bd927e3..4ba462e 100644
--- a/app/assets/javascripts/discourse/routes/user-activity-stream.js.es6
+++ b/app/assets/javascripts/discourse/routes/user-activity-stream.js.es6
@@ -1,17 +1,20 @@
 import ViewingActionType from "discourse/mixins/viewing-action-type";
 
 export default Discourse.Route.extend(ViewingActionType, {
+  queryParams: {
+    acting_username: { refreshModel: true }
+  },
+
   model() {
     return this.modelFor("user").get("stream");
   },
 
-  afterModel() {
-    return this.modelFor("user")
-      .get("stream")
-      .filterBy(
-        this.get("userActionType"),
-        this.get("noContentHelpKey") || "user_activity.no_default"
-      );
+  afterModel(model, transition) {
+    return model.filterBy(
+      this.get("userActionType"),
+      this.get("noContentHelpKey") || "user_activity.no_default",
+      transition.queryParams.acting_username
+    );
   },
 
   renderTemplate() {
diff --git a/app/assets/javascripts/discourse/widgets/notification-item.js.es6 b/app/assets/javascripts/discourse/widgets/notification-item.js.es6
index 3de11d5..bd43d2d 100644
--- a/app/assets/javascripts/discourse/widgets/notification-item.js.es6
+++ b/app/assets/javascripts/discourse/widgets/notification-item.js.es6
@@ -16,6 +16,7 @@ import { iconNode } from "discourse-common/lib/icon-library";
 const LIKED_TYPE = 5;
 const INVITED_TYPE = 8;
 const GROUP_SUMMARY_TYPE = 16;
+export const LIKED_CONSOLIDATED_TYPE = 19;
 
 createWidget("notification-item", {
   tagName: "li",
@@ -61,6 +62,14 @@ createWidget("notification-item", {
       return userPath(data.display_username);
     }
 
+    if (attrs.notification_type === LIKED_CONSOLIDATED_TYPE) {
+      return userPath(
+        `${
+          this.currentUser.username
+        }/notifications/likes-received?acting_username=${data.display_username}`
+      );
+    }
+
     if (data.group_id) {
       return userPath(data.username + "/messages/group/" + data.group_name);
     }
@@ -77,7 +86,16 @@ createWidget("notification-item", {
       return this.attrs.fancy_title;
     }
 
-    const title = data.topic_title;
+    let title;
+
+    if (this.attrs.notification_type === LIKED_CONSOLIDATED_TYPE) {
+      title = I18n.t("notifications.liked_consolidated_description", {
+        count: parseInt(data.count)
+      });
+    } else {
+      title = data.topic_title;
+    }
+
     return Ember.isEmpty(title) ? "" : escapeExpression(title);
   },
 
@@ -95,9 +113,11 @@ createWidget("notification-item", {
 
     const username = formatUsername(data.display_username);
     const description = this.description();
+
     if (notificationType === LIKED_TYPE && data.count > 1) {
       const count = data.count - 2;
       const username2 = formatUsername(data.username2);
+
       if (count === 0) {
         return I18n.t("notifications.liked_2", {
           description,
diff --git a/app/controllers/user_actions_controller.rb b/app/controllers/user_actions_controller.rb
index d72b28d..2d26bbb 100644
--- a/app/controllers/user_actions_controller.rb
+++ b/app/controllers/user_actions_controller.rb
@@ -2,7 +2,7 @@ class UserActionsController < ApplicationController
 
   def index
     params.require(:username)
-    params.permit(:filter, :offset)
+    params.permit(:filter, :offset, :acting_username)
 
     per_chunk = 30
 
@@ -11,13 +11,16 @@ class UserActionsController < ApplicationController
 
     action_types = (params[:filter] || "").split(",").map(&:to_i)
 
-    opts = { user_id: user.id,
-             user: user,
-             offset: params[:offset].to_i,
-             limit: per_chunk,
-             action_types: action_types,
-             guardian: guardian,
-             ignore_private_messages: params[:filter] ? false : true }
+    opts = {
+      user_id: user.id,
+      user: user,
+      offset: params[:offset].to_i,
+      limit: per_chunk,
+      action_types: action_types,
+      guardian: guardian,
+      ignore_private_messages: params[:filter] ? false : true,
+      acting_username: params[:acting_username]
+    }
 
     # Pending is restricted
     stream = if opts[:action_types].include?(UserAction::PENDING)
diff --git a/app/models/notification.rb b/app/models/notification.rb
index 27ecc9a..4066370 100644
--- a/app/models/notification.rb
+++ b/app/models/notification.rb
@@ -13,6 +13,12 @@ class Notification < ActiveRecord::Base
   scope :visible , lambda { joins('LEFT JOIN topics ON notifications.topic_id = topics.id')
     .where('topics.id IS NULL OR topics.deleted_at IS NULL') }
 
+  scope :get_liked_by, ->(user) {
+    where("data::json ->> 'original_username' = ?", user.username_lower)
+      .where(notification_type: Notification.types[:liked])
+      .order(created_at: :desc)
+  }
+
   attr_accessor :skip_send_email
 
   after_commit :send_email, on: :create
@@ -53,7 +59,8 @@ class Notification < ActiveRecord::Base
                         group_mentioned: 15,
                         group_message_summary: 16,
                         watching_first_post: 17,
-                        topic_reminder: 18
+                        topic_reminder: 18,
+                        liked_consolidated: 19,
                        )
   end
 
diff --git a/app/models/user_action.rb b/app/models/user_action.rb
index 4d9e86d..8a7d8d0 100644
--- a/app/models/user_action.rb
+++ b/app/models/user_action.rb
@@ -201,6 +201,7 @@ class UserAction < ActiveRecord::Base
     ignore_private_messages = opts[:ignore_private_messages]
     offset = opts[:offset] || 0
     limit = opts[:limit] || 60
+    acting_username = opts[:acting_username]
 
     # Acting user columns. Can be extended by plugins to include custom avatar
     # columns
@@ -258,6 +259,12 @@ class UserAction < ActiveRecord::Base
       builder.where("a.user_id = :user_id", user_id: user_id.to_i)
       builder.where("a.action_type in (:action_types)", action_types: action_types) if action_types && action_types.length > 0
 
+      if acting_username

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

GitHub sha: ebe65577

1 Like

REFACTOR: `filterBy` in `UserStream`.