diff --git a/app/models/discourse_automation/field.rb b/app/models/discourse_automation/field.rb index 92f8fd20..15bf115e 100644 --- a/app/models/discourse_automation/field.rb +++ b/app/models/discourse_automation/field.rb @@ -112,6 +112,11 @@ def metadata_schema "type" => %w[string integer null], }, }, + "category_notification_level" => { + "value" => { + "type" => "integer", + }, + }, "user" => { "value" => { "type" => "string", diff --git a/assets/javascripts/discourse/components/fields/da-category-notification-level-field.js b/assets/javascripts/discourse/components/fields/da-category-notification-level-field.js new file mode 100644 index 00000000..ebf56e3d --- /dev/null +++ b/assets/javascripts/discourse/components/fields/da-category-notification-level-field.js @@ -0,0 +1,3 @@ +import BaseField from "./da-base-field"; + +export default class CategoryNotficiationLevelField extends BaseField {} diff --git a/assets/javascripts/discourse/templates/components/fields/da-category-notification-level-field.hbs b/assets/javascripts/discourse/templates/components/fields/da-category-notification-level-field.hbs new file mode 100644 index 00000000..9a1c634b --- /dev/null +++ b/assets/javascripts/discourse/templates/components/fields/da-category-notification-level-field.hbs @@ -0,0 +1,14 @@ +
+
+ {{fields/da-field-label label=label field=field}} + +
+ {{category-notifications-button + value=field.metadata.value + onChange=(action (mut field.metadata.value)) + }} + + {{fields/da-field-description description=description}} +
+
+
\ No newline at end of file diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 73cdd9ba..c5b6064a 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -141,6 +141,11 @@ en: restricted_category: label: Category description: Optional, allows to limit trigger execution to this category + category_created_edited: + fields: + restricted_category: + label: Parent Category + description: Optional, allows to limit trigger execution to this category pm_created: fields: restricted_user: @@ -189,6 +194,15 @@ en: label: Topic ID post: label: Post content + group_category_notification_default: + fields: + group: + label: Group + notification_level: + label: Notification level + update_existing_members: + label: Update existing members + description: Updates the notification level for existing group members user_global_notice: fields: level: diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 7235c54a..8d1bcd4c 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -18,6 +18,9 @@ en: title: API call description: Making a POST request to trigger an automation doc: The POST request should be made to `/automations/:id/trigger.json` with a global API key. + category_created_edited: + title: Category created/edited + description: When a category is created or edited the automation will be triggered recurring: title: Recurring point_in_time: @@ -110,3 +113,6 @@ en: button_text: Done add_user_to_group_through_custom_field: title: "Add user to group through User Custom Field" + group_category_notification_default: + title: "Group Category Notification Default" + description: "Set the default notification level of a category for members of a group" diff --git a/lib/discourse_automation/event_handlers.rb b/lib/discourse_automation/event_handlers.rb index 269c726b..b926a5e1 100644 --- a/lib/discourse_automation/event_handlers.rb +++ b/lib/discourse_automation/event_handlers.rb @@ -25,6 +25,21 @@ def self.handle_post_created_edited(post, action) end end + def self.handle_category_created_edited(category, action) + name = DiscourseAutomation::Triggerable::CATEGORY_CREATED_EDITED + + DiscourseAutomation::Automation + .where(trigger: name, enabled: true) + .find_each do |automation| + restricted_category = automation.trigger_field("restricted_category") + if restricted_category["value"].present? + next if restricted_category["value"] != category.parent_category_id + end + + automation.trigger!("kind" => name, "action" => action, "category" => category) + end + end + def self.handle_pm_created(topic) return if topic.user_id < 0 diff --git a/lib/discourse_automation/scripts/group_category_notification_default.rb b/lib/discourse_automation/scripts/group_category_notification_default.rb new file mode 100644 index 00000000..22f0e7b3 --- /dev/null +++ b/lib/discourse_automation/scripts/group_category_notification_default.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +DiscourseAutomation::Scriptable::GROUP_CATEGORY_NOTIFICATION_DEFAULT = + "group_category_notification_default" + +DiscourseAutomation::Scriptable.add( + DiscourseAutomation::Scriptable::GROUP_CATEGORY_NOTIFICATION_DEFAULT, +) do + version 1 + + field :group, component: :group + field :notification_level, component: :category_notification_level + field :update_existing_members, component: :boolean + + triggerables %i[category_created_edited] + + script do |context, fields| + category_id = context["category"].id + group_id = fields.dig("group", "value") + notification_level = fields.dig("notification_level", "value") + + unless group = Group.find_by(id: group_id) + Rails.logger.warn "[discourse-automation] Couldn’t find group with id #{group_id}" + next + end + + GroupCategoryNotificationDefault + .find_or_initialize_by(group_id: group_id, category_id: category_id) + .tap do |gc| + gc.notification_level = notification_level + gc.save! + end + + if fields.dig("update_existing_members", "value") + group + .users + .select(:id, :user_id) + .find_in_batches do |batch| + user_ids = batch.pluck(:user_id) + + category_users = [] + existing_users = + CategoryUser.where(category_id: category_id, user_id: user_ids).where( + "notification_level IS NOT NULL", + ) + skip_user_ids = existing_users.pluck(:user_id) + + batch.each do |group_user| + next if skip_user_ids.include?(group_user.user_id) + category_users << { + category_id: category_id, + user_id: group_user.user_id, + notification_level: notification_level, + } + end + + next if category_users.blank? + + CategoryUser.insert_all!(category_users) + end + end + end +end diff --git a/lib/discourse_automation/triggers/category_created_edited.rb b/lib/discourse_automation/triggers/category_created_edited.rb new file mode 100644 index 00000000..f49a5fee --- /dev/null +++ b/lib/discourse_automation/triggers/category_created_edited.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +DiscourseAutomation::Triggerable::CATEGORY_CREATED_EDITED = "category_created_edited" + +DiscourseAutomation::Triggerable.add(DiscourseAutomation::Triggerable::CATEGORY_CREATED_EDITED) do + field :restricted_category, component: :category +end diff --git a/plugin.rb b/plugin.rb index 97ed719b..c2246c4d 100644 --- a/plugin.rb +++ b/plugin.rb @@ -45,6 +45,7 @@ module ::DiscourseAutomation lib/discourse_automation/scripts/close_topic lib/discourse_automation/scripts/flag_post_on_words lib/discourse_automation/scripts/gift_exchange + lib/discourse_automation/scripts/group_category_notification_default lib/discourse_automation/scripts/pin_topic lib/discourse_automation/scripts/post lib/discourse_automation/scripts/send_pms @@ -54,6 +55,7 @@ module ::DiscourseAutomation lib/discourse_automation/scripts/zapier_webhook lib/discourse_automation/triggers/after_post_cook lib/discourse_automation/triggers/api_call + lib/discourse_automation/triggers/category_created_edited lib/discourse_automation/triggers/pm_created lib/discourse_automation/triggers/point_in_time lib/discourse_automation/triggers/post_created_edited @@ -162,6 +164,14 @@ module ::DiscourseAutomation DiscourseAutomation::EventHandlers.handle_post_created_edited(post, :edit) end + on(:category_created) do |category| + DiscourseAutomation::EventHandlers.handle_category_created_edited(category, :create) + end + + on(:category_edited) do |category| + DiscourseAutomation::EventHandlers.handle_category_created_edited(category, :edit) + end + Plugin::Filter.register(:after_post_cook) do |post, cooked| DiscourseAutomation::EventHandlers.handle_after_post_cook(post, cooked) end diff --git a/spec/scripts/group_category_notification_default_spec.rb b/spec/scripts/group_category_notification_default_spec.rb new file mode 100644 index 00000000..dd6e4e95 --- /dev/null +++ b/spec/scripts/group_category_notification_default_spec.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +require_relative "../discourse_automation_helper" + +describe "GroupCategoryNotificationDefault" do + fab!(:category) { Fabricate(:category) } + fab!(:group) { Fabricate(:group) } + + before { SiteSetting.discourse_automation_enabled = true } + + context "when using category_created_edited trigger" do + fab!(:automation) do + Fabricate( + :automation, + script: DiscourseAutomation::Scriptable::GROUP_CATEGORY_NOTIFICATION_DEFAULT, + trigger: DiscourseAutomation::Triggerable::CATEGORY_CREATED_EDITED, + ) + end + + before do + automation.upsert_field!( + "restricted_category", + "category", + { value: category.id }, + target: "trigger", + ) + automation.upsert_field!("group", "group", { value: group.id }, target: "script") + automation.upsert_field!( + "notification_level", + "category_notification_level", + { value: 4 }, + target: "script", + ) + end + + context "when category is allowed" do + it "creates a GroupCategoryNotificationDefault record" do + subcategory = nil + expect { subcategory = Fabricate(:category, parent_category_id: category.id) }.to change { + GroupCategoryNotificationDefault.count + }.by(1) + + record = GroupCategoryNotificationDefault.last + expect(record.category_id).to eq(subcategory.id) + expect(record.group_id).to eq(group.id) + expect(record.notification_level).to eq(4) + end + + it "updates category notification level for existing members" do + automation.upsert_field!( + "update_existing_members", + "boolean", + { value: true }, + target: "script", + ) + user = Fabricate(:user) + group.add(user) + subcategory = nil + + expect { subcategory = Fabricate(:category, parent_category_id: category.id) }.to change { + CategoryUser.count + }.by(1) + + record = CategoryUser.last + expect(record.category_id).to eq(subcategory.id) + expect(record.user_id).to eq(user.id) + expect(record.notification_level).to eq(4) + end + end + end +end diff --git a/spec/triggers/category_created_edited_spec.rb b/spec/triggers/category_created_edited_spec.rb new file mode 100644 index 00000000..d7e76581 --- /dev/null +++ b/spec/triggers/category_created_edited_spec.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require_relative "../discourse_automation_helper" + +describe "CategoryCreatedEdited" do + before { SiteSetting.discourse_automation_enabled = true } + + fab!(:automation) do + Fabricate(:automation, trigger: DiscourseAutomation::Triggerable::CATEGORY_CREATED_EDITED) + end + + context "when editing/creating a post" do + it "fires the trigger" do + category = nil + + list = capture_contexts { category = Fabricate(:category) } + + expect(list.length).to eq(1) + expect(list[0]["kind"]).to eq("category_created_edited") + expect(list[0]["action"].to_s).to eq("create") + end + + context "when category is restricted" do + let(:parent_category_id) { Category.first.id } + before do + automation.upsert_field!( + "restricted_category", + "category", + { value: parent_category_id }, + target: "trigger", + ) + end + + context "when category is allowed" do + it "fires the trigger" do + list = capture_contexts { Fabricate(:category, parent_category_id: parent_category_id) } + + expect(list.length).to eq(1) + expect(list[0]["kind"]).to eq("category_created_edited") + end + end + + context "when category is not allowed" do + it "doesn’t fire the trigger" do + list = capture_contexts { Fabricate(:category) } + + expect(list).to be_blank + end + end + end + end +end