DEV: Update internal tracking in pm/topic tracking state (#29120)

This commit is contained in:
Jarek Radosz 2024-10-08 21:13:40 +02:00 committed by GitHub
parent a4531be580
commit da77d06ebb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 120 additions and 136 deletions

View File

@ -28,13 +28,11 @@ export default class CategoryListItem extends Component {
); );
} }
@discourseComputed("topicTrackingState.messageCount") get unreadTopicsCount() {
unreadTopicsCount() {
return this.category.unreadTopicsCount; return this.category.unreadTopicsCount;
} }
@discourseComputed("topicTrackingState.messageCount") get newTopicsCount() {
newTopicsCount() {
return this.category.newTopicsCount; return this.category.newTopicsCount;
} }

View File

@ -1,28 +1,20 @@
import Controller, { inject as controller } from "@ember/controller"; import Controller, { inject as controller } from "@ember/controller";
import { action, computed } from "@ember/object"; import { action } from "@ember/object";
import { service } from "@ember/service";
import I18n from "discourse-i18n"; import I18n from "discourse-i18n";
export default class extends Controller { export default class extends Controller {
@service pmTopicTrackingState;
@controller user; @controller user;
get viewingSelf() { get viewingSelf() {
return this.user.viewingSelf; return this.user.get("viewingSelf");
} }
@computed(
"pmTopicTrackingState.newIncoming.[]",
"pmTopicTrackingState.statesModificationCounter",
"pmTopicTrackingState.isTracking"
)
get newLinkText() { get newLinkText() {
return this.#linkText("new"); return this.#linkText("new");
} }
@computed(
"pmTopicTrackingState.newIncoming.[]",
"pmTopicTrackingState.statesModificationCounter",
"pmTopicTrackingState.isTracking"
)
get unreadLinkText() { get unreadLinkText() {
return this.#linkText("unread"); return this.#linkText("unread");
} }

View File

@ -1,17 +1,16 @@
import Controller, { inject as controller } from "@ember/controller"; import Controller, { inject as controller } from "@ember/controller";
import { computed } from "@ember/object";
import { service } from "@ember/service"; import { service } from "@ember/service";
import I18n from "discourse-i18n"; import I18n from "discourse-i18n";
export default class extends Controller { export default class extends Controller {
@service currentUser;
@service router; @service router;
@controller user; @controller user;
get viewingSelf() { get viewingSelf() {
return this.user.viewingSelf; return this.user.get("viewingSelf");
} }
@computed("viewingSelf", "router.currentRoute.name", "currentUser.admin")
get showWarningsWarning() { get showWarningsWarning() {
return ( return (
this.router.currentRoute.name === "userPrivateMessages.user.warnings" && this.router.currentRoute.name === "userPrivateMessages.user.warnings" &&
@ -20,20 +19,10 @@ export default class extends Controller {
); );
} }
@computed(
"pmTopicTrackingState.newIncoming.[]",
"pmTopicTrackingState.statesModificationCounter",
"pmTopicTrackingState.isTracking"
)
get newLinkText() { get newLinkText() {
return this.#linkText("new"); return this.#linkText("new");
} }
@computed(
"pmTopicTrackingState.newIncoming.[]",
"pmTopicTrackingState.statesModificationCounter",
"pmTopicTrackingState.isTracking"
)
get unreadLinkText() { get unreadLinkText() {
return this.#linkText("unread"); return this.#linkText("unread");
} }

View File

@ -1,7 +1,8 @@
import { tracked } from "@glimmer/tracking"; import { tracked } from "@glimmer/tracking";
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import { action } from "@ember/object"; import { action } from "@ember/object";
import { or, reads } from "@ember/object/computed"; import { dependentKeyCompat } from "@ember/object/compat";
import { or } from "@ember/object/computed";
import { isNone } from "@ember/utils"; import { isNone } from "@ember/utils";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
import BulkSelectHelper from "discourse/lib/bulk-select-helper"; import BulkSelectHelper from "discourse/lib/bulk-select-helper";
@ -26,8 +27,6 @@ export default class UserTopicsListController extends Controller {
bulkSelectHelper = new BulkSelectHelper(this); bulkSelectHelper = new BulkSelectHelper(this);
@reads("pmTopicTrackingState.newIncoming.length") incomingCount;
@or("currentUser.canManageTopic", "showDismissRead", "showResetNew") @or("currentUser.canManageTopic", "showDismissRead", "showResetNew")
canBulkSelect; canBulkSelect;
@ -39,6 +38,11 @@ export default class UserTopicsListController extends Controller {
} }
} }
@dependentKeyCompat
get incomingCount() {
return this.pmTopicTrackingState.newIncoming.length;
}
get bulkSelectEnabled() { get bulkSelectEnabled() {
return this.bulkSelectHelper.bulkSelectEnabled; return this.bulkSelectHelper.bulkSelectEnabled;
} }

View File

@ -307,19 +307,15 @@ export default class NavItem extends EmberObject {
"topicTrackingState.messageCount" "topicTrackingState.messageCount"
) )
count(name, category, tagId, noSubcategories, currentRouteQueryParams) { count(name, category, tagId, noSubcategories, currentRouteQueryParams) {
const state = this.topicTrackingState; return this.topicTrackingState?.lookupCount({
type: name,
if (state) { category,
return state.lookupCount({ tagId,
type: name, noSubcategories,
category, customFilterFn: hasTrackedFilter(currentRouteQueryParams)
tagId, ? isTrackedTopic
noSubcategories, : undefined,
customFilterFn: hasTrackedFilter(currentRouteQueryParams) });
? isTrackedTopic
: undefined,
});
}
} }
} }

View File

@ -1,13 +1,15 @@
import { tracked } from "@glimmer/tracking";
import EmberObject, { get } from "@ember/object"; import EmberObject, { get } from "@ember/object";
import { service } from "@ember/service"; import { service } from "@ember/service";
import { isEmpty } from "@ember/utils"; import { isEmpty } from "@ember/utils";
import { TrackedArray, TrackedMap } from "@ember-compat/tracked-built-ins";
import { NotificationLevels } from "discourse/lib/notification-levels"; import { NotificationLevels } from "discourse/lib/notification-levels";
import PreloadStore from "discourse/lib/preload-store"; import PreloadStore from "discourse/lib/preload-store";
import DiscourseURL from "discourse/lib/url"; import DiscourseURL from "discourse/lib/url";
import Category from "discourse/models/category"; import Category from "discourse/models/category";
import Site from "discourse/models/site"; import Site from "discourse/models/site";
import { deepEqual, deepMerge } from "discourse-common/lib/object"; import { deepEqual, deepMerge } from "discourse-common/lib/object";
import discourseComputed, { bind } from "discourse-common/utils/decorators"; import { bind } from "discourse-common/utils/decorators";
function isNew(topic) { function isNew(topic) {
return ( return (
@ -52,15 +54,15 @@ export default class TopicTrackingState extends EmberObject {
@service messageBus; @service messageBus;
@service siteSettings; @service siteSettings;
messageCount = 0; @tracked messageCount = 0;
@tracked incomingCount = 0;
init() { @tracked newIncoming;
super.init(...arguments); @tracked filterCategory;
@tracked filterTag;
this.states = new Map(); @tracked filter;
this.stateChangeCallbacks = {}; states = new TrackedMap();
this._trackedTopicLimit = 4000; stateChangeCallbacks = {};
} _trackedTopicLimit = 4000;
willDestroy() { willDestroy() {
super.willDestroy(...arguments); super.willDestroy(...arguments);
@ -143,18 +145,18 @@ export default class TopicTrackingState extends EmberObject {
@bind @bind
onDeleteMessage(msg) { onDeleteMessage(msg) {
this.modifyStateProp(msg, "deleted", true); this.modifyStateProp(msg, "deleted", true);
this.incrementMessageCount(); this.messageCount++;
} }
@bind @bind
onRecoverMessage(msg) { onRecoverMessage(msg) {
this.modifyStateProp(msg, "deleted", false); this.modifyStateProp(msg, "deleted", false);
this.incrementMessageCount(); this.messageCount++;
} }
@bind @bind
onDestroyMessage(msg) { onDestroyMessage(msg) {
this.incrementMessageCount(); this.messageCount++;
const currentRoute = DiscourseURL.router.currentRoute.parent; const currentRoute = DiscourseURL.router.currentRoute.parent;
if ( if (
@ -165,49 +167,50 @@ export default class TopicTrackingState extends EmberObject {
} }
} }
mutedTopics() { get mutedTopics() {
return (this.currentUser && this.currentUser.muted_topics) || []; return this.currentUser?.muted_topics || [];
} }
unmutedTopics() { get unmutedTopics() {
return (this.currentUser && this.currentUser.unmuted_topics) || []; return this.currentUser?.unmuted_topics || [];
} }
trackMutedOrUnmutedTopic(data) { trackMutedOrUnmutedTopic(data) {
let topics, key; let topics, key;
if (data.message_type === "muted") { if (data.message_type === "muted") {
key = "muted_topics"; key = "muted_topics";
topics = this.mutedTopics(); topics = this.mutedTopics;
} else { } else {
key = "unmuted_topics"; key = "unmuted_topics";
topics = this.unmutedTopics(); topics = this.unmutedTopics;
} }
topics = topics.concat({ topics = topics.concat({
topicId: data.topic_id, topicId: data.topic_id,
createdAt: Date.now(), createdAt: Date.now(),
}); });
this.currentUser && this.currentUser.set(key, topics); this.currentUser?.set(key, topics);
} }
pruneOldMutedAndUnmutedTopics() { pruneOldMutedAndUnmutedTopics() {
const now = Date.now(); const now = Date.now();
let mutedTopics = this.mutedTopics().filter( let mutedTopics = this.mutedTopics.filter(
(mutedTopic) => now - mutedTopic.createdAt < 60000 (mutedTopic) => now - mutedTopic.createdAt < 60000
); );
let unmutedTopics = this.unmutedTopics().filter( let unmutedTopics = this.unmutedTopics.filter(
(unmutedTopic) => now - unmutedTopic.createdAt < 60000 (unmutedTopic) => now - unmutedTopic.createdAt < 60000
); );
this.currentUser &&
this.currentUser.set("muted_topics", mutedTopics) && this.currentUser?.set("muted_topics", mutedTopics);
this.currentUser.set("unmuted_topics", unmutedTopics); this.currentUser?.set("unmuted_topics", unmutedTopics);
} }
isMutedTopic(topicId) { isMutedTopic(topicId) {
return !!this.mutedTopics().findBy("topicId", topicId); return !!this.mutedTopics.findBy("topicId", topicId);
} }
isUnmutedTopic(topicId) { isUnmutedTopic(topicId) {
return !!this.unmutedTopics().findBy("topicId", topicId); return !!this.unmutedTopics.findBy("topicId", topicId);
} }
/** /**
@ -235,7 +238,7 @@ export default class TopicTrackingState extends EmberObject {
state.last_read_post_number < highestSeen state.last_read_post_number < highestSeen
) { ) {
this.modifyStateProp(topicId, "last_read_post_number", highestSeen); this.modifyStateProp(topicId, "last_read_post_number", highestSeen);
this.incrementMessageCount(); this.messageCount++;
} }
} }
@ -324,7 +327,7 @@ export default class TopicTrackingState extends EmberObject {
} }
// hasIncoming relies on this count // hasIncoming relies on this count
this.set("incomingCount", this.newIncoming.length); this.incomingCount = this.newIncoming.length;
} }
/** /**
@ -335,8 +338,8 @@ export default class TopicTrackingState extends EmberObject {
* @method resetTracking * @method resetTracking
*/ */
resetTracking() { resetTracking() {
this.newIncoming = []; this.newIncoming = new TrackedArray();
this.set("incomingCount", 0); this.incomingCount = 0;
} }
/** /**
@ -346,10 +349,10 @@ export default class TopicTrackingState extends EmberObject {
*/ */
clearIncoming(topicIds) { clearIncoming(topicIds) {
const toRemove = new Set(topicIds); const toRemove = new Set(topicIds);
this.newIncoming = this.newIncoming.filter( this.newIncoming = new TrackedArray(
(topicId) => !toRemove.has(topicId) this.newIncoming.filter((topicId) => !toRemove.has(topicId))
); );
this.set("incomingCount", this.newIncoming.length); this.incomingCount = this.newIncoming.length;
} }
/** /**
@ -366,7 +369,7 @@ export default class TopicTrackingState extends EmberObject {
* c/cat/sub-cat/6/l/latest or tags/c/cat/sub-cat/6/test/l/latest. * c/cat/sub-cat/6/l/latest or tags/c/cat/sub-cat/6/test/l/latest.
*/ */
trackIncoming(filter) { trackIncoming(filter) {
this.newIncoming = []; this.newIncoming = new TrackedArray();
let category, tag; let category, tag;
@ -388,21 +391,20 @@ export default class TopicTrackingState extends EmberObject {
tag = split[1]; tag = split[1];
} }
this.set("filterCategory", category); this.filterCategory = category;
this.set("filterTag", tag); this.filterTag = tag;
this.set("filter", filter); this.filter = filter;
this.set("incomingCount", 0); this.incomingCount = 0;
} }
/** /**
* Used to determine whether to show the message at the top of the topic list * Used to determine whether to show the message at the top of the topic list
* e.g. "see 1 new or updated topic" * e.g. "see 1 new or updated topic"
* *
* @method incomingCount * @method hasIncoming
*/ */
@discourseComputed("incomingCount") get hasIncoming() {
hasIncoming(incomingCount) { return this.incomingCount > 0;
return incomingCount && incomingCount > 0;
} }
/** /**
@ -430,7 +432,7 @@ export default class TopicTrackingState extends EmberObject {
*/ */
removeTopics(topicIds) { removeTopics(topicIds) {
topicIds.forEach((topicId) => this.removeTopic(topicId)); topicIds.forEach((topicId) => this.removeTopic(topicId));
this.incrementMessageCount(); this.messageCount++;
this._afterStateChange(); this._afterStateChange();
} }
@ -525,11 +527,7 @@ export default class TopicTrackingState extends EmberObject {
this._correctMissingState(list, filter); this._correctMissingState(list, filter);
} }
this.incrementMessageCount(); this.messageCount++;
}
incrementMessageCount() {
this.incrementProperty("messageCount");
} }
_generateCallbackId() { _generateCallbackId() {
@ -1029,7 +1027,7 @@ export default class TopicTrackingState extends EmberObject {
if (old.tags !== data.payload.tags) { if (old.tags !== data.payload.tags) {
this.modifyStateProp(data, "tags", data.payload.tags); this.modifyStateProp(data, "tags", data.payload.tags);
this.incrementMessageCount(); this.messageCount++;
} }
} }
@ -1071,7 +1069,7 @@ export default class TopicTrackingState extends EmberObject {
} }
this.modifyState(data, payload); this.modifyState(data, payload);
this.incrementMessageCount(); this.messageCount++;
} }
} }
} }
@ -1081,7 +1079,7 @@ export default class TopicTrackingState extends EmberObject {
this.modifyStateProp(topicId, "is_seen", true); this.modifyStateProp(topicId, "is_seen", true);
}); });
this.incrementMessageCount(); this.messageCount++;
} }
_dismissNewPosts(topicIds) { _dismissNewPosts(topicIds) {
@ -1097,7 +1095,7 @@ export default class TopicTrackingState extends EmberObject {
} }
}); });
this.incrementMessageCount(); this.messageCount++;
} }
_addIncoming(topicId) { _addIncoming(topicId) {
@ -1129,7 +1127,6 @@ export default class TopicTrackingState extends EmberObject {
} }
_afterStateChange() { _afterStateChange() {
this.notifyPropertyChange("states");
Object.values(this.stateChangeCallbacks).forEach((cb) => cb()); Object.values(this.stateChangeCallbacks).forEach((cb) => cb());
} }

View File

@ -1,3 +1,4 @@
import { getOwner } from "@ember/owner";
import { capitalize } from "@ember/string"; import { capitalize } from "@ember/string";
import { findOrResetCachedTopicList } from "discourse/lib/cached-topic-list"; import { findOrResetCachedTopicList } from "discourse/lib/cached-topic-list";
import createPMRoute from "discourse/routes/build-private-messages-route"; import createPMRoute from "discourse/routes/build-private-messages-route";
@ -80,10 +81,10 @@ export default (inboxType, filter) => {
const userTopicsListController = this.controllerFor("user-topics-list"); const userTopicsListController = this.controllerFor("user-topics-list");
userTopicsListController.set("group", this.group); userTopicsListController.set("group", this.group);
userTopicsListController.set( const pmTopicTrackingState = getOwner(this).lookup(
"pmTopicTrackingState.activeGroup", "service:pm-topic-tracking-state"
this.group
); );
pmTopicTrackingState.activeGroup = this.group;
this.controllerFor("user-private-messages").set("group", this.group); this.controllerFor("user-private-messages").set("group", this.group);
} }

View File

@ -1,4 +1,6 @@
import Service from "@ember/service"; import { tracked } from "@glimmer/tracking";
import Service, { service } from "@ember/service";
import { TrackedArray, TrackedMap } from "@ember-compat/tracked-built-ins";
import { Promise } from "rsvp"; import { Promise } from "rsvp";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
@ -12,22 +14,22 @@ import {
import { deepEqual, deepMerge } from "discourse-common/lib/object"; import { deepEqual, deepMerge } from "discourse-common/lib/object";
import { bind } from "discourse-common/utils/decorators"; import { bind } from "discourse-common/utils/decorators";
const CHANNEL_PREFIX = "/private-message-topic-tracking-state";
// See private_message_topic_tracking_state.rb for documentation // See private_message_topic_tracking_state.rb for documentation
class PrivateMessageTopicTrackingState extends Service { class PrivateMessageTopicTrackingState extends Service {
CHANNEL_PREFIX = "/private-message-topic-tracking-state"; @service currentUser;
inbox = null; @service messageBus;
filter = null;
activeGroup = null;
init() { @tracked isTracking = false;
super.init(...arguments); @tracked isTrackingIncoming = false;
@tracked statesModificationCounter = 0;
this.states = new Map(); @tracked inbox = null;
this.statesModificationCounter = 0; @tracked filter = null;
this.isTracking = false; @tracked activeGroup = null;
this.newIncoming = []; @tracked newIncoming = new TrackedArray();
this.stateChangeCallbacks = new Map(); states = new TrackedMap();
} stateChangeCallbacks = new Map();
willDestroy() { willDestroy() {
super.willDestroy(...arguments); super.willDestroy(...arguments);
@ -62,7 +64,7 @@ class PrivateMessageTopicTrackingState extends Service {
}); });
return this._loadInitialState().finally(() => { return this._loadInitialState().finally(() => {
this.set("isTracking", true); this.isTracking = true;
}); });
} }
@ -82,13 +84,11 @@ class PrivateMessageTopicTrackingState extends Service {
}).length; }).length;
} }
trackIncoming(inbox, filter, group) { trackIncoming(inbox, filter, activeGroup) {
this.setProperties({ this.inbox = inbox;
inbox, this.filter = filter;
filter, this.activeGroup = activeGroup;
activeGroup: group, this.isTrackingIncoming = true;
isTrackingIncoming: true,
});
} }
resetIncomingTracking(topicIds) { resetIncomingTracking(topicIds) {
@ -98,21 +98,18 @@ class PrivateMessageTopicTrackingState extends Service {
if (topicIds) { if (topicIds) {
const topicIdSet = new Set(topicIds); const topicIdSet = new Set(topicIds);
this.set( this.newIncoming = new TrackedArray(
"newIncoming",
this.newIncoming.filter((id) => !topicIdSet.has(id)) this.newIncoming.filter((id) => !topicIdSet.has(id))
); );
} else { } else {
this.set("newIncoming", []); this.newIncoming = new TrackedArray();
} }
} }
stopIncomingTracking() { stopIncomingTracking() {
if (this.isTrackingIncoming) { if (this.isTrackingIncoming) {
this.setProperties({ this.isTrackingIncoming = false;
isTrackingIncoming: false, this.newIncoming = new TrackedArray();
newIncoming: [],
});
} }
} }
@ -130,11 +127,11 @@ class PrivateMessageTopicTrackingState extends Service {
} }
userChannel() { userChannel() {
return `${this.CHANNEL_PREFIX}/user/${this.currentUser.id}`; return `${CHANNEL_PREFIX}/user/${this.currentUser.id}`;
} }
groupChannel(groupId) { groupChannel(groupId) {
return `${this.CHANNEL_PREFIX}/group/${groupId}`; return `${CHANNEL_PREFIX}/group/${groupId}`;
} }
_isNew(topic) { _isNew(topic) {
@ -248,7 +245,7 @@ class PrivateMessageTopicTrackingState extends Service {
_notifyIncoming(topicId) { _notifyIncoming(topicId) {
if (this.isTrackingIncoming && !this.newIncoming.includes(topicId)) { if (this.isTrackingIncoming && !this.newIncoming.includes(topicId)) {
this.newIncoming.pushObject(topicId); this.newIncoming.push(topicId);
} }
} }
@ -280,7 +277,7 @@ class PrivateMessageTopicTrackingState extends Service {
} }
_afterStateChange() { _afterStateChange() {
this.incrementProperty("statesModificationCounter"); this.statesModificationCounter++;
this.stateChangeCallbacks.forEach((callback) => callback()); this.stateChangeCallbacks.forEach((callback) => callback());
} }
} }

View File

@ -393,6 +393,16 @@ acceptance(
); );
assert.ok(exists(".show-mores"), "displays the topic incoming info"); assert.ok(exists(".show-mores"), "displays the topic incoming info");
await publishNewToMessageBus({ topicId: 2 });
assert.strictEqual(
query(".messages-nav .user-nav__messages-new").innerText.trim(),
I18n.t("user.messages.new_with_count", { count: 2 }),
"displays the right count"
);
assert.ok(exists(".show-mores"), "displays the topic incoming info");
}); });
test("incoming unread messages while viewing unread", async function (assert) { test("incoming unread messages while viewing unread", async function (assert) {

View File

@ -44,7 +44,7 @@ module("Unit | Model | nav-item", function (hooks) {
last_read_post_number: null, last_read_post_number: null,
created_in_new_period: true, created_in_new_period: true,
}); });
navItem.topicTrackingState.incrementMessageCount(); navItem.topicTrackingState.messageCount++;
assert.strictEqual( assert.strictEqual(
navItem.count, navItem.count,