From 68d955f8139f790868240eb6edd2517cbbe3bce4 Mon Sep 17 00:00:00 2001 From: zogstrip Date: Thu, 16 Jan 2025 16:23:35 +0100 Subject: [PATCH 1/4] FEATURE: new "notification level when assigned" user preference This adds a new user preference allowed users to control how the notification level of the topic they're assigned to changes. Internal ref - t/141162 --- .discourse-compatibility | 1 + .../notification-level-when-assigned.gjs | 38 ++++++++++++++++ .../initializers/extend-for-assigns.js | 2 + config/locales/client.en.yml | 5 +++ ...ication_level_when_assigned_user_option.rb | 6 +++ lib/assigner.rb | 25 ++++++----- lib/discourse_assign/user_option_extension.rb | 13 ++++++ plugin.rb | 11 +++++ spec/lib/assigner_spec.rb | 40 +++++++++++++++++ spec/system/user_preferences_spec.rb | 43 +++++++++++++++++++ 10 files changed, 173 insertions(+), 11 deletions(-) create mode 100644 assets/javascripts/discourse/connectors/user-preferences-tracking-topics/notification-level-when-assigned.gjs create mode 100644 db/migrate/20250115130542_add_notification_level_when_assigned_user_option.rb create mode 100644 lib/discourse_assign/user_option_extension.rb create mode 100644 spec/system/user_preferences_spec.rb diff --git a/.discourse-compatibility b/.discourse-compatibility index 66f63f2f..f9b8384c 100644 --- a/.discourse-compatibility +++ b/.discourse-compatibility @@ -1,3 +1,4 @@ +< 3.4.0.beta4-dev: 654f197003f9cdf1926b07137fc2214b21c91a79 < 3.4.0.beta3-dev: 6472f4593e1a4abbb457288db012ddb10f0b16f5 < 3.4.0.beta1-dev: fe725251c1b248c349c38c96432e892c668822c6 < 3.3.0.beta2-dev: b796ae3fcc89b48cf777de5ee3a4c21aada9271e diff --git a/assets/javascripts/discourse/connectors/user-preferences-tracking-topics/notification-level-when-assigned.gjs b/assets/javascripts/discourse/connectors/user-preferences-tracking-topics/notification-level-when-assigned.gjs new file mode 100644 index 00000000..f439ac5c --- /dev/null +++ b/assets/javascripts/discourse/connectors/user-preferences-tracking-topics/notification-level-when-assigned.gjs @@ -0,0 +1,38 @@ +import Component from "@glimmer/component"; +import ComboBox from "select-kit/components/combo-box"; +import { i18n } from "discourse-i18n"; +import { service } from "@ember/service"; + +export default class NotificationLevelWhenAssigned extends Component { + @service siteSettings; + + constructor(owner, args) { + super(...arguments); + if (this.siteSettings.assign_enabled) { + args.outletArgs.customAttrNames.push("notification_level_when_assigned"); + } + } + + get notificationLevelsWhenAssigned() { + // The order matches the "notification level when replying" user preference + return [ + { name: i18n("user.notification_level_when_assigned.watch_topic"), value: "watch_topic" }, + { name: i18n("user.notification_level_when_assigned.track_topic"), value: "track_topic" }, + { name: i18n("user.notification_level_when_assigned.do_nothing"), value: "do_nothing" }, + ]; + } + + +} \ No newline at end of file diff --git a/assets/javascripts/discourse/initializers/extend-for-assigns.js b/assets/javascripts/discourse/initializers/extend-for-assigns.js index 3dce8f09..ce99df0e 100644 --- a/assets/javascripts/discourse/initializers/extend-for-assigns.js +++ b/assets/javascripts/discourse/initializers/extend-for-assigns.js @@ -860,6 +860,8 @@ export default { api.addUserSearchOption("assignableGroups"); + api.addSaveableUserOptionField("notification_level_when_assigned"); + api.addBulkActionButton({ id: "assign-topics", label: "topics.bulk.assign", diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index d0fe9cb9..e083f9bc 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -100,6 +100,11 @@ en: assignable_levels: title: "Who can assign this group" user: + notification_level_when_assigned: + label: "When assigned" + watch_topic: "watch topic" + track_topic: "track topic" + do_nothing: "do nothing" messages: assigned_title: "Assigned (%{count})" assigned: "Assigned" diff --git a/db/migrate/20250115130542_add_notification_level_when_assigned_user_option.rb b/db/migrate/20250115130542_add_notification_level_when_assigned_user_option.rb new file mode 100644 index 00000000..fde424b7 --- /dev/null +++ b/db/migrate/20250115130542_add_notification_level_when_assigned_user_option.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true +class AddNotificationLevelWhenAssignedUserOption < ActiveRecord::Migration[7.2] + def change + add_column :user_options, :notification_level_when_assigned, :integer, null: false, default: 3 # watch topic + end +end diff --git a/lib/assigner.rb b/lib/assigner.rb index a0e1786b..c77de82a 100644 --- a/lib/assigner.rb +++ b/lib/assigner.rb @@ -329,17 +329,19 @@ def assign( publish_assignment(assignment, assign_to, note, status) if assignment.assigned_to_user? - if !TopicUser.exists?( - user_id: assign_to.id, - topic_id: topic.id, - notification_level: TopicUser.notification_levels[:watching], - ) - TopicUser.change( - assign_to.id, - topic.id, - notification_level: TopicUser.notification_levels[:watching], - notifications_reason_id: TopicUser.notification_reasons[:plugin_changed], - ) + if !assign_to.user_option.do_nothing_when_assigned? + notification_level = + if assign_to.user_option.track_topic_when_assigned? + TopicUser.notification_levels[:tracking] + else + TopicUser.notification_levels[:watching] + end + + topic_user = TopicUser.find_by(user_id: assign_to.id, topic:) + if !topic_user || topic_user.notification_level < notification_level + notifications_reason_id = TopicUser.notification_reasons[:plugin_changed] + TopicUser.change(assign_to.id, topic.id, notification_level:, notifications_reason_id:) + end end if SiteSetting.assign_mailer == AssignMailer.levels[:always] || @@ -506,6 +508,7 @@ def add_small_action_post(action_code, assign_to, text) @assigned_by, text, bump: false, + auto_track: false, post_type: SiteSetting.assigns_public ? Post.types[:small_action] : Post.types[:whisper], action_code: action_code, custom_fields: custom_fields, diff --git a/lib/discourse_assign/user_option_extension.rb b/lib/discourse_assign/user_option_extension.rb new file mode 100644 index 00000000..68bbda42 --- /dev/null +++ b/lib/discourse_assign/user_option_extension.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module DiscourseAssign + module UserOptionExtension + extend ActiveSupport::Concern + + prepended do + enum :notification_level_when_assigned, + { do_nothing: 1, track_topic: 2, watch_topic: 3 }, + suffix: "when_assigned" + end + end +end diff --git a/plugin.rb b/plugin.rb index 18620960..8bf76ea4 100644 --- a/plugin.rb +++ b/plugin.rb @@ -22,6 +22,8 @@ module ::DiscourseAssign require_relative "lib/validators/assign_statuses_validator" after_initialize do + UserUpdater::OPTION_ATTR.push(:notification_level_when_assigned) + reloadable_patch do |plugin| Group.prepend(DiscourseAssign::GroupExtension) ListController.prepend(DiscourseAssign::ListControllerExtension) @@ -29,6 +31,15 @@ module ::DiscourseAssign Topic.prepend(DiscourseAssign::TopicExtension) WebHook.prepend(DiscourseAssign::WebHookExtension) Notification.prepend(DiscourseAssign::NotificationExtension) + UserOption.prepend(DiscourseAssign::UserOptionExtension) + end + + add_to_serializer(:user_option, :notification_level_when_assigned) do + object.notification_level_when_assigned + end + + add_to_serializer(:current_user_option, :notification_level_when_assigned) do + object.notification_level_when_assigned end register_group_param(:assignable_level) diff --git a/spec/lib/assigner_spec.rb b/spec/lib/assigner_spec.rb index 63b43d2d..3fa5dc6e 100644 --- a/spec/lib/assigner_spec.rb +++ b/spec/lib/assigner_spec.rb @@ -43,6 +43,46 @@ ) end + describe "when user watchs topic when assigned" do + before { moderator.user_option.watch_topic_when_assigned! } + + it "respects 'when assigned' user preference" do + expect(TopicUser.find_by(user: moderator)).to be(nil) + + assigner.assign(moderator) + + expect(TopicUser.find_by(user: moderator).notification_level).to eq( + TopicUser.notification_levels[:watching], + ) + end + end + + describe "when user tracks topic when assigned" do + before { moderator.user_option.track_topic_when_assigned! } + + it "respects 'when assigned' user preference" do + expect(TopicUser.find_by(user: moderator)).to be(nil) + + assigner.assign(moderator) + + expect(TopicUser.find_by(user: moderator).notification_level).to eq( + TopicUser.notification_levels[:tracking], + ) + end + end + + describe "when user wants to do nothing when assigned" do + before { moderator.user_option.do_nothing_when_assigned! } + + it "respects 'when assigned' user preference" do + expect(TopicUser.find_by(user: moderator)).to be(nil) + + assigner.assign(moderator) + + expect(TopicUser.find_by(user: moderator)).to be(nil) + end + end + it "deletes notification for original assignee when reassigning" do Jobs.run_immediately! diff --git a/spec/system/user_preferences_spec.rb b/spec/system/user_preferences_spec.rb new file mode 100644 index 00000000..595bd95b --- /dev/null +++ b/spec/system/user_preferences_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +describe "Assign | User Preferences", type: :system, js: true do + fab!(:user) + + let(:selector) { "[data-setting-name='user-notification-level-when-assigned'] .combobox" } + + before { sign_in(user) } + + describe "when discourse-assign is disabled" do + before { SiteSetting.assign_enabled = false } + + it "does not show the 'when assigned' tracking user preference" do + visit "/my/preferences/tracking" + + expect(page).not_to have_css(selector) + end + end + + describe "when discourse-assign is enabled" do + before { SiteSetting.assign_enabled = true } + + let(:when_assigned) { PageObjects::Components::SelectKit.new(selector) } + + it "shows the 'when assigned' tracking user preference" do + visit "/my/preferences/tracking" + + expect(when_assigned).to have_selected_value("watch_topic") + end + + it "supports changing the 'when assigned' tracking user preference" do + visit "/my/preferences/tracking" + + when_assigned.expand + when_assigned.select_row_by_value("track_topic") + + page.find("button.save-changes").click + page.refresh + + expect(when_assigned).to have_selected_value("track_topic") + end + end +end From 3d9f3562892427a5335a75995548f3198a900847 Mon Sep 17 00:00:00 2001 From: zogstrip Date: Thu, 16 Jan 2025 16:48:36 +0100 Subject: [PATCH 2/4] LINT: make linters happy --- .../notification-level-when-assigned.gjs | 76 +++++++++++-------- 1 file changed, 46 insertions(+), 30 deletions(-) diff --git a/assets/javascripts/discourse/connectors/user-preferences-tracking-topics/notification-level-when-assigned.gjs b/assets/javascripts/discourse/connectors/user-preferences-tracking-topics/notification-level-when-assigned.gjs index f439ac5c..5438b150 100644 --- a/assets/javascripts/discourse/connectors/user-preferences-tracking-topics/notification-level-when-assigned.gjs +++ b/assets/javascripts/discourse/connectors/user-preferences-tracking-topics/notification-level-when-assigned.gjs @@ -1,38 +1,54 @@ import Component from "@glimmer/component"; -import ComboBox from "select-kit/components/combo-box"; -import { i18n } from "discourse-i18n"; import { service } from "@ember/service"; +import { i18n } from "discourse-i18n"; +import ComboBox from "select-kit/components/combo-box"; export default class NotificationLevelWhenAssigned extends Component { - @service siteSettings; + @service siteSettings; - constructor(owner, args) { - super(...arguments); - if (this.siteSettings.assign_enabled) { - args.outletArgs.customAttrNames.push("notification_level_when_assigned"); - } + constructor(owner, args) { + super(...arguments); + if (this.siteSettings.assign_enabled) { + args.outletArgs.customAttrNames.push("notification_level_when_assigned"); } + } - get notificationLevelsWhenAssigned() { - // The order matches the "notification level when replying" user preference - return [ - { name: i18n("user.notification_level_when_assigned.watch_topic"), value: "watch_topic" }, - { name: i18n("user.notification_level_when_assigned.track_topic"), value: "track_topic" }, - { name: i18n("user.notification_level_when_assigned.do_nothing"), value: "do_nothing" }, - ]; - } + get notificationLevelsWhenAssigned() { + // The order matches the "notification level when replying" user preference + return [ + { + name: i18n("user.notification_level_when_assigned.watch_topic"), + value: "watch_topic", + }, + { + name: i18n("user.notification_level_when_assigned.track_topic"), + value: "track_topic", + }, + { + name: i18n("user.notification_level_when_assigned.do_nothing"), + value: "do_nothing", + }, + ]; + } - -} \ No newline at end of file + +} From 27c3f65bddf1a660a8905acd758ab37c47ea66e1 Mon Sep 17 00:00:00 2001 From: zogstrip Date: Thu, 16 Jan 2025 16:51:09 +0100 Subject: [PATCH 3/4] LINK: make linters happy - take 2 --- .../notification-level-when-assigned.gjs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/assets/javascripts/discourse/connectors/user-preferences-tracking-topics/notification-level-when-assigned.gjs b/assets/javascripts/discourse/connectors/user-preferences-tracking-topics/notification-level-when-assigned.gjs index 5438b150..b6496cc9 100644 --- a/assets/javascripts/discourse/connectors/user-preferences-tracking-topics/notification-level-when-assigned.gjs +++ b/assets/javascripts/discourse/connectors/user-preferences-tracking-topics/notification-level-when-assigned.gjs @@ -40,12 +40,10 @@ export default class NotificationLevelWhenAssigned extends Component { From 519b4732b7ee59550b7a8b1c5622a66a8343034e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Thu, 16 Jan 2025 17:56:55 +0100 Subject: [PATCH 4/4] Update config/locales/client.en.yml Co-authored-by: Keegan George --- config/locales/client.en.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index e083f9bc..9f82fa20 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -102,9 +102,9 @@ en: user: notification_level_when_assigned: label: "When assigned" - watch_topic: "watch topic" - track_topic: "track topic" - do_nothing: "do nothing" + watch_topic: "Watch topic" + track_topic: "Track topic" + do_nothing: "Do nothing" messages: assigned_title: "Assigned (%{count})" assigned: "Assigned"