From aad0d5fcfba4aa8004b3323426f3ff109e157e3e Mon Sep 17 00:00:00 2001 From: Osama Sayegh Date: Fri, 3 Mar 2023 17:52:02 +0300 Subject: [PATCH] DEV: Unify behavior of category and tag links in sidebar in new new view experiment (#20488) Follow up to https://github.com/discourse/discourse/commit/a5094411487f3fa5d1b7922717104ae4c80e5bfd This commit makes category and tag link in the sidebar consistent with the Everything link when the new New view experiment is enabled. In particular: 1. Category and tag links navigate to the per-category (or tag) `/new` view if there's at least one topic, and to `/latest` if there are no topics 2. Category and tag links only show the count of topics in `/new` without text 3. The Everything link navigates to the global `/new` view if there's at least one topic there, and to `/latest` if there are no topics in `/new`. Internal topic: t/77234. --- .../everything-section-link.js | 30 ++-- .../category-section-link.js | 32 +++- .../user/tags-section/tag-section-link.js | 30 +++- .../app/models/topic-tracking-state.js | 36 +++- .../sidebar-user-categories-section-test.js | 168 ++++++++++++++++++ .../sidebar-user-community-section-test.js | 140 +++++++++++++++ .../sidebar-user-tags-section-test.js | 145 +++++++++++++++ 7 files changed, 562 insertions(+), 19 deletions(-) diff --git a/app/assets/javascripts/discourse/app/lib/sidebar/common/community-section/everything-section-link.js b/app/assets/javascripts/discourse/app/lib/sidebar/common/community-section/everything-section-link.js index 44f5fc38953..35ca0858c5d 100644 --- a/app/assets/javascripts/discourse/app/lib/sidebar/common/community-section/everything-section-link.js +++ b/app/assets/javascripts/discourse/app/lib/sidebar/common/community-section/everything-section-link.js @@ -9,7 +9,6 @@ export default class EverythingSectionLink extends BaseSectionLink { @tracked totalNew = 0; @tracked hideCount = this.currentUser?.sidebarListDestination !== UNREAD_LIST_DESTINATION; - linkToNew = !!this.currentUser?.new_new_view_enabled; constructor() { super(...arguments); @@ -27,7 +26,7 @@ export default class EverythingSectionLink extends BaseSectionLink { this.totalUnread = this.topicTrackingState.countUnread(); - if (this.totalUnread === 0 || this.linkToNew) { + if (this.totalUnread === 0 || this.#linkToNew) { this.totalNew = this.topicTrackingState.countNew(); } } @@ -49,16 +48,15 @@ export default class EverythingSectionLink extends BaseSectionLink { } get currentWhen() { - if (this.linkToNew) { - return "discovery.new"; - } else { - return "discovery.latest discovery.new discovery.unread discovery.top"; - } + return "discovery.latest discovery.new discovery.unread discovery.top"; } get badgeText() { - if (this.linkToNew && this.#unreadAndNewCount > 0) { - return this.#unreadAndNewCount.toString(); + if (this.#linkToNew) { + if (this.#unreadAndNewCount > 0) { + return this.#unreadAndNewCount.toString(); + } + return; } if (this.hideCount) { return; @@ -75,8 +73,12 @@ export default class EverythingSectionLink extends BaseSectionLink { } get route() { - if (this.linkToNew) { - return "discovery.new"; + if (this.#linkToNew) { + if (this.#unreadAndNewCount > 0) { + return "discovery.new"; + } else { + return "discovery.latest"; + } } else if ( this.currentUser?.sidebarListDestination === UNREAD_LIST_DESTINATION ) { @@ -106,7 +108,7 @@ export default class EverythingSectionLink extends BaseSectionLink { if ( this.hideCount && (this.totalUnread || this.totalNew) && - !this.linkToNew + !this.#linkToNew ) { return "circle"; } @@ -115,4 +117,8 @@ export default class EverythingSectionLink extends BaseSectionLink { get #unreadAndNewCount() { return this.totalUnread + this.totalNew; } + + get #linkToNew() { + return !!this.currentUser?.new_new_view_enabled; + } } diff --git a/app/assets/javascripts/discourse/app/lib/sidebar/user/categories-section/category-section-link.js b/app/assets/javascripts/discourse/app/lib/sidebar/user/categories-section/category-section-link.js index 1427b50bdf5..a3d9fe8457f 100644 --- a/app/assets/javascripts/discourse/app/lib/sidebar/user/categories-section/category-section-link.js +++ b/app/assets/javascripts/discourse/app/lib/sidebar/user/categories-section/category-section-link.js @@ -7,6 +7,17 @@ import { bind } from "discourse-common/utils/decorators"; import Category from "discourse/models/category"; import { UNREAD_LIST_DESTINATION } from "discourse/controllers/preferences/sidebar"; +const UNREAD_AND_NEW_COUNTABLE = { + propertyName: "unreadAndNewCount", + badgeTextFunction: (count) => count.toString(), + route: "discovery.newCategory", + refreshCountFunction: ({ topicTrackingState, category }) => { + return topicTrackingState.countNewAndUnread({ + categoryId: category.id, + }); + }, +}; + const DEFAULT_COUNTABLES = [ { propertyName: "totalUnread", @@ -74,7 +85,13 @@ export default class CategorySectionLink { } #countables() { - const countables = [...DEFAULT_COUNTABLES]; + const countables = []; + + if (this.#linkToNew) { + countables.push(UNREAD_AND_NEW_COUNTABLE); + } else { + countables.push(...DEFAULT_COUNTABLES); + } if (customCountables.length > 0) { customCountables.forEach((customCountable) => { @@ -157,7 +174,7 @@ export default class CategorySectionLink { } get badgeText() { - if (this.hideCount) { + if (this.hideCount && !this.#linkToNew) { return; } @@ -171,7 +188,10 @@ export default class CategorySectionLink { } get route() { - if (this.currentUser?.sidebarListDestination === UNREAD_LIST_DESTINATION) { + if ( + this.currentUser?.sidebarListDestination === UNREAD_LIST_DESTINATION || + this.#linkToNew + ) { const activeCountable = this.activeCountable; if (activeCountable) { @@ -201,8 +221,12 @@ export default class CategorySectionLink { } get suffixValue() { - if (this.hideCount && this.activeCountable) { + if (this.hideCount && this.activeCountable && !this.#linkToNew) { return "circle"; } } + + get #linkToNew() { + return !!this.currentUser?.new_new_view_enabled; + } } diff --git a/app/assets/javascripts/discourse/app/lib/sidebar/user/tags-section/tag-section-link.js b/app/assets/javascripts/discourse/app/lib/sidebar/user/tags-section/tag-section-link.js index 3d3c4121bae..179ee6486a0 100644 --- a/app/assets/javascripts/discourse/app/lib/sidebar/user/tags-section/tag-section-link.js +++ b/app/assets/javascripts/discourse/app/lib/sidebar/user/tags-section/tag-section-link.js @@ -25,7 +25,7 @@ export default class TagSectionLink extends BaseTagSectionLink { tagId: this.tagName, }); - if (this.totalUnread === 0) { + if (this.totalUnread === 0 || this.#linkToNew) { this.totalNew = this.topicTrackingState.countNew({ tagId: this.tagName, }); @@ -37,6 +37,13 @@ export default class TagSectionLink extends BaseTagSectionLink { } get route() { + if (this.#linkToNew) { + if (this.#unreadAndNewCount > 0) { + return "tag.showNew"; + } else { + return "tag.show"; + } + } if (this.currentUser?.sidebarListDestination === UNREAD_LIST_DESTINATION) { if (this.totalUnread > 0) { return "tag.showUnread"; @@ -53,6 +60,13 @@ export default class TagSectionLink extends BaseTagSectionLink { } get badgeText() { + if (this.#linkToNew) { + if (this.#unreadAndNewCount > 0) { + return this.#unreadAndNewCount.toString(); + } + return; + } + if (this.hideCount) { return; } @@ -76,8 +90,20 @@ export default class TagSectionLink extends BaseTagSectionLink { } get suffixValue() { - if (this.hideCount && (this.totalUnread || this.totalNew)) { + if ( + this.hideCount && + (this.totalUnread || this.totalNew) && + !this.#linkToNew + ) { return "circle"; } } + + get #unreadAndNewCount() { + return this.totalUnread + this.totalNew; + } + + get #linkToNew() { + return !!this.currentUser?.new_new_view_enabled; + } } diff --git a/app/assets/javascripts/discourse/app/models/topic-tracking-state.js b/app/assets/javascripts/discourse/app/models/topic-tracking-state.js index e2605021726..aff8f8699f8 100644 --- a/app/assets/javascripts/discourse/app/models/topic-tracking-state.js +++ b/app/assets/javascripts/discourse/app/models/topic-tracking-state.js @@ -27,6 +27,10 @@ function isUnread(topic) { ); } +function isNewOrUnread(topic) { + return isUnread(topic) || isNew(topic); +} + function isUnseen(topic) { return !topic.is_seen; } @@ -552,7 +556,22 @@ const TopicTrackingState = EmberObject.extend({ const mutedCategoryIds = this.currentUser?.muted_category_ids?.concat( this.currentUser.indirectly_muted_category_ids ); - let filterFn = type === "new" ? isNew : isUnread; + + let filterFn; + switch (type) { + case "new": + filterFn = isNew; + break; + case "unread": + filterFn = isUnread; + break; + case "new_and_unread": + case "unread_and_new": + filterFn = isNewOrUnread; + break; + default: + throw new Error(`Unkown filter type ${type}`); + } return Array.from(this.states.values()).filter((topic) => { if (!filterFn(topic)) { @@ -599,6 +618,21 @@ const TopicTrackingState = EmberObject.extend({ }); }, + countNewAndUnread({ + categoryId, + tagId, + noSubcategories, + customFilterFn, + } = {}) { + return this.countCategoryByState({ + type: "new_and_unread", + categoryId, + tagId, + noSubcategories, + customFilterFn, + }); + }, + /** * Calls the provided callback for each of the currently tracked topics * we have in state. diff --git a/app/assets/javascripts/discourse/tests/acceptance/sidebar-user-categories-section-test.js b/app/assets/javascripts/discourse/tests/acceptance/sidebar-user-categories-section-test.js index 704cf303a3f..53a55af11f8 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/sidebar-user-categories-section-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/sidebar-user-categories-section-test.js @@ -947,3 +947,171 @@ acceptance("Sidebar - Logged on user - Categories Section", function (needs) { ); }); }); + +acceptance( + "Sidebar - Logged on user - Categories Section - New new view experiment enabled", + function (needs) { + needs.settings({ + navigation_menu: "sidebar", + }); + + needs.user({ new_new_view_enabled: true }); + + test("count shown next to category link", async function (assert) { + const categories = Site.current().categories; + const category1 = categories[0]; + const category2 = categories[1]; + const category3 = categories[2]; + + updateCurrentUser({ + sidebar_category_ids: [category1.id, category2.id, category3.id], + }); + + this.container.lookup("service:topic-tracking-state").loadStates([ + { + topic_id: 1, + highest_post_number: 1, + last_read_post_number: null, + created_at: "2022-05-11T03:09:31.959Z", + category_id: category1.id, + notification_level: null, + created_in_new_period: true, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + }, + { + topic_id: 2, + highest_post_number: 12, + last_read_post_number: 11, + created_at: "2020-02-09T09:40:02.672Z", + category_id: category1.id, + notification_level: 2, + created_in_new_period: false, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + }, + { + topic_id: 3, + highest_post_number: 15, + last_read_post_number: 15, + created_at: "2021-06-14T12:41:02.477Z", + category_id: category1.id, + notification_level: 2, + created_in_new_period: false, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + }, + { + topic_id: 4, + highest_post_number: 10, + last_read_post_number: null, + created_at: "2022-05-11T03:09:31.959Z", + category_id: category2.id, + notification_level: null, + created_in_new_period: true, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + }, + { + topic_id: 5, + highest_post_number: 19, + last_read_post_number: 18, + created_at: "2021-06-14T12:41:02.477Z", + category_id: category3.id, + notification_level: 2, + created_in_new_period: false, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + }, + ]); + + await visit("/"); + + assert.strictEqual( + query( + `.sidebar-section-link[data-category-id="${category1.id}"] .sidebar-section-link-content-badge` + ).textContent.trim(), + "2", + "count for category1 is 2 because it has 1 unread topic and 1 new topic" + ); + + assert.strictEqual( + query( + `.sidebar-section-link[data-category-id="${category2.id}"] .sidebar-section-link-content-badge` + ).textContent.trim(), + "1", + "count for category2 is 1 because it has 1 new topic" + ); + + assert.strictEqual( + query( + `.sidebar-section-link[data-category-id="${category3.id}"] .sidebar-section-link-content-badge` + ).textContent.trim(), + "1", + "count for category3 is 1 because it has 1 unread topic" + ); + }); + + test("category link href", async function (assert) { + const categories = Site.current().categories; + const category1 = categories[0]; + const category2 = categories[1]; + const category3 = categories[2]; + + updateCurrentUser({ + sidebar_category_ids: [category1.id, category2.id, category3.id], + }); + + this.container.lookup("service:topic-tracking-state").loadStates([ + { + topic_id: 1, + highest_post_number: 1, + last_read_post_number: null, + created_at: "2022-05-11T03:09:31.959Z", + category_id: category1.id, + notification_level: null, + created_in_new_period: true, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + }, + { + topic_id: 2, + highest_post_number: 12, + last_read_post_number: 11, + created_at: "2020-02-09T09:40:02.672Z", + category_id: category2.id, + notification_level: 2, + created_in_new_period: false, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + }, + { + topic_id: 3, + highest_post_number: 4, + last_read_post_number: 4, + created_at: "2020-02-09T09:40:02.672Z", + category_id: category3.id, + notification_level: 2, + created_in_new_period: false, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + }, + ]); + + await visit("/"); + + assert.true( + query( + `.sidebar-section-link[data-category-id="${category1.id}"]` + ).href.endsWith("/c/meta/3/l/new"), + "links to the new topics list for the category because there's 1 new topic" + ); + + assert.true( + query( + `.sidebar-section-link[data-category-id="${category2.id}"]` + ).href.endsWith("/c/howto/10/l/new"), + "links to the new topics list for the category because there's 1 unread topic" + ); + + assert.true( + query( + `.sidebar-section-link[data-category-id="${category3.id}"]` + ).href.endsWith("/c/feature/spec/26"), + "links to the latest topics list for the category because there are no unread or new topics" + ); + }); + } +); diff --git a/app/assets/javascripts/discourse/tests/acceptance/sidebar-user-community-section-test.js b/app/assets/javascripts/discourse/tests/acceptance/sidebar-user-community-section-test.js index d032ca38351..88deb17c4a4 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/sidebar-user-community-section-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/sidebar-user-community-section-test.js @@ -1037,3 +1037,143 @@ acceptance("Sidebar - Logged on user - Community Section", function (needs) { assert.ok(teardownCalled, "section link teardown callback was called"); }); }); + +acceptance( + "Sidebar - Logged on user - Community Section - New new view experiment enabled", + function (needs) { + needs.user({ + new_new_view_enabled: true, + }); + + needs.settings({ + navigation_menu: "sidebar", + }); + + test("count shown next to the everything link", async function (assert) { + this.container.lookup("service:topic-tracking-state").loadStates([ + { + topic_id: 1, + highest_post_number: 1, + last_read_post_number: null, + created_at: "2022-05-11T03:09:31.959Z", + category_id: 1, + notification_level: null, + created_in_new_period: true, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + }, + { + topic_id: 2, + highest_post_number: 12, + last_read_post_number: 11, + created_at: "2020-02-09T09:40:02.672Z", + category_id: 2, + notification_level: 2, + created_in_new_period: false, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + }, + { + topic_id: 3, + highest_post_number: 12, + last_read_post_number: 12, + created_at: "2020-02-09T09:40:02.672Z", + category_id: 2, + notification_level: 2, + created_in_new_period: false, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + }, + ]); + + await visit("/"); + + assert.strictEqual( + query( + ".sidebar-section-community .sidebar-section-link-everything .sidebar-section-link-content-badge" + ).textContent.trim(), + "2", + "count is 2 because there's 1 unread topic and 1 new topic" + ); + }); + + test("everything link href", async function (assert) { + this.container.lookup("service:topic-tracking-state").loadStates([ + { + topic_id: 1, + highest_post_number: 1, + last_read_post_number: null, + created_at: "2022-05-11T03:09:31.959Z", + category_id: 1, + notification_level: null, + created_in_new_period: true, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + }, + { + topic_id: 2, + highest_post_number: 12, + last_read_post_number: 11, + created_at: "2020-02-09T09:40:02.672Z", + category_id: 2, + notification_level: 2, + created_in_new_period: false, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + }, + ]); + + await visit("/"); + + assert.true( + query( + ".sidebar-section-community .sidebar-section-link-everything" + ).href.endsWith("/new"), + "links to /new because there are 1 new and 1 unread topics" + ); + + await publishToMessageBus("/unread", { + topic_id: 1, + message_type: "read", + payload: { + last_read_post_number: 3, + highest_post_number: 3, + }, + }); + + assert.true( + query( + ".sidebar-section-community .sidebar-section-link-everything" + ).href.endsWith("/new"), + "links to /new because there is 1 unread topic" + ); + + await publishToMessageBus("/unread", { + topic_id: 2, + message_type: "read", + payload: { + last_read_post_number: 12, + highest_post_number: 12, + }, + }); + + assert.true( + query( + ".sidebar-section-community .sidebar-section-link-everything" + ).href.endsWith("/latest"), + "links to /latest because there are no unread or new topics" + ); + + await publishToMessageBus("/unread", { + topic_id: 1, + message_type: "read", + payload: { + last_read_post_number: null, + highest_post_number: 34, + }, + }); + + assert.true( + query( + ".sidebar-section-community .sidebar-section-link-everything" + ).href.endsWith("/new"), + "links to /new because there is 1 new topic" + ); + }); + } +); diff --git a/app/assets/javascripts/discourse/tests/acceptance/sidebar-user-tags-section-test.js b/app/assets/javascripts/discourse/tests/acceptance/sidebar-user-tags-section-test.js index f7d2ad2b07a..88cec9d74f0 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/sidebar-user-tags-section-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/sidebar-user-tags-section-test.js @@ -657,3 +657,148 @@ acceptance("Sidebar - Logged on user - Tags section", function (needs) { ); }); }); + +acceptance( + "Sidebar - Logged on user - Tags section - New new view enabled", + function (needs) { + needs.settings({ + tagging_enabled: true, + navigation_menu: "sidebar", + }); + + needs.user({ + new_new_view_enabled: true, + display_sidebar_tags: true, + sidebar_tags: [ + { name: "tag2", pm_only: false }, + { name: "tag1", pm_only: false }, + { name: "tag3", pm_only: false }, + ], + }); + + test("count shown next to tag link", async function (assert) { + this.container.lookup("service:topic-tracking-state").loadStates([ + { + topic_id: 1, + highest_post_number: 1, + last_read_post_number: null, + created_at: "2022-05-11T03:09:31.959Z", + category_id: 1, + notification_level: null, + created_in_new_period: true, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + tags: ["tag1", "tag3"], + }, + { + topic_id: 2, + highest_post_number: 12, + last_read_post_number: 11, + created_at: "2020-02-09T09:40:02.672Z", + category_id: 2, + notification_level: 2, + created_in_new_period: false, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + tags: ["tag1", "tag2"], + }, + { + topic_id: 3, + highest_post_number: 15, + last_read_post_number: 15, + created_at: "2021-06-14T12:41:02.477Z", + category_id: 3, + notification_level: 2, + created_in_new_period: false, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + tags: ["tag1"], + }, + ]); + + await visit("/"); + + assert.strictEqual( + query( + '.sidebar-section-link[data-tag-name="tag1"] .sidebar-section-link-content-badge' + ).textContent.trim(), + "2", + "count for tag1 is 2 because it has 1 unread topic and 1 new topic" + ); + + assert.strictEqual( + query( + '.sidebar-section-link[data-tag-name="tag2"] .sidebar-section-link-content-badge' + ).textContent.trim(), + "1", + "count for tag2 is 1 because it has 1 unread topic" + ); + + assert.strictEqual( + query( + '.sidebar-section-link[data-tag-name="tag3"] .sidebar-section-link-content-badge' + ).textContent.trim(), + "1", + "count for tag3 is 1 because it has 1 new topic" + ); + }); + + test("tag link href", async function (assert) { + this.container.lookup("service:topic-tracking-state").loadStates([ + { + topic_id: 1, + highest_post_number: 1, + last_read_post_number: null, + created_at: "2022-05-11T03:09:31.959Z", + category_id: 1, + notification_level: null, + created_in_new_period: true, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + tags: ["tag1"], + }, + { + topic_id: 2, + highest_post_number: 12, + last_read_post_number: 11, + created_at: "2020-02-09T09:40:02.672Z", + category_id: 2, + notification_level: 2, + created_in_new_period: false, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + tags: ["tag2"], + }, + { + topic_id: 3, + highest_post_number: 15, + last_read_post_number: 15, + created_at: "2021-06-14T12:41:02.477Z", + category_id: 3, + notification_level: 2, + created_in_new_period: false, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + tags: ["tag3"], + }, + ]); + + await visit("/"); + + assert.true( + query('.sidebar-section-link[data-tag-name="tag1"]').href.endsWith( + "/tag/tag1/l/new" + ), + "links to the new topics list for the tag because there's 1 new topic" + ); + + assert.true( + query('.sidebar-section-link[data-tag-name="tag2"]').href.endsWith( + "/tag/tag2/l/new" + ), + "links to the new topics list for the tag because there's 1 unread topic" + ); + + assert.true( + query('.sidebar-section-link[data-tag-name="tag3"]').href.endsWith( + "/tag/tag3" + ), + "links to the latest topics list for the tag because there are no unread or new topics" + ); + }); + } +);