diff --git a/plugins/automation/config/locales/client.en.yml b/plugins/automation/config/locales/client.en.yml index 5a7308f5831..aad7493d020 100644 --- a/plugins/automation/config/locales/client.en.yml +++ b/plugins/automation/config/locales/client.en.yml @@ -203,6 +203,14 @@ en: valid_trust_levels: label: Valid trust levels description: Will trigger only if post is created by user in these trust levels, defaults to any trust level + topic_tags_changed: + fields: + watching_categories: + label: Watching categories + description: Will trigger only if the topic is in one of these categories + watching_tags: + label: Watching tags + description: Will trigger only if the topic has any of these tags after_post_cook: fields: valid_trust_levels: diff --git a/plugins/automation/config/locales/server.en.yml b/plugins/automation/config/locales/server.en.yml index 98c6cb25577..19694ccfb3a 100644 --- a/plugins/automation/config/locales/server.en.yml +++ b/plugins/automation/config/locales/server.en.yml @@ -50,6 +50,9 @@ en: pm_created: title: Personal message created description: When a valid PM is created the automation will be triggered + topic_tags_changed: + title: Topic tags changed + description: When the tags of a topic are changed the automation will be triggered topic: title: Topic description: The associated script will only be used on the specified topic, this is usefull for scripts doing validation for example diff --git a/plugins/automation/lib/discourse_automation/event_handlers.rb b/plugins/automation/lib/discourse_automation/event_handlers.rb index 8ed26d28691..997a00c3eae 100644 --- a/plugins/automation/lib/discourse_automation/event_handlers.rb +++ b/plugins/automation/lib/discourse_automation/event_handlers.rb @@ -195,6 +195,36 @@ module DiscourseAutomation end end + def self.handle_topic_tags_changed(topic, old_tag_names, new_tag_names) + name = DiscourseAutomation::Triggers::TOPIC_TAGS_CHANGED + + DiscourseAutomation::Automation + .where(trigger: name, enabled: true) + .find_each do |automation| + watching_categories = automation.trigger_field("watching_categories") + if watching_categories["value"] + next if !watching_categories["value"].include?(topic.category_id) + end + + watching_tags = automation.trigger_field("watching_tags") + if watching_tags["value"] + should_skip = false + watching_tags["value"].each do |tag| + should_skip = true if !old_tag_names.empty? && !old_tag_names.include?(tag) + should_skip = true if !new_tag_names.empty? && !new_tag_names.include?(tag) + end + next if should_skip + end + + automation.trigger!( + "kind" => name, + "topic" => topic, + "removed_tags" => old_tag_names, + "added_tags" => new_tag_names, + ) + end + end + def self.handle_after_post_cook(post, cooked) return cooked if post.post_type != Post.types[:regular] || post.post_number > 1 diff --git a/plugins/automation/lib/discourse_automation/triggers.rb b/plugins/automation/lib/discourse_automation/triggers.rb index a1ad649d589..d1a0a1348fd 100644 --- a/plugins/automation/lib/discourse_automation/triggers.rb +++ b/plugins/automation/lib/discourse_automation/triggers.rb @@ -6,6 +6,7 @@ module DiscourseAutomation API_CALL = "api_call" CATEGORY_CREATED_EDITED = "category_created_edited" PM_CREATED = "pm_created" + TOPIC_TAGS_CHANGED = "topic_tags_changed" POINT_IN_TIME = "point_in_time" POST_CREATED_EDITED = "post_created_edited" RECURRING = "recurring" diff --git a/plugins/automation/lib/discourse_automation/triggers/topic_tags_changed.rb b/plugins/automation/lib/discourse_automation/triggers/topic_tags_changed.rb new file mode 100644 index 00000000000..d316573726f --- /dev/null +++ b/plugins/automation/lib/discourse_automation/triggers/topic_tags_changed.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +DiscourseAutomation::Triggerable.add(DiscourseAutomation::Triggers::TOPIC_TAGS_CHANGED) do + field :watching_categories, component: :categories + field :watching_tags, component: :tags +end diff --git a/plugins/automation/plugin.rb b/plugins/automation/plugin.rb index 982d39c1dbd..2ec7249fbdb 100644 --- a/plugins/automation/plugin.rb +++ b/plugins/automation/plugin.rb @@ -72,6 +72,7 @@ after_initialize do lib/discourse_automation/triggers/recurring lib/discourse_automation/triggers/stalled_topic lib/discourse_automation/triggers/stalled_wiki + lib/discourse_automation/triggers/topic_tags_changed lib/discourse_automation/triggers/topic lib/discourse_automation/triggers/user_added_to_group lib/discourse_automation/triggers/user_badge_granted @@ -181,6 +182,14 @@ after_initialize do DiscourseAutomation::EventHandlers.handle_pm_created(topic) if topic.private_message? end + on(:topic_tags_changed) do |topic, tags| + DiscourseAutomation::EventHandlers.handle_topic_tags_changed( + topic, + tags[:old_tag_names], + tags[:new_tag_names], + ) + end + on(:post_created) do |post| DiscourseAutomation::EventHandlers.handle_post_created_edited(post, :create) end diff --git a/plugins/automation/spec/triggers/topic_tags_changed_spec.rb b/plugins/automation/spec/triggers/topic_tags_changed_spec.rb new file mode 100644 index 00000000000..25e2c7190fe --- /dev/null +++ b/plugins/automation/spec/triggers/topic_tags_changed_spec.rb @@ -0,0 +1,136 @@ +# frozen_string_literal: true + +describe DiscourseAutomation::Triggers::TOPIC_TAGS_CHANGED do + before { SiteSetting.discourse_automation_enabled = true } + + fab!(:cool_tag) { Fabricate(:tag) } + fab!(:bad_tag) { Fabricate(:tag) } + fab!(:category) + + fab!(:user) + + fab!(:automation) do + Fabricate(:automation, trigger: DiscourseAutomation::Triggers::TOPIC_TAGS_CHANGED) + end + + before do + SiteSetting.tagging_enabled = true + SiteSetting.create_tag_allowed_groups = Group::AUTO_GROUPS[:everyone] + SiteSetting.tag_topic_allowed_groups = Group::AUTO_GROUPS[:everyone] + end + + context "when watching a cool tag" do + before do + automation.upsert_field!( + "watching_tags", + "tags", + { value: [cool_tag.name] }, + target: "trigger", + ) + automation.reload + end + + it "should fire the trigger if the tag is added" do + topic_0 = Fabricate(:topic, user: user, tags: [], category: category) + + list = + capture_contexts do + DiscourseTagging.tag_topic_by_names(topic_0, Guardian.new(user), [cool_tag.name]) + end + + expect(list.length).to eq(1) + expect(list[0]["kind"]).to eq(DiscourseAutomation::Triggers::TOPIC_TAGS_CHANGED) + end + + it "should fire the trigger if the tag is removed" do + topic_0 = Fabricate(:topic, user: user, tags: [cool_tag], category: category) + + list = + capture_contexts { DiscourseTagging.tag_topic_by_names(topic_0, Guardian.new(user), []) } + + expect(list.length).to eq(1) + expect(list[0]["kind"]).to eq(DiscourseAutomation::Triggers::TOPIC_TAGS_CHANGED) + end + + it "should not fire if the tag is not present" do + topic_0 = Fabricate(:topic, user: user, tags: [], category: category) + + list = + capture_contexts do + DiscourseTagging.tag_topic_by_names(topic_0, Guardian.new(user), [bad_tag.name]) + end + + expect(list.length).to eq(0) + end + end + + context "when watching a category" do + before do + automation.upsert_field!( + "watching_categories", + "categories", + { value: [category.id] }, + target: "trigger", + ) + automation.reload + end + + it "should fire the trigger if the tag is added" do + topic_0 = Fabricate(:topic, user: user, tags: [], category: category) + topic_1 = Fabricate(:topic, user: user, tags: [], category: Fabricate(:category)) + + list = + capture_contexts do + DiscourseTagging.tag_topic_by_names(topic_0, Guardian.new(user), [bad_tag.name]) + DiscourseTagging.tag_topic_by_names(topic_1, Guardian.new(user), [bad_tag.name]) + end + + expect(list.length).to eq(1) + expect(list[0]["kind"]).to eq(DiscourseAutomation::Triggers::TOPIC_TAGS_CHANGED) + end + + it "should fire the trigger if the tag is removed" do + topic_0 = Fabricate(:topic, user: user, tags: [cool_tag], category: category) + + list = + capture_contexts { DiscourseTagging.tag_topic_by_names(topic_0, Guardian.new(user), []) } + expect(list.length).to eq(1) + expect(list[0]["kind"]).to eq(DiscourseAutomation::Triggers::TOPIC_TAGS_CHANGED) + end + + it "should not fire if not the watching category" do + topic_0 = Fabricate(:topic, user: user, tags: [], category: Fabricate(:category)) + + list = + capture_contexts do + DiscourseTagging.tag_topic_by_names(topic_0, Guardian.new(user), [cool_tag.name]) + end + + expect(list.length).to eq(0) + end + end + + context "when without any watching tags or categories" do + it "should fire the trigger if the tag is added" do + topic_0 = Fabricate(:topic, user: user, tags: [], category: category) + + list = + capture_contexts do + DiscourseTagging.tag_topic_by_names(topic_0, Guardian.new(user), [cool_tag.name]) + end + + expect(list.length).to eq(1) + expect(list[0]["kind"]).to eq(DiscourseAutomation::Triggers::TOPIC_TAGS_CHANGED) + end + + it "should fire the trigger if the tag is removed" do + topic_0 = Fabricate(:topic, user: user, tags: [cool_tag], category: category) + + list = + capture_contexts { DiscourseTagging.tag_topic_by_names(topic_0, Guardian.new(user), []) } + + expect(list.length).to eq(1) + expect(list[0]["kind"]).to eq(DiscourseAutomation::Triggers::TOPIC_TAGS_CHANGED) + end + end +end