DEV: Update discourse-presence plugin to use new PresenceChannel system (#14519)

DEV: Update discourse-presence plugin to use new PresenceChannel system (#14519)

This removes all custom controllers and redis/messagebus logic from discourse-presence, and replaces it with core’s new PresenceChannel system.

All functionality should be retained. This implementation should scale much better to large numbers of users, reduce the number of HTTP requests made by clients, and reduce the volume of messages on the MessageBus.

For more information on PresenceChannel, see 31db8352

diff --git a/app/assets/javascripts/discourse/app/services/presence.js b/app/assets/javascripts/discourse/app/services/presence.js
index ac1ba6d..346dcef 100644
--- a/app/assets/javascripts/discourse/app/services/presence.js
+++ b/app/assets/javascripts/discourse/app/services/presence.js
@@ -2,7 +2,15 @@ import Service from "@ember/service";
 import EmberObject, { computed, defineProperty } from "@ember/object";
 import { readOnly } from "@ember/object/computed";
 import { ajax } from "discourse/lib/ajax";
-import { cancel, debounce, later, next, once, throttle } from "@ember/runloop";
+import {
+  cancel,
+  debounce,
+  later,
+  next,
+  once,
+  run,
+  throttle,
+} from "@ember/runloop";
 import Session from "discourse/models/session";
 import { Promise } from "rsvp";
 import { isLegacyEmber, isTesting } from "discourse-common/config/environment";
@@ -137,9 +145,8 @@ class PresenceChannelState extends EmberObject {
 
     this.lastSeenId = initialData.last_message_id;
 
-    let callback = (data, global_id, message_id) => {
-      this._processMessage(data, global_id, message_id);
-    };
+    let callback = (data, global_id, message_id) =>
+      run(() => this._processMessage(data, global_id, message_id));
     this.presenceService.messageBus.subscribe(
       `/presence${this.name}`,
       callback,
diff --git a/lib/presence_channel.rb b/lib/presence_channel.rb
index 531e861..a848534 100644
--- a/lib/presence_channel.rb
+++ b/lib/presence_channel.rb
@@ -61,7 +61,7 @@ class PresenceChannel
   end
 
   DEFAULT_TIMEOUT ||= 60
-  CONFIG_CACHE_SECONDS ||= 120
+  CONFIG_CACHE_SECONDS ||= 10
   GC_SECONDS ||= 24.hours.to_i
   MUTEX_TIMEOUT_SECONDS ||= 10
   MUTEX_LOCKED_ERROR ||= "PresenceChannel mutex is locked"
@@ -281,7 +281,7 @@ class PresenceChannel
   # should not exist, the block should return `nil`. If the channel should exist,
   # the block should return a PresenceChannel::Config object.
   #
-  # Return values may be cached for up to 2 minutes.
+  # Return values may be cached for up to 10 seconds.
   #
   # Plugins should use the {Plugin::Instance.register_presence_channel_prefix} API instead
   def self.register_prefix(prefix, &block)
diff --git a/plugins/discourse-presence/README.md b/plugins/discourse-presence/README.md
index 4e41c6c..64be78e 100644
--- a/plugins/discourse-presence/README.md
+++ b/plugins/discourse-presence/README.md
@@ -1,14 +1,2 @@
 # Discourse Presence plugin
 This plugin shows which users are currently writing a reply at the same time as you.
-
-## Installation
-
-Follow the directions at [Install a Plugin](https://meta.discourse.org/t/install-a-plugin/19157) using https://github.com/discourse/discourse-presence.git as the repository URL.
-
-## Authors
-
-André Pereira, David Taylor
-
-## License
-
-GNU GPL v2
diff --git a/plugins/discourse-presence/assets/javascripts/discourse/components/composer-presence-display.js.es6 b/plugins/discourse-presence/assets/javascripts/discourse/components/composer-presence-display.js.es6
index 19a82d0..6e33438 100644
--- a/plugins/discourse-presence/assets/javascripts/discourse/components/composer-presence-display.js.es6
+++ b/plugins/discourse-presence/assets/javascripts/discourse/components/composer-presence-display.js.es6
@@ -1,117 +1,108 @@
-import {
-  CLOSED,
-  COMPOSER_TYPE,
-  EDITING,
-  KEEP_ALIVE_DURATION_SECONDS,
-  REPLYING,
-} from "discourse/plugins/discourse-presence/discourse/lib/presence";
-import { cancel, throttle } from "@ember/runloop";
 import discourseComputed, {
   observes,
   on,
 } from "discourse-common/utils/decorators";
-import { gt, readOnly } from "@ember/object/computed";
+import { equal, gt, readOnly, union } from "@ember/object/computed";
 import Component from "@ember/component";
 import { inject as service } from "@ember/service";
 
 export default Component.extend({
-  // Passed in variables
-  presenceManager: service(),
+  presence: service(),
+  composerPresenceManager: service(),
 
-  @discourseComputed("model.topic.id")
-  users(topicId) {
-    return this.presenceManager.users(topicId);
-  },
-
-  @discourseComputed("model.topic.id")
-  editingUsers(topicId) {
-    return this.presenceManager.editingUsers(topicId);
+  @discourseComputed(
+    "model.replyingToTopic",
+    "model.editingPost",
+    "model.whisper",
+    "model.composerOpened",
+    "isDestroying"
+  )
+  state(replyingToTopic, editingPost, whisper, composerOpen, isDestroying) {
+    if (!composerOpen || isDestroying) {
+      return;
+    } else if (editingPost) {
+      return "edit";
+    } else if (whisper) {
+      return "whisper";
+    } else if (replyingToTopic) {
+      return "reply";
+    }
   },
 
-  isReply: readOnly("model.replyingToTopic"),
-  isEdit: readOnly("model.editingPost"),
+  isReply: equal("state", "reply"),
+  isEdit: equal("state", "edit"),
+  isWhisper: equal("state", "whisper"),
 
-  @on("didInsertElement")
-  subscribe() {
-    this.presenceManager.subscribe(this.get("model.topic.id"), COMPOSER_TYPE);
+  @discourseComputed("model.topic.id", "isReply", "isWhisper")
+  replyChannelName(topicId, isReply, isWhisper) {
+    if (topicId && (isReply || isWhisper)) {
+      return `/discourse-presence/reply/${topicId}`;
+    }
   },
 
-  @discourseComputed(
-    "model.post.id",
-    "editingUsers.@each.last_seen",
-    "users.@each.last_seen",
-    "isReply",
-    "isEdit"
-  )
-  presenceUsers(postId, editingUsers, users, isReply, isEdit) {
-    if (isEdit) {
-      return editingUsers.filterBy("post_id", postId);
-    } else if (isReply) {
-      return users;
+  @discourseComputed("model.topic.id", "isReply", "isWhisper")
+  whisperChannelName(topicId, isReply, isWhisper) {
+    if (topicId && this.currentUser.staff && (isReply || isWhisper)) {
+      return `/discourse-presence/whisper/${topicId}`;
     }
-    return [];
   },
 
-  shouldDisplay: gt("presenceUsers.length", 0),
-
-  @observes("model.reply", "model.title")
-  typing() {
-    throttle(this, this._typing, KEEP_ALIVE_DURATION_SECONDS * 1000);
+  @discourseComputed("isEdit", "model.post.id")
+  editChannelName(isEdit, postId) {
+    if (isEdit) {
+      return `/discourse-presence/edit/${postId}`;
+    }
   },
 
-  _typing() {
-    if ((!this.isReply && !this.isEdit) || !this.get("model.composerOpened")) {
-      return;
+  _setupChannel(channelKey, name) {
+    if (this[channelKey]?.name !== name) {
+      this[channelKey]?.unsubscribe();
+      if (name) {
+        this.set(channelKey, this.presence.getChannel(name));
+        this[channelKey].subscribe();
+      } else if (this[channelKey]) {
+        this.set(channelKey, null);
+      }
     }
+  },
 
-    let data = {
-      topicId: this.get("model.topic.id"),
-      state: this.isEdit ? EDITING : REPLYING,
-      whisper: this.get("model.whisper"),
-      postId: this.get("model.post.id"),
-      presenceStaffOnly: this.get("model._presenceStaffOnly"),
-    };
+  @observes("replyChannelName", "whisperChannelName", "editChannelName")
+  _setupChannels() {
+    this._setupChannel("replyChannel", this.replyChannelName);
+    this._setupChannel("whisperChannel", this.whisperChannelName);
+    this._setupChannel("editChannel", this.editChannelName);
+  },
 
-    this._prevPublishData = data;
+  replyingUsers: union("replyChannel.users", "whisperChannel.users"),
+  editingUsers: readOnly("editChannel.users"),
 
-    this._throttle = this.presenceManager.publish(
-      data.topicId,
-      data.state,
-      data.whisper,
-      data.postId,
-      data.presenceStaffOnly
-    );
+  @discourseComputed("isReply", "replyingUsers.[]", "editingUsers.[]")
+  presenceUsers(isReply, replyingUsers, editingUsers) {
+    const users = isReply ? replyingUsers : editingUsers;
+    return users
+      ?.filter((u) => u.id !== this.currentUser.id)
+      ?.slice(0, this.siteSettings.presence_max_users_shown);
   },
 
-  @observes("model.whisper")
-  cancelThrottle() {
-    this._cancelThrottle();

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

GitHub sha: b57b079ff278d5f9b6ea5134acc2b4b0db9fba7d

This commit appears in #14519 which was approved by tgxworld. It was merged by davidtaylorhq.