diff --git a/app/assets/javascripts/discourse/app/components/suggested-topics.js b/app/assets/javascripts/discourse/app/components/suggested-topics.js index f601c1d84d2..02616fdaa73 100644 --- a/app/assets/javascripts/discourse/app/components/suggested-topics.js +++ b/app/assets/javascripts/discourse/app/components/suggested-topics.js @@ -5,6 +5,7 @@ import Site from "discourse/models/site"; import { categoryBadgeHTML } from "discourse/helpers/category-link"; import discourseComputed from "discourse-common/utils/decorators"; import getURL from "discourse-common/lib/get-url"; +import { iconHTML } from "discourse-common/lib/icon-library"; export default Component.extend({ tagName: "", @@ -18,13 +19,68 @@ export default Component.extend({ } }), - @discourseComputed("topic", "topicTrackingState.messageCount") + @discourseComputed( + "topic", + "pmTopicTrackingState.isTracking", + "pmTopicTrackingState.statesModificationCounter", + "topicTrackingState.messageCount" + ) browseMoreMessage(topic) { - // TODO decide what to show for pms - if (topic.get("isPrivateMessage")) { - return; - } + return topic.isPrivateMessage + ? this._privateMessageBrowseMoreMessage(topic) + : this._topicBrowseMoreMessage(topic); + }, + _privateMessageBrowseMoreMessage(topic) { + const username = this.currentUser.username; + const suggestedGroupName = topic.suggested_group_name; + const inboxFilter = suggestedGroupName ? "group" : "user"; + + const unreadCount = this.pmTopicTrackingState.lookupCount("unread", { + inboxFilter: inboxFilter, + groupName: suggestedGroupName, + }); + + const newCount = this.pmTopicTrackingState.lookupCount("new", { + inboxFilter: inboxFilter, + groupName: suggestedGroupName, + }); + + if (unreadCount + newCount > 0) { + const hasBoth = unreadCount > 0 && newCount > 0; + + if (suggestedGroupName) { + return I18n.messageFormat("user.messages.read_more_group_pm_MF", { + BOTH: hasBoth, + UNREAD: unreadCount, + NEW: newCount, + username: username, + groupName: suggestedGroupName, + groupLink: this._groupLink(username, suggestedGroupName), + basePath: getURL(""), + }); + } else { + return I18n.messageFormat("user.messages.read_more_personal_pm_MF", { + BOTH: hasBoth, + UNREAD: unreadCount, + NEW: newCount, + username, + basePath: getURL(""), + }); + } + } else if (suggestedGroupName) { + return I18n.t("user.messages.read_more_in_group", { + groupLink: this._groupLink(username, suggestedGroupName), + }); + } else { + return I18n.t("user.messages.read_more", { + basePath: getURL(""), + username, + }); + } + }, + + _topicBrowseMoreMessage(topic) { const opts = { latestLink: `${I18n.t( "topic.view_latest_topics" @@ -50,8 +106,13 @@ export default Component.extend({ ""; } - const unreadTopics = this.topicTrackingState.countUnread(); - const newTopics = this.currentUser ? this.topicTrackingState.countNew() : 0; + let unreadTopics = 0; + let newTopics = 0; + + if (this.currentUser) { + unreadTopics = this.topicTrackingState.countUnread(); + newTopics = this.topicTrackingState.countNew(); + } if (newTopics + unreadTopics > 0) { const hasBoth = unreadTopics > 0 && newTopics > 0; @@ -71,4 +132,10 @@ export default Component.extend({ return I18n.t("topic.read_more", opts); } }, + + _groupLink(username, groupName) { + return `${iconHTML("users")} ${groupName}`; + }, }); diff --git a/app/assets/javascripts/discourse/app/controllers/user-topics-list.js b/app/assets/javascripts/discourse/app/controllers/user-topics-list.js index 6800f060d3c..7e95ed10997 100644 --- a/app/assets/javascripts/discourse/app/controllers/user-topics-list.js +++ b/app/assets/javascripts/discourse/app/controllers/user-topics-list.js @@ -18,7 +18,6 @@ export default Controller.extend(BulkTopicSelection, { showPosters: false, channel: null, tagsForUser: null, - pmTopicTrackingState: null, incomingCount: reads("pmTopicTrackingState.newIncoming.length"), @discourseComputed("emptyState", "model.topics.length", "incomingCount") @@ -46,15 +45,11 @@ export default Controller.extend(BulkTopicSelection, { }, subscribe() { - this.pmTopicTrackingState?.trackIncoming( - this.inbox, - this.filter, - this.group - ); + this.pmTopicTrackingState.trackIncoming(this.inbox, this.filter); }, unsubscribe() { - this.pmTopicTrackingState?.resetTracking(); + this.pmTopicTrackingState.resetIncomingTracking(); }, @action @@ -85,7 +80,7 @@ export default Controller.extend(BulkTopicSelection, { @action showInserted() { this.model.loadBefore(this.pmTopicTrackingState.newIncoming); - this.pmTopicTrackingState.resetTracking(); + this.pmTopicTrackingState.resetIncomingTracking(); return false; }, }); diff --git a/app/assets/javascripts/discourse/app/models/post-stream.js b/app/assets/javascripts/discourse/app/models/post-stream.js index a28a4833212..62b96e788f9 100644 --- a/app/assets/javascripts/discourse/app/models/post-stream.js +++ b/app/assets/javascripts/discourse/app/models/post-stream.js @@ -1076,9 +1076,7 @@ export default RestModel.extend({ const store = this.store; return ajax(url, { data }).then((result) => { - if (result.suggested_topics) { - this.set("topic.suggested_topics", result.suggested_topics); - } + this._setSuggestedTopics(result); const posts = get(result, "post_stream.posts"); @@ -1124,9 +1122,7 @@ export default RestModel.extend({ data, headers, }).then((result) => { - if (result.suggested_topics) { - this.set("topic.suggested_topics", result.suggested_topics); - } + this._setSuggestedTopics(result); const posts = get(result, "post_stream.posts"); @@ -1245,4 +1241,17 @@ export default RestModel.extend({ } } }, + + _setSuggestedTopics(result) { + if (!result.suggested_topics) { + return; + } + + this.topic.setProperties({ + suggested_topics: result.suggested_topics, + suggested_group_name: result.suggested_group_name, + }); + + this.pmTopicTrackingState.startTracking(); + }, }); diff --git a/app/assets/javascripts/discourse/app/models/private-message-topic-tracking-state.js b/app/assets/javascripts/discourse/app/models/private-message-topic-tracking-state.js index 4fcb8f96925..de465563897 100644 --- a/app/assets/javascripts/discourse/app/models/private-message-topic-tracking-state.js +++ b/app/assets/javascripts/discourse/app/models/private-message-topic-tracking-state.js @@ -1,4 +1,7 @@ import EmberObject from "@ember/object"; +import { ajax } from "discourse/lib/ajax"; +import { on } from "discourse-common/utils/decorators"; +import { popupAjaxError } from "discourse/lib/ajax-error"; import { ARCHIVE_FILTER, INBOX_FILTER, @@ -15,20 +18,33 @@ const PrivateMessageTopicTrackingState = EmberObject.extend({ filter: null, activeGroup: null, - startTracking(data) { + @on("init") + _setup() { this.states = new Map(); + this.statesModificationCounter = 0; + this.isTracking = false; this.newIncoming = []; - this._loadStates(data); - this.establishChannels(); }, - establishChannels() { + startTracking() { + if (this.isTracking) { + return; + } + + this._establishChannels(); + + this._loadInitialState().finally(() => { + this.set("isTracking", true); + }); + }, + + _establishChannels() { this.messageBus.subscribe( - this._userChannel(this.user.id), + this._userChannel(), this._processMessage.bind(this) ); - this.user.groupsWithMessages?.forEach((group) => { + this.currentUser.groupsWithMessages?.forEach((group) => { this.messageBus.subscribe( this._groupChannel(group.id), this._processMessage.bind(this) @@ -36,26 +52,21 @@ const PrivateMessageTopicTrackingState = EmberObject.extend({ }); }, - stopTracking() { - this.messageBus.unsubscribe(this._userChannel(this.user.id)); - - this.user.groupsWithMessages?.forEach((group) => { - this.messageBus.unsubscribe(this._groupChannel(group.id)); - }); - }, - - lookupCount(type) { + lookupCount(type, opts = {}) { const typeFilterFn = type === "new" ? this._isNew : this._isUnread; + const inbox = opts.inboxFilter || this.inbox; let filterFn; - if (this.inbox === "user") { + if (inbox === "user") { filterFn = this._isPersonal.bind(this); - } else if (this.inbox === "group") { + } else if (inbox === "group") { filterFn = this._isGroup.bind(this); } return Array.from(this.states.values()).filter((topic) => { - return typeFilterFn(topic) && (!filterFn || filterFn(topic)); + return ( + typeFilterFn(topic) && (!filterFn || filterFn(topic, opts.groupName)) + ); }).length; }, @@ -63,14 +74,14 @@ const PrivateMessageTopicTrackingState = EmberObject.extend({ this.setProperties({ inbox, filter, activeGroup: group }); }, - resetTracking() { + resetIncomingTracking() { if (this.inbox) { this.set("newIncoming", []); } }, - _userChannel(userId) { - return `${this.CHANNEL_PREFIX}/user/${userId}`; + _userChannel() { + return `${this.CHANNEL_PREFIX}/user/${this.currentUser.id}`; }, _groupChannel(groupId) { @@ -95,9 +106,9 @@ const PrivateMessageTopicTrackingState = EmberObject.extend({ }, _isPersonal(topic) { - const groups = this.user.groups; + const groups = this.currentUser?.groups; - if (groups.length === 0) { + if (!groups || groups.length === 0) { return true; } @@ -106,10 +117,10 @@ const PrivateMessageTopicTrackingState = EmberObject.extend({ }); }, - _isGroup(topic) { - return this.user.groups.some((group) => { + _isGroup(topic, activeGroupName) { + return this.currentUser.groups.some((group) => { return ( - group.name === this.activeGroup.name && + group.name === (activeGroupName || this.activeGroup.name) && topic.group_ids?.includes(group.id) ); }); @@ -182,14 +193,24 @@ const PrivateMessageTopicTrackingState = EmberObject.extend({ } }, - _loadStates(data) { - (data || []).forEach((topic) => { - this._modifyState(topic.topic_id, topic); - }); + _loadInitialState() { + return ajax( + `/u/${this.currentUser.username}/private-message-topic-tracking-state` + ) + .then((pmTopicTrackingStateData) => { + pmTopicTrackingStateData.forEach((topic) => { + this._modifyState(topic.topic_id, topic, { skipIncrement: true }); + }); + }) + .catch(popupAjaxError); }, - _modifyState(topicId, data) { + _modifyState(topicId, data, opts = {}) { this.states.set(topicId, data); + + if (!opts.skipIncrement) { + this.incrementProperty("statesModificationCounter"); + } }, }); diff --git a/app/assets/javascripts/discourse/app/pre-initializers/inject-discourse-objects.js b/app/assets/javascripts/discourse/app/pre-initializers/inject-discourse-objects.js index b3e19dba60d..10fb0706835 100644 --- a/app/assets/javascripts/discourse/app/pre-initializers/inject-discourse-objects.js +++ b/app/assets/javascripts/discourse/app/pre-initializers/inject-discourse-objects.js @@ -1,6 +1,7 @@ import TopicTrackingState, { startTracking, } from "discourse/models/topic-tracking-state"; +import PrivateMessageTopicTrackingState from "discourse/models/private-message-topic-tracking-state"; import DiscourseLocation from "discourse/lib/discourse-location"; import KeyValueStore from "discourse/lib/key-value-store"; import MessageBus from "message-bus-client"; @@ -50,19 +51,30 @@ export default { app.register("current-user:main", currentUser, { instantiate: false }); app.currentUser = currentUser; - ALL_TARGETS.forEach((t) => - app.inject(t, "topicTrackingState", "topic-tracking-state:main") - ); + ALL_TARGETS.forEach((t) => { + app.inject(t, "topicTrackingState", "topic-tracking-state:main"); + app.inject(t, "pmTopicTrackingState", "pm-topic-tracking-state:main"); + }); const topicTrackingState = TopicTrackingState.create({ messageBus: MessageBus, siteSettings, currentUser, }); + app.register("topic-tracking-state:main", topicTrackingState, { instantiate: false, }); + const pmTopicTrackingState = PrivateMessageTopicTrackingState.create({ + messageBus: MessageBus, + currentUser, + }); + + app.register("pm-topic-tracking-state:main", pmTopicTrackingState, { + instantiate: false, + }); + const site = Site.current(); app.register("site:main", site, { instantiate: false }); diff --git a/app/assets/javascripts/discourse/app/routes/build-private-messages-route.js b/app/assets/javascripts/discourse/app/routes/build-private-messages-route.js index 846408a7cc7..65a3cddd621 100644 --- a/app/assets/javascripts/discourse/app/routes/build-private-messages-route.js +++ b/app/assets/javascripts/discourse/app/routes/build-private-messages-route.js @@ -61,8 +61,6 @@ export default (inboxType, path, filter) => { filter: filter, group: null, inbox: inboxType, - pmTopicTrackingState: - userPrivateMessagesController.pmTopicTrackingState, emptyState: this.emptyState(), }); diff --git a/app/assets/javascripts/discourse/app/routes/topic-from-params.js b/app/assets/javascripts/discourse/app/routes/topic-from-params.js index f97c7c882ea..e2918e1f420 100644 --- a/app/assets/javascripts/discourse/app/routes/topic-from-params.js +++ b/app/assets/javascripts/discourse/app/routes/topic-from-params.js @@ -34,6 +34,14 @@ export default DiscourseRoute.extend({ }); }, + afterModel() { + const topic = this.modelFor("topic"); + + if (topic.isPrivateMessage && topic.suggested_topics) { + this.pmTopicTrackingState.startTracking(); + } + }, + deactivate() { this._super(...arguments); this.controllerFor("topic").unsubscribe(); diff --git a/app/assets/javascripts/discourse/app/routes/user-private-messages.js b/app/assets/javascripts/discourse/app/routes/user-private-messages.js index 9c0673865e0..a80933010ca 100644 --- a/app/assets/javascripts/discourse/app/routes/user-private-messages.js +++ b/app/assets/javascripts/discourse/app/routes/user-private-messages.js @@ -1,9 +1,6 @@ import Composer from "discourse/models/composer"; import DiscourseRoute from "discourse/routes/discourse"; import Draft from "discourse/models/draft"; -import { ajax } from "discourse/lib/ajax"; -import { popupAjaxError } from "discourse/lib/ajax-error"; -import PrivateMessageTopicTrackingState from "discourse/models/private-message-topic-tracking-state"; export default DiscourseRoute.extend({ renderTemplate() { @@ -11,36 +8,15 @@ export default DiscourseRoute.extend({ }, model() { - const user = this.modelFor("user"); - return ajax(`/u/${user.username}/private-message-topic-tracking-state`) - .then((pmTopicTrackingStateData) => { - return { - user, - pmTopicTrackingStateData, - }; - }) - .catch((e) => { - popupAjaxError(e); - return { user }; - }); + return this.modelFor("user"); + }, + + afterModel() { + return this.pmTopicTrackingState.startTracking(); }, setupController(controller, model) { - const user = model.user; - - const pmTopicTrackingState = PrivateMessageTopicTrackingState.create({ - messageBus: controller.messageBus, - user, - }); - - pmTopicTrackingState.startTracking(model.pmTopicTrackingStateData); - - controller.setProperties({ - model: user, - pmTopicTrackingState, - }); - - this.set("pmTopicTrackingState", pmTopicTrackingState); + controller.set("model", model); if (this.currentUser) { const composerController = this.controllerFor("composer"); @@ -58,10 +34,6 @@ export default DiscourseRoute.extend({ } }, - deactivate() { - this.pmTopicTrackingState.stopTracking(); - }, - actions: { refresh() { this.refresh(); diff --git a/app/assets/javascripts/discourse/tests/acceptance/user-private-messages-test.js b/app/assets/javascripts/discourse/tests/acceptance/user-private-messages-test.js index d1891a8dead..d785f1b09f0 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/user-private-messages-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/user-private-messages-test.js @@ -10,6 +10,7 @@ import { } from "discourse/tests/helpers/qunit-helpers"; import selectKit from "discourse/tests/helpers/select-kit-helper"; import { PERSONAL_INBOX } from "discourse/controllers/user-private-messages"; +import { fixturesByUrl } from "discourse/tests/helpers/create-pretender"; acceptance( "User Private Messages - user with no group messages", @@ -47,7 +48,11 @@ acceptance( let fetchUserNew; let fetchedGroupNew; - needs.user(); + needs.user({ + id: 5, + username: "charlie", + groups: [{ id: 14, name: "awesome_group", has_messages: true }], + }); needs.site({ can_tag_pms: true, @@ -60,6 +65,12 @@ acceptance( }); needs.pretender((server, helper) => { + server.get("/t/13.json", () => { + const response = { ...fixturesByUrl["/t/12/1.json"] }; + response.suggested_group_name = "awesome_group"; + return helper.response(response); + }); + server.get("/topics/private-messages-all/:username.json", () => { return helper.response({ topic_list: { @@ -159,46 +170,86 @@ acceptance( }); }); - const publishUnreadToMessageBus = function (group_ids) { - publishToMessageBus("/private-message-topic-tracking-state/user/5", { - topic_id: Math.random(), - message_type: "unread", - payload: { - last_read_post_number: 1, - highest_post_number: 2, - notification_level: 2, - group_ids: group_ids || [], - }, - }); - }; - - const publishNewToMessageBus = function (group_ids) { - publishToMessageBus("/private-message-topic-tracking-state/user/5", { - topic_id: Math.random(), - message_type: "new_topic", - payload: { - last_read_post_number: null, - highest_post_number: 1, - group_ids: group_ids || [], - }, - }); - }; - - const publishArchiveToMessageBus = function () { - publishToMessageBus("/private-message-topic-tracking-state/user/5", { - topic_id: Math.random(), - message_type: "archive", - }); - }; - - const publishGroupArchiveToMessageBus = function (group_ids) { + const publishUnreadToMessageBus = function (opts = {}) { publishToMessageBus( - `/private-message-topic-tracking-state/group/${group_ids[0]}`, + `/private-message-topic-tracking-state/user/${opts.userId || 5}`, + { + topic_id: Math.random(), + message_type: "unread", + payload: { + last_read_post_number: 1, + highest_post_number: 2, + notification_level: 2, + group_ids: opts.groupIds || [], + }, + } + ); + }; + + const publishNewToMessageBus = function (opts = {}) { + publishToMessageBus( + `/private-message-topic-tracking-state/user/${opts.userId || 5}`, + { + topic_id: Math.random(), + message_type: "new_topic", + payload: { + last_read_post_number: null, + highest_post_number: 1, + group_ids: opts.groupIds || [], + }, + } + ); + }; + + const publishArchiveToMessageBus = function (userId) { + publishToMessageBus( + `/private-message-topic-tracking-state/user/${userId || 5}`, + { + topic_id: Math.random(), + message_type: "archive", + } + ); + }; + + const publishGroupArchiveToMessageBus = function (groupIds) { + publishToMessageBus( + `/private-message-topic-tracking-state/group/${groupIds[0]}`, { topic_id: Math.random(), message_type: "group_archive", payload: { - group_ids: group_ids, + group_ids: groupIds, + }, + } + ); + }; + + const publishGroupUnreadToMessageBus = function (groupIds) { + publishToMessageBus( + `/private-message-topic-tracking-state/group/${groupIds[0]}`, + { + topic_id: Math.random(), + message_type: "unread", + payload: { + last_read_post_number: 1, + highest_post_number: 2, + notification_level: 2, + group_ids: groupIds || [], + }, + } + ); + }; + + const publishGroupNewToMessageBus = function (groupIds) { + publishToMessageBus( + `/private-message-topic-tracking-state/group/${groupIds[0]}`, + { + topic_id: Math.random(), + message_type: "new_topic", + payload: { + last_read_post_number: null, + highest_post_number: 1, + group_ids: groupIds || [], }, } ); @@ -332,8 +383,8 @@ acceptance( test("incoming unread messages while viewing group unread", async function (assert) { await visit("/u/charlie/messages/group/awesome_group/unread"); - publishUnreadToMessageBus([14]); - publishNewToMessageBus([14]); + publishUnreadToMessageBus({ groupIds: [14] }); + publishNewToMessageBus({ groupIds: [14] }); await visit("/u/charlie/messages/group/awesome_group/unread"); // wait for re-render @@ -544,6 +595,74 @@ acceptance( "does not display the tags filter" ); }); + + test("suggested messages without new or unread", async function (assert) { + await visit("/t/12"); + + assert.equal( + query(".suggested-topics-message").innerText.trim(), + "Want to read more? Browse other messages in personal messages.", + "displays the right browse more message" + ); + }); + + test("suggested messages with new and unread", async function (assert) { + await visit("/t/12"); + + publishNewToMessageBus({ userId: 5 }); + + await visit("/t/12"); // await re-render + + assert.equal( + query(".suggested-topics-message").innerText.trim(), + "There is 1 new message remaining, or browse other personal messages", + "displays the right browse more message" + ); + + publishUnreadToMessageBus({ userId: 5 }); + + await visit("/t/12"); // await re-render + + assert.equal( + query(".suggested-topics-message").innerText.trim(), + "There is 1 unread and 1 new message remaining, or browse other personal messages", + "displays the right browse more message" + ); + }); + + test("suggested messages for group messages without new or unread", async function (assert) { + await visit("/t/13"); + + assert.equal( + query(".suggested-topics-message").innerText.trim(), + "Want to read more? Browse other messages in awesome_group.", + "displays the right browse more message" + ); + }); + + test("suggested messages for group messages with new and unread", async function (assert) { + await visit("/t/13"); + + publishGroupNewToMessageBus([14]); + + await visit("/t/13"); // await re-render + + assert.equal( + query(".suggested-topics-message").innerText.trim(), + "There is 1 new message remaining, or browse other messages in awesome_group", + "displays the right browse more message" + ); + + publishGroupUnreadToMessageBus([14]); + + await visit("/t/13"); // await re-render + + assert.equal( + query(".suggested-topics-message").innerText.trim(), + "There is 1 unread and 1 new message remaining, or browse other messages in awesome_group", + "displays the right browse more message" + ); + }); } ); diff --git a/app/serializers/current_user_serializer.rb b/app/serializers/current_user_serializer.rb index b0fd9fbe708..5bd1e7e0157 100644 --- a/app/serializers/current_user_serializer.rb +++ b/app/serializers/current_user_serializer.rb @@ -69,8 +69,8 @@ class CurrentUserSerializer < BasicUserSerializer def groups owned_group_ids = GroupUser.where(user_id: id, owner: true).pluck(:group_id).to_set - object.visible_groups.pluck(:id, :name).map do |id, name| - group = { id: id, name: name } + object.visible_groups.pluck(:id, :name, :has_messages).map do |id, name, has_messages| + group = { id: id, name: name, has_messages: has_messages } group[:owner] = true if owned_group_ids.include?(id) group end diff --git a/app/serializers/suggested_topics_mixin.rb b/app/serializers/suggested_topics_mixin.rb index cfd8f23ce18..cf0de7b4a10 100644 --- a/app/serializers/suggested_topics_mixin.rb +++ b/app/serializers/suggested_topics_mixin.rb @@ -4,6 +4,7 @@ module SuggestedTopicsMixin def self.included(klass) klass.attributes :related_messages klass.attributes :suggested_topics + klass.attributes :suggested_group_name end def include_related_messages? @@ -16,6 +17,24 @@ module SuggestedTopicsMixin object.next_page.nil? && object.suggested_topics&.topics end + def include_suggested_group_name? + return false unless include_suggested_topics? + object.topic.private_message? && scope.user + end + + def suggested_group_name + return if object.topic.topic_allowed_users.exists?(user_id: scope.user.id) + + if object.topic_allowed_group_ids.present? + Group.joins(:group_users) + .where( + "group_users.group_id IN (?) AND group_users.user_id = ?", + object.topic_allowed_group_ids, scope.user.id + ) + .pluck_first(:name) + end + end + def related_messages object.related_messages.topics.map do |t| SuggestedTopicSerializer.new(t, scope: scope, root: false) diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index f531052cad7..846e7512348 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1204,6 +1204,38 @@ en: failed_to_move: "Failed to move selected messages (perhaps your network is down)" tags: "Tags" warnings: "Official Warnings" + read_more_in_group: "Want to read more? Browse other messages in %{groupLink}." + read_more: "Want to read more? Browse other messages in personal messages." + + read_more_group_pm_MF: "There { + UNREAD, plural, + =0 {} + one { + is # unread + } other { + are # unread + } + } { + NEW, plural, + =0 {} + one { {BOTH, select, true{and } false {is } other{}} # new message} + other { {BOTH, select, true{and } false {are } other{}} # new messages} + } remaining, or browse other messages in {groupLink}" + + read_more_personal_pm_MF: "There { + UNREAD, plural, + =0 {} + one { + is # unread + } other { + are # unread + } + } { + NEW, plural, + =0 {} + one { {BOTH, select, true{and } false {is } other{}} # new message} + other { {BOTH, select, true{and } false {are } other{}} # new messages} + } remaining, or browse other personal messages" preferences_nav: account: "Account" diff --git a/lib/topic_view.rb b/lib/topic_view.rb index e97ea551453..0e72a2b14b0 100644 --- a/lib/topic_view.rb +++ b/lib/topic_view.rb @@ -464,11 +464,18 @@ class TopicView end end + def topic_allowed_group_ids + @topic_allowed_group_ids ||= begin + @topic.allowed_groups.map(&:id) + end + end + def group_allowed_user_ids return @group_allowed_user_ids unless @group_allowed_user_ids.nil? - group_ids = @topic.allowed_groups.map(&:id) - @group_allowed_user_ids = Set.new(GroupUser.where(group_id: group_ids).pluck('distinct user_id')) + @group_allowed_user_ids = GroupUser + .where(group_id: topic_allowed_group_ids) + .pluck('distinct user_id') end def category_group_moderator_user_ids diff --git a/spec/serializers/current_user_serializer_spec.rb b/spec/serializers/current_user_serializer_spec.rb index 52049da3f95..3b63aff05e8 100644 --- a/spec/serializers/current_user_serializer_spec.rb +++ b/spec/serializers/current_user_serializer_spec.rb @@ -135,7 +135,9 @@ RSpec.describe CurrentUserSerializer do public_group.save! payload = serializer.as_json - expect(payload[:groups]).to eq([{ id: public_group.id, name: public_group.name }]) + expect(payload[:groups]).to contain_exactly( + { id: public_group.id, name: public_group.name, has_messages: false } + ) end end diff --git a/spec/serializers/topic_view_serializer_spec.rb b/spec/serializers/topic_view_serializer_spec.rb index cc4eabbf2fa..02a126060a9 100644 --- a/spec/serializers/topic_view_serializer_spec.rb +++ b/spec/serializers/topic_view_serializer_spec.rb @@ -151,6 +151,34 @@ describe TopicViewSerializer do end end + describe '#suggested_group_name' do + fab!(:pm) { Fabricate(:private_message_post).topic } + fab!(:group) { Fabricate(:group) } + + it 'is nil for a regular topic' do + json = serialize_topic(topic, user) + + expect(json[:suggested_group_name]).to eq(nil) + end + + it 'is nil if user is an allowed user of the private message' do + pm.allowed_users << user + + json = serialize_topic(pm, user) + + expect(json[:suggested_group_name]).to eq(nil) + end + + it 'returns the right group name if user is part of allowed group in the private message' do + pm.allowed_groups << group + group.add(user) + + json = serialize_topic(pm, user) + + expect(json[:suggested_group_name]).to eq(group.name) + end + end + describe 'when tags added to private message topics' do fab!(:moderator) { Fabricate(:moderator) } fab!(:tag) { Fabricate(:tag) }