FEATURE: Add new/unread counts to tags section links exp sidebar (#17057)

This commit is contained in:
Alan Guo Xiang Tan 2022-06-13 14:54:01 +08:00 committed by GitHub
parent 95fa4c5d52
commit e9a77e7f19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 281 additions and 19 deletions

View File

@ -4,10 +4,27 @@ import GlimmerComponent from "discourse/components/glimmer";
import TagSectionLink from "discourse/lib/sidebar/tags-section/tag-section-link";
export default class SidebarTagsSection extends GlimmerComponent {
constructor() {
super(...arguments);
this.callbackId = this.topicTrackingState.onStateChange(() => {
this.sectionLinks.forEach((sectionLink) => {
sectionLink.refreshCounts();
});
});
}
willDestroy() {
this.topicTrackingState.offStateChange(this.callbackId);
}
@cached
get sectionLinks() {
return this.currentUser.trackedTags.map((trackedTag) => {
return new TagSectionLink({ tag: trackedTag });
return new TagSectionLink({
tag: trackedTag,
topicTrackingState: this.topicTrackingState,
});
});
}
}

View File

@ -1,6 +1,30 @@
import I18n from "I18n";
import { tracked } from "@glimmer/tracking";
import { bind } from "discourse-common/utils/decorators";
export default class TagSectionLink {
constructor({ tag }) {
@tracked totalUnread = 0;
@tracked totalNew = 0;
constructor({ tag, topicTrackingState }) {
this.tag = tag;
this.topicTrackingState = topicTrackingState;
this.refreshCounts();
}
@bind
refreshCounts() {
this.totalUnread = this.topicTrackingState.countUnread({
tagId: this.tag,
});
if (this.totalUnread === 0) {
this.totalNew = this.topicTrackingState.countNew({
tagId: this.tag,
});
}
}
get name() {
@ -22,4 +46,26 @@ export default class TagSectionLink {
get text() {
return this.tag;
}
get badgeText() {
if (this.totalUnread > 0) {
return I18n.t("sidebar.unread_count", {
count: this.totalUnread,
});
} else if (this.totalNew > 0) {
return I18n.t("sidebar.new_count", {
count: this.totalNew,
});
}
}
get route() {
if (this.totalUnread > 0) {
return "tag.showUnread";
} else if (this.totalNew > 0) {
return "tag.showNew";
} else {
return "tag.show";
}
}
}

View File

@ -12,6 +12,7 @@
@title={{sectionLink.title}}
@content={{sectionLink.text}}
@currentWhen={{sectionLink.currentWhen}}
@badgeText={{sectionLink.badgeText}}
@model={{sectionLink.model}}>
</Sidebar::SectionLink>
{{/each}}

View File

@ -1,11 +1,12 @@
import I18n from "I18n";
import { click, currentURL, visit } from "@ember/test-helpers";
import { click, currentURL, settled, visit } from "@ember/test-helpers";
import {
acceptance,
conditionalTest,
exists,
publishToMessageBus,
query,
queryAll,
updateCurrentUser,
@ -226,4 +227,147 @@ acceptance("Sidebar - Tags section", function (needs) {
);
}
);
conditionalTest(
"new and unread count for tag section links",
!isLegacyEmber(),
async function (assert) {
this.container.lookup("topic-tracking-state:main").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,
unread_not_too_old: 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,
unread_not_too_old: true,
treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z",
tags: ["tag1"],
},
{
topic_id: 3,
highest_post_number: 15,
last_read_post_number: 14,
created_at: "2021-06-14T12:41:02.477Z",
category_id: 3,
notification_level: 2,
created_in_new_period: false,
unread_not_too_old: true,
treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z",
tags: ["tag2"],
},
{
topic_id: 4,
highest_post_number: 17,
last_read_post_number: 16,
created_at: "2020-10-31T03:41:42.257Z",
category_id: 4,
notification_level: 2,
created_in_new_period: false,
unread_not_too_old: true,
treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z",
tags: ["tag4"],
},
]);
await visit("/");
assert.strictEqual(
query(
`.sidebar-section-link-tag1 .sidebar-section-link-content-badge`
).textContent.trim(),
I18n.t("sidebar.unread_count", { count: 1 }),
`displays 1 unread count for tag1 section link`
);
assert.strictEqual(
query(
`.sidebar-section-link-tag2 .sidebar-section-link-content-badge`
).textContent.trim(),
I18n.t("sidebar.unread_count", { count: 1 }),
`displays 1 unread count for tag2 section link`
);
assert.ok(
!exists(
`.sidebar-section-link-tag3 .sidebar-section-link-content-badge`
),
"does not display any badge for tag3 section link"
);
publishToMessageBus("/unread", {
topic_id: 2,
message_type: "read",
payload: {
last_read_post_number: 12,
highest_post_number: 12,
},
});
await settled();
assert.strictEqual(
query(
`.sidebar-section-link-tag1 .sidebar-section-link-content-badge`
).textContent.trim(),
I18n.t("sidebar.new_count", { count: 1 }),
`displays 1 new count for tag1 section link`
);
publishToMessageBus("/unread", {
topic_id: 1,
message_type: "read",
payload: {
last_read_post_number: 1,
highest_post_number: 1,
},
});
await settled();
assert.ok(
!exists(
`.sidebar-section-link-tag1 .sidebar-section-link-content-badge`
),
`does not display any badge tag1 section link`
);
}
);
conditionalTest(
"cleans up topic tracking state state changed callbacks when section is destroyed",
!isLegacyEmber(),
async function (assert) {
await visit("/");
const topicTrackingState = this.container.lookup(
"topic-tracking-state:main"
);
const initialCallbackCount = Object.keys(
topicTrackingState.stateChangeCallbacks
).length;
await click(".header-sidebar-toggle .btn");
await click(".header-sidebar-toggle .btn");
assert.strictEqual(
Object.keys(topicTrackingState.stateChangeCallbacks).length,
initialCallbackCount
);
}
);
});

View File

@ -278,7 +278,7 @@ class TopicTrackingState
end
def self.include_tags_in_report?
SiteSetting.tagging_enabled && @include_tags_in_report
SiteSetting.tagging_enabled && (@include_tags_in_report || SiteSetting.enable_experimental_sidebar)
end
def self.include_tags_in_report=(v)

View File

@ -9,7 +9,8 @@ class TopicTrackingStateSerializer < ApplicationSerializer
:notification_level,
:created_in_new_period,
:unread_not_too_old,
:treat_as_new_topic_start_date
:treat_as_new_topic_start_date,
:tags
def created_in_new_period
return true if !scope
@ -20,4 +21,8 @@ class TopicTrackingStateSerializer < ApplicationSerializer
return true if object.first_unread_at.blank?
object.updated_at >= object.first_unread_at
end
def include_tags?
object.respond_to?(:tags)
end
end

View File

@ -539,14 +539,7 @@ describe TopicTrackingState do
end
context "tag support" do
after do
# this is a bit of an odd hook, but this is a global change
# used by plugins that leverage tagging heavily and need
# tag information in topic tracking state
TopicTrackingState.include_tags_in_report = false
end
it "correctly handles tags" do
before do
SiteSetting.tagging_enabled = true
post.topic.notifier.watch_topic!(post.topic.user_id)
@ -556,6 +549,27 @@ describe TopicTrackingState do
Guardian.new(Discourse.system_user),
['bananas', 'apples']
)
end
it "includes tags when SiteSetting.enable_experimental_sidebar is true" do
report = TopicTrackingState.report(user)
expect(report.length).to eq(1)
row = report[0]
expect(row.respond_to?(:tags)).to eq(false)
SiteSetting.enable_experimental_sidebar = true
report = TopicTrackingState.report(user)
expect(report.length).to eq(1)
row = report[0]
expect(row.tags).to contain_exactly("apples", "bananas")
end
it "includes tags when TopicTrackingState.include_tags_in_report option is enabled" do
report = TopicTrackingState.report(user)
expect(report.length).to eq(1)
row = report[0]
expect(row.respond_to? :tags).to eq(false)
TopicTrackingState.include_tags_in_report = true
@ -563,13 +577,8 @@ describe TopicTrackingState do
expect(report.length).to eq(1)
row = report[0]
expect(row.tags).to contain_exactly("apples", "bananas")
ensure
TopicTrackingState.include_tags_in_report = false
report = TopicTrackingState.report(user)
expect(report.length).to eq(1)
row = report[0]
expect(row.respond_to? :tags).to eq(false)
end
end

View File

@ -0,0 +1,40 @@
# frozen_string_literal: true
describe TopicTrackingStateSerializer do
fab!(:user) { Fabricate(:user) }
fab!(:post) { create_post }
it 'serializes topic tracking state reports' do
report = TopicTrackingState.report(user)
serialized = described_class.new(report[0], scope: Guardian.new(user), root: false).as_json
expect(serialized[:topic_id]).to eq(post.topic_id)
expect(serialized[:highest_post_number]).to eq(post.topic.highest_post_number)
expect(serialized[:last_read_post_number]).to eq(nil)
expect(serialized[:created_at]).to be_present
expect(serialized[:notification_level]).to eq(nil)
expect(serialized[:created_in_new_period]).to eq(true)
expect(serialized[:unread_not_too_old]).to eq(true)
expect(serialized[:treat_as_new_topic_start_date]).to be_present
expect(serialized.has_key?(:tags)).to eq(false)
end
it "includes tags attribute when tags are present" do
TopicTrackingState.include_tags_in_report = true
post.topic.notifier.watch_topic!(post.topic.user_id)
DiscourseTagging.tag_topic_by_names(
post.topic,
Guardian.new(Discourse.system_user),
['bananas', 'apples']
)
report = TopicTrackingState.report(user)
serialized = described_class.new(report[0], scope: Guardian.new(user), root: false).as_json
expect(serialized[:tags]).to contain_exactly("bananas", "apples")
ensure
TopicTrackingState.include_tags_in_report = false
end
end