diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 220cd1b7..af8440cb 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -281,3 +281,12 @@ en: label: URL channel: label: Channel + + send_chat_integration_message: + title: Send Chat-Integration message + fields: + channel_name: + label: Channel name + description: "You can find the channel name in the Chat Integration settings" + provider: + label: Provider diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 29d36916..157810ee 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -125,6 +125,8 @@ en: scriptables: send_slack_message: title: Send Slack message + send_chat_integration_message: + title: Send Chat-Integration message chat_integration: all_categories: "(all categories)" @@ -134,6 +136,12 @@ en: group_mention_template: "mentions of: @%{name}" group_message_template: "messages to: @%{name}" + + topic_tag_changed: + added_and_removed: "Added %{added} and removed %{removed}" + added: "Added %{added}" + removed: "Removed %{removed}" + provider: ####################################### diff --git a/db/migrate/20240808175526_migrate_tag_added_from_filter_to_automation.rb b/db/migrate/20240808175526_migrate_tag_added_from_filter_to_automation.rb index 72a00d6d..658e4d8d 100644 --- a/db/migrate/20240808175526_migrate_tag_added_from_filter_to_automation.rb +++ b/db/migrate/20240808175526_migrate_tag_added_from_filter_to_automation.rb @@ -1,92 +1,96 @@ # frozen_string_literal: true + +# The next migration file is a migration that migrates the tag_added filter to an automation. +# this one uses ActiveRecord which is not recommended for migrations. + class MigrateTagAddedFromFilterToAutomation < ActiveRecord::Migration[7.1] def up - if defined?(DiscourseAutomation) && - DiscourseChatIntegration::Channel.with_provider("slack").exists? - begin - DiscourseChatIntegration::Rule - .where("value::json->>'filter'=?", "tag_added") - .each do |rule| - channel_id = rule.value["channel_id"] - channel_name = - DiscourseChatIntegration::Channel.find(channel_id).value["data"]["identifier"] # it _must_ have a channel_id + # if defined?(DiscourseAutomation) && + # DiscourseChatIntegration::Channel.with_provider("slack").exists? + # begin + # DiscourseChatIntegration::Rule + # .where("value::json->>'filter'=?", "tag_added") + # .each do |rule| + # channel_id = rule.value["channel_id"] + # channel_name = + # DiscourseChatIntegration::Channel.find(channel_id).value["data"]["identifier"] # it _must_ have a channel_id - category_id = rule.value["category_id"] - tags = rule.value["tags"] + # category_id = rule.value["category_id"] + # tags = rule.value["tags"] - automation = - DiscourseAutomation::Automation.new( - script: "send_slack_message", - trigger: "topic_tags_changed", - name: "When tags change in topic", - enabled: true, - last_updated_by_id: Discourse.system_user.id, - ) + # automation = + # DiscourseAutomation::Automation.new( + # script: "send_slack_message", + # trigger: "topic_tags_changed", + # name: "When tags change in topic", + # enabled: true, + # last_updated_by_id: Discourse.system_user.id, + # ) - automation.save! + # automation.save! - # Triggers: - # Watching categories + # # Triggers: + # # Watching categories - metadata = (category_id ? { "value" => [category_id] } : {}) + # metadata = (category_id ? { "value" => [category_id] } : {}) - automation.upsert_field!( - "watching_categories", - "categories", - metadata, - target: "trigger", - ) + # automation.upsert_field!( + # "watching_categories", + # "categories", + # metadata, + # target: "trigger", + # ) - # Watching tags + # # Watching tags - metadata = (tags ? { "value" => tags } : {}) + # metadata = (tags ? { "value" => tags } : {}) - automation.upsert_field!("watching_tags", "tags", metadata, target: "trigger") + # automation.upsert_field!("watching_tags", "tags", metadata, target: "trigger") - # Script options: - # Message - automation.upsert_field!( - "message", - "message", - { "value" => "${ADDED_AND_REMOVED}" }, - target: "script", - ) + # # Script options: + # # Message + # automation.upsert_field!( + # "message", + # "message", + # { "value" => "${ADDED_AND_REMOVED}" }, + # target: "script", + # ) - # URL - automation.upsert_field!( - "url", - "text", - { "value" => Discourse.current_hostname }, - target: "script", - ) + # # URL + # automation.upsert_field!( + # "url", + # "text", + # { "value" => Discourse.current_hostname }, + # target: "script", + # ) - # Channel - automation.upsert_field!( - "channel", - "text", - { "value" => channel_name }, - target: "script", - ) - end - rescue StandardError - Rails.logger.warn("Failed to migrate tag_added rules to automations") - end - end + # # Channel + # automation.upsert_field!( + # "channel", + # "text", + # { "value" => channel_name }, + # target: "script", + # ) + # end + # rescue StandardError + # Rails.logger.warn("Failed to migrate tag_added rules to automations") + # end + # end end def down - if defined?(DiscourseAutomation) && - DiscourseChatIntegration::Channel.with_provider("slack").exists? - DiscourseAutomation::Automation - .where(script: "send_slack_message", trigger: "topic_tags_changed") - .each do |automation| - # if is the same name as created and message is the same - if automation.name == "When tags change in topic" && - automation.fields.where(name: "message").first.metadata["value"] == - "${ADDED_AND_REMOVED}" - automation.destroy! - end - end - end + # if defined?(DiscourseAutomation) && + # DiscourseChatIntegration::Channel.with_provider("slack").exists? + # DiscourseAutomation::Automation + # .where(script: "send_slack_message", trigger: "topic_tags_changed") + # .each do |automation| + # # if is the same name as created and message is the same + # if automation.name == "When tags change in topic" && + # automation.fields.where(name: "message").first.metadata["value"] == + # "${ADDED_AND_REMOVED}" + # automation.destroy! + # end + # end + # end end end diff --git a/db/migrate/20240903184807_migrate_tag_added_filter_to_all_providers.rb b/db/migrate/20240903184807_migrate_tag_added_filter_to_all_providers.rb new file mode 100644 index 00000000..e8b1eb33 --- /dev/null +++ b/db/migrate/20240903184807_migrate_tag_added_filter_to_all_providers.rb @@ -0,0 +1,153 @@ +# frozen_string_literal: true +class MigrateTagAddedFilterToAllProviders < ActiveRecord::Migration[7.1] + def up + if defined?(DiscourseAutomation) + begin + slack_usage_rows = DB.query <<~SQL + SELECT plugin_store_rows.* FROM plugin_store_rows + WHERE plugin_store_rows.type_name = 'JSON' + AND plugin_store_rows.plugin_name = 'discourse-chat-integration' + AND (key LIKE 'channel:%') + AND (value::json->>'provider'='slack') + SQL + + old_migration_delete = <<~SQL + DELETE FROM discourse_automation_automations + WHERE id IN ( + SELECT a.id + FROM discourse_automation_automations a + JOIN discourse_automation_fields f ON f.automation_id = a.id + WHERE a.script = 'send_slack_message' + AND a.trigger = 'topic_tags_changed' + AND a.name = 'When tags change in topic' + AND f.name = 'message' + AND f.metadata->>'value' = '${ADDED_AND_REMOVED}' + ) + SQL + # Trash old migration + DB.exec old_migration_delete if slack_usage_rows.count > 0 + + rules_with_tag_added = <<~SQL + SELECT value + FROM plugin_store_rows + WHERE plugin_name = 'discourse-chat-integration' + AND key LIKE 'rule:%' + AND value::json->>'filter' = 'tag_added' + SQL + + channel_query = <<~SQL + SELECT * + FROM plugin_store_rows + WHERE type_name = 'JSON' + AND plugin_name = 'discourse-chat-integration' + AND key LIKE 'channel:%' + AND id = :channel_id + LIMIT 1 + SQL + + automation_creation = <<~SQL + INSERT INTO discourse_automation_automations (script, trigger, name, enabled, last_updated_by_id, created_at, updated_at) + VALUES ('send_chat_integration_message', 'topic_tags_changed', 'When tags change in topic', true, -1, NOW(), NOW()) + RETURNING id + SQL + + create_automation_field = <<~SQL + INSERT INTO discourse_automation_fields (automation_id, name, component, metadata, target, created_at, updated_at) + VALUES (:automation_id, :name, :component, :metadata, :target, NOW(), NOW()) + SQL + + provider_identifier_map = { + "groupme" => "groupme_instance_name", + "discord" => "name", + "guilded" => "name", + "mattermost" => "identifier", + "matrix" => "name", + "teams" => "name", + "zulip" => "stream", + "powerautomate" => "name", + "rocketchat" => "identifier", + "gitter" => "name", + "telegram" => "name", + "flowdock" => "flow_token", + "google" => "name", + "webex" => "name", + "slack" => "identifier", + } + + DB + .query(rules_with_tag_added) + .each do |row| + rule = JSON.parse(row.value).with_indifferent_access + + channel = + JSON.parse( + DB.query(channel_query, channel_id: rule[:channel_id]).first.value, + ).with_indifferent_access + + provider_name = channel[:provider] + channel_name = channel[:data][provider_identifier_map[provider_name]] + + category_id = rule[:category_id] + tags = rule[:tags] + + automation_id = DB.query(automation_creation).first.id + + # Triggers: + # Watching categories + metadata = (category_id ? { "value" => [category_id] } : {}).to_json + DB.exec( + create_automation_field, + automation_id: automation_id, + name: "watching_categories", + component: "categories", + metadata: metadata, + target: "trigger", + ) + + # Watching tags + metadata = (tags.present? ? { "value" => tags } : {}).to_json + DB.exec( + create_automation_field, + automation_id: automation_id, + name: "watching_tags", + component: "tags", + metadata: metadata, + target: "trigger", + ) + + # Script options: + # Provider + DB.exec( + create_automation_field, + automation_id: automation_id, + name: "provider", + component: "choices", + metadata: { "value" => provider_name }.to_json, + target: "script", + ) + + # Channel name + DB.exec( + create_automation_field, + automation_id: automation_id, + name: "channel_name", + component: "text", + metadata: { "value" => channel_name }.to_json, + target: "script", + ) + end + rescue StandardError + puts "Error migrating tag_added filters to all providers" + end + end + end + + def down + DB.exec <<~SQL if defined?(DiscourseAutomation) + DELETE FROM discourse_automation_automations + WHERE script = 'send_chat_integration_message' + AND trigger = 'topic_tags_changed' + AND name = 'When tags change in topic' + SQL + end +end diff --git a/lib/discourse_chat_integration/chat_integration_reference_post.rb b/lib/discourse_chat_integration/chat_integration_reference_post.rb new file mode 100644 index 00000000..57bad510 --- /dev/null +++ b/lib/discourse_chat_integration/chat_integration_reference_post.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +module DiscourseChatIntegration + class ChatIntegrationReferencePost + def initialize(user:, topic:, kind:, raw: nil, context: {}) + @user = user + @topic = topic + @kind = kind + @raw = raw if raw.present? + @context = context + @created_at = Time.current + end + + def id + @topic.posts.empty? ? @topic.id : @topic.posts.first.id + end + + def user + @user + end + + def topic + @topic + end + + def full_url + @topic.posts.empty? ? @topic.full_url : @topic.posts.first.full_url + end + + def excerpt(maxlength = nil, options = {}) + cooked = PrettyText.cook(raw, { user_id: user.id }) + maxlength ||= SiteSetting.post_excerpt_maxlength + PrettyText.excerpt(cooked, maxlength, options) + end + + def is_first_post? + topic.try(:highest_post_number) == 0 + end + + def created_at + @created_at + end + + def raw + if @raw.nil? && @kind == DiscourseAutomation::Triggers::TOPIC_TAGS_CHANGED + tag_list_to_raw = ->(tag_list) do + tag_list.sort.map { |tag_name| "##{tag_name}" }.join(", ") + end + + added_tags = @context["added_tags"] + removed_tags = @context["removed_tags"] + + @raw = + if added_tags.present? && removed_tags.present? + I18n.t( + "chat_integration.topic_tag_changed.added_and_removed", + added: tag_list_to_raw.call(added_tags), + removed: tag_list_to_raw.call(removed_tags), + ) + elsif added_tags.present? + I18n.t( + "chat_integration.topic_tag_changed.added", + added: tag_list_to_raw.call(added_tags), + ) + elsif removed_tags.present? + I18n.t( + "chat_integration.topic_tag_changed.removed", + removed: tag_list_to_raw.call(removed_tags), + ) + end + end + @raw + end + end +end diff --git a/lib/discourse_chat_integration/provider/discord/discord_provider.rb b/lib/discourse_chat_integration/provider/discord/discord_provider.rb index 85b9532e..bdfab282 100644 --- a/lib/discourse_chat_integration/provider/discord/discord_provider.rb +++ b/lib/discourse_chat_integration/provider/discord/discord_provider.rb @@ -5,7 +5,7 @@ module Provider module DiscordProvider PROVIDER_NAME = "discord".freeze PROVIDER_ENABLED_SETTING = :chat_integration_discord_enabled - + CHANNEL_IDENTIFIER_KEY = "name".freeze CHANNEL_PARAMETERS = [ { key: "name", regex: '^\S+' }, { @@ -94,6 +94,13 @@ def self.trigger_notification(post, channel, rule) ) end end + + def self.get_channel_by_name(name) + DiscourseChatIntegration::Channel + .with_provider(PROVIDER_NAME) + .with_data_value(CHANNEL_IDENTIFIER_KEY, name) + .first + end end end end diff --git a/lib/discourse_chat_integration/provider/flowdock/flowdock_provider.rb b/lib/discourse_chat_integration/provider/flowdock/flowdock_provider.rb index c1386b52..59c12f26 100644 --- a/lib/discourse_chat_integration/provider/flowdock/flowdock_provider.rb +++ b/lib/discourse_chat_integration/provider/flowdock/flowdock_provider.rb @@ -3,6 +3,7 @@ module DiscourseChatIntegration::Provider::FlowdockProvider PROVIDER_NAME = "flowdock".freeze PROVIDER_ENABLED_SETTING = :chat_integration_flowdock_enabled + CHANNEL_IDENTIFIER_KEY = "flow_token".freeze # this is really weird but is the only way to identify a channel in this provider CHANNEL_PARAMETERS = [{ key: "flow_token", regex: '^\S+', unique: true, hidden: true }] def self.send_message(url, message) @@ -60,4 +61,11 @@ def self.trigger_notification(post, channel, rule) } end end + + def self.get_channel_by_name(name) + DiscourseChatIntegration::Channel + .with_provider(PROVIDER_NAME) + .with_data_value(CHANNEL_IDENTIFIER_KEY, name) + .first + end end diff --git a/lib/discourse_chat_integration/provider/gitter/gitter_provider.rb b/lib/discourse_chat_integration/provider/gitter/gitter_provider.rb index 04da4a53..430377cc 100644 --- a/lib/discourse_chat_integration/provider/gitter/gitter_provider.rb +++ b/lib/discourse_chat_integration/provider/gitter/gitter_provider.rb @@ -5,6 +5,7 @@ module Provider module GitterProvider PROVIDER_NAME = "gitter".freeze PROVIDER_ENABLED_SETTING = :chat_integration_gitter_enabled + CHANNEL_IDENTIFIER_KEY = "name".freeze CHANNEL_PARAMETERS = [ { key: "name", regex: '^\S+$', unique: true }, { @@ -43,6 +44,13 @@ def self.gitter_message(post) "[__#{display_name}__ - #{topic.title} - #{category_name}](#{post.full_url})" end + + def self.get_channel_by_name(name) + DiscourseChatIntegration::Channel + .with_provider(PROVIDER_NAME) + .with_data_value(CHANNEL_IDENTIFIER_KEY, name) + .first + end end end end diff --git a/lib/discourse_chat_integration/provider/google/google_provider.rb b/lib/discourse_chat_integration/provider/google/google_provider.rb index ec223b8a..fbe54dc8 100644 --- a/lib/discourse_chat_integration/provider/google/google_provider.rb +++ b/lib/discourse_chat_integration/provider/google/google_provider.rb @@ -5,6 +5,7 @@ module Provider module GoogleProvider PROVIDER_NAME = "google".freeze PROVIDER_ENABLED_SETTING = :chat_integration_google_enabled + CHANNEL_IDENTIFIER_KEY = "name".freeze CHANNEL_PARAMETERS = [ { key: "name", regex: '^\S+$', unique: true }, { @@ -107,6 +108,13 @@ def self.get_message(post) ], } end + + def self.get_channel_by_name(name) + DiscourseChatIntegration::Channel + .with_provider(PROVIDER_NAME) + .with_data_value(CHANNEL_IDENTIFIER_KEY, name) + .first + end end end end diff --git a/lib/discourse_chat_integration/provider/groupme/groupme_provider.rb b/lib/discourse_chat_integration/provider/groupme/groupme_provider.rb index 797afb05..c87ed17b 100644 --- a/lib/discourse_chat_integration/provider/groupme/groupme_provider.rb +++ b/lib/discourse_chat_integration/provider/groupme/groupme_provider.rb @@ -2,6 +2,7 @@ module DiscourseChatIntegration::Provider::GroupmeProvider PROVIDER_NAME = "groupme".freeze PROVIDER_ENABLED_SETTING = :chat_integration_groupme_enabled + CHANNEL_IDENTIFIER_KEY = "groupme_instance_name".freeze CHANNEL_PARAMETERS = [{ key: "groupme_instance_name", regex: '[\s\S]*', unique: true }] def self.generate_groupme_message(post) @@ -84,4 +85,11 @@ def self.trigger_notification(post, channel, rule) data_package = generate_groupme_message(post) self.send_via_webhook(data_package, channel) end + + def self.get_channel_by_name(name) + DiscourseChatIntegration::Channel + .with_provider(PROVIDER_NAME) + .with_data_value(CHANNEL_IDENTIFIER_KEY, name) + .first + end end diff --git a/lib/discourse_chat_integration/provider/guilded/guilded_provider.rb b/lib/discourse_chat_integration/provider/guilded/guilded_provider.rb index d50ae0c7..8292517f 100644 --- a/lib/discourse_chat_integration/provider/guilded/guilded_provider.rb +++ b/lib/discourse_chat_integration/provider/guilded/guilded_provider.rb @@ -5,7 +5,7 @@ module Provider module GuildedProvider PROVIDER_NAME = "guilded".freeze PROVIDER_ENABLED_SETTING = :chat_integration_guilded_enabled - + CHANNEL_IDENTIFIER_KEY = "name".freeze CHANNEL_PARAMETERS = [ { key: "name", regex: '^\S+' }, { @@ -92,6 +92,13 @@ def self.ensure_protocol(url) return url if !url.start_with?("//") "http:#{url}" end + + def self.get_channel_by_name(name) + DiscourseChatIntegration::Channel + .with_provider(PROVIDER_NAME) + .with_data_value(CHANNEL_IDENTIFIER_KEY, name) + .first + end end end end diff --git a/lib/discourse_chat_integration/provider/matrix/matrix_provider.rb b/lib/discourse_chat_integration/provider/matrix/matrix_provider.rb index 612d81b7..5df9f600 100644 --- a/lib/discourse_chat_integration/provider/matrix/matrix_provider.rb +++ b/lib/discourse_chat_integration/provider/matrix/matrix_provider.rb @@ -5,6 +5,7 @@ module Provider module MatrixProvider PROVIDER_NAME = "matrix".freeze PROVIDER_ENABLED_SETTING = :chat_integration_matrix_enabled + CHANNEL_IDENTIFIER_KEY = "name".freeze CHANNEL_PARAMETERS = [ { key: "name", regex: '^\S+' }, { key: "room_id", regex: '^\!\S+:\S+$', unique: true, hidden: true }, @@ -88,6 +89,13 @@ def self.trigger_notification(post, channel, rule) end end end + + def self.get_channel_by_name(name) + DiscourseChatIntegration::Channel + .with_provider(PROVIDER_NAME) + .with_data_value(CHANNEL_IDENTIFIER_KEY, name) + .first + end end end end diff --git a/lib/discourse_chat_integration/provider/mattermost/mattermost_provider.rb b/lib/discourse_chat_integration/provider/mattermost/mattermost_provider.rb index e75aa913..b378c339 100644 --- a/lib/discourse_chat_integration/provider/mattermost/mattermost_provider.rb +++ b/lib/discourse_chat_integration/provider/mattermost/mattermost_provider.rb @@ -5,6 +5,7 @@ module Provider module MattermostProvider PROVIDER_NAME = "mattermost".freeze PROVIDER_ENABLED_SETTING = :chat_integration_mattermost_enabled + CHANNEL_IDENTIFIER_KEY = "identifier".freeze CHANNEL_PARAMETERS = [{ key: "identifier", regex: '^[@#]\S*$', unique: true }] def self.send_via_webhook(message) @@ -93,6 +94,13 @@ def self.trigger_notification(post, channel, rule) self.send_via_webhook(message) end + + def self.get_channel_by_name(name) + DiscourseChatIntegration::Channel + .with_provider(PROVIDER_NAME) + .with_data_value(CHANNEL_IDENTIFIER_KEY, name) + .first + end end end end diff --git a/lib/discourse_chat_integration/provider/powerautomate/powerautomate_provider.rb b/lib/discourse_chat_integration/provider/powerautomate/powerautomate_provider.rb index 9118c7ef..d66a344e 100644 --- a/lib/discourse_chat_integration/provider/powerautomate/powerautomate_provider.rb +++ b/lib/discourse_chat_integration/provider/powerautomate/powerautomate_provider.rb @@ -3,6 +3,7 @@ module DiscourseChatIntegration::Provider::PowerAutomateProvider PROVIDER_NAME = "powerautomate" PROVIDER_ENABLED_SETTING = :chat_integration_powerautomate_enabled + CHANNEL_IDENTIFIER_KEY = "name".freeze CHANNEL_PARAMETERS = [ { key: "name", regex: '^\S+$', unique: true }, { key: "webhook_url", regex: '^https:\/\/\S+$', unique: true, hidden: true }, @@ -130,4 +131,11 @@ def self.get_message(post) message end + + def self.get_channel_by_name(name) + DiscourseChatIntegration::Channel + .with_provider(PROVIDER_NAME) + .with_data_value(CHANNEL_IDENTIFIER_KEY, name) + .first + end end diff --git a/lib/discourse_chat_integration/provider/rocketchat/rocketchat_provider.rb b/lib/discourse_chat_integration/provider/rocketchat/rocketchat_provider.rb index 79ba6652..2a9ebd89 100644 --- a/lib/discourse_chat_integration/provider/rocketchat/rocketchat_provider.rb +++ b/lib/discourse_chat_integration/provider/rocketchat/rocketchat_provider.rb @@ -4,7 +4,7 @@ module DiscourseChatIntegration::Provider::RocketchatProvider PROVIDER_NAME = "rocketchat".freeze PROVIDER_ENABLED_SETTING = :chat_integration_rocketchat_enabled - + CHANNEL_IDENTIFIER_KEY = "identifier".freeze CHANNEL_PARAMETERS = [{ key: "identifier", regex: '^[@#]\S*$', unique: true }] def self.rocketchat_message(post, channel) @@ -82,4 +82,11 @@ def self.trigger_notification(post, channel, rule) self.send_via_webhook(message) end + + def self.get_channel_by_name(name) + DiscourseChatIntegration::Channel + .with_provider(PROVIDER_NAME) + .with_data_value(CHANNEL_IDENTIFIER_KEY, name) + .first + end end diff --git a/lib/discourse_chat_integration/provider/slack/slack_provider.rb b/lib/discourse_chat_integration/provider/slack/slack_provider.rb index d7e4c011..6c8ab9b5 100644 --- a/lib/discourse_chat_integration/provider/slack/slack_provider.rb +++ b/lib/discourse_chat_integration/provider/slack/slack_provider.rb @@ -12,7 +12,7 @@ module DiscourseChatIntegration::Provider::SlackProvider THREAD_LEGACY = "thread" PROVIDER_ENABLED_SETTING = :chat_integration_slack_enabled - + CHANNEL_IDENTIFIER_KEY = "identifier".freeze CHANNEL_PARAMETERS = [{ key: "identifier", regex: '^[@#]?\S*$', unique: true }] require_dependency "topic" @@ -339,6 +339,13 @@ def self.replace_placehoders(content, context) def self.create_tag_list(tag_list) tag_list.map { |tag_name| "<#{Tag.find_by_name(tag_name).full_url}|#{tag_name}>" }.join(", ") end + + def self.get_channel_by_name(name) + DiscourseChatIntegration::Channel + .with_provider(PROVIDER_NAME) + .with_data_value(CHANNEL_IDENTIFIER_KEY, name) + .first + end end require_relative "slack_message_formatter" diff --git a/lib/discourse_chat_integration/provider/teams/teams_provider.rb b/lib/discourse_chat_integration/provider/teams/teams_provider.rb index 5709376e..cac70da7 100644 --- a/lib/discourse_chat_integration/provider/teams/teams_provider.rb +++ b/lib/discourse_chat_integration/provider/teams/teams_provider.rb @@ -3,6 +3,7 @@ module DiscourseChatIntegration::Provider::TeamsProvider PROVIDER_NAME = "teams".freeze PROVIDER_ENABLED_SETTING = :chat_integration_teams_enabled + CHANNEL_IDENTIFIER_KEY = "name".freeze CHANNEL_PARAMETERS = [ { key: "name", regex: '^\S+$', unique: true }, { key: "webhook_url", regex: '^https:\/\/\S+$', unique: true, hidden: true }, @@ -82,4 +83,11 @@ def self.get_message(post) message end + + def self.get_channel_by_name(name) + DiscourseChatIntegration::Channel + .with_provider(PROVIDER_NAME) + .with_data_value(CHANNEL_IDENTIFIER_KEY, name) + .first + end end diff --git a/lib/discourse_chat_integration/provider/telegram/telegram_provider.rb b/lib/discourse_chat_integration/provider/telegram/telegram_provider.rb index 6bd12b37..46e69c61 100644 --- a/lib/discourse_chat_integration/provider/telegram/telegram_provider.rb +++ b/lib/discourse_chat_integration/provider/telegram/telegram_provider.rb @@ -5,6 +5,7 @@ module Provider module TelegramProvider PROVIDER_NAME = "telegram".freeze PROVIDER_ENABLED_SETTING = :chat_integration_telegram_enabled + CHANNEL_IDENTIFIER_KEY = "name".freeze CHANNEL_PARAMETERS = [ { key: "name", regex: '^\S+' }, { key: "chat_id", regex: '^(-?[0-9]+|@\S+)$', unique: true }, @@ -110,6 +111,13 @@ def self.trigger_notification(post, channel, rule) } end end + + def self.get_channel_by_name(name) + DiscourseChatIntegration::Channel + .with_provider(PROVIDER_NAME) + .with_data_value(CHANNEL_IDENTIFIER_KEY, name) + .first + end end end end diff --git a/lib/discourse_chat_integration/provider/webex/webex_provider.rb b/lib/discourse_chat_integration/provider/webex/webex_provider.rb index f64d35aa..94ace863 100644 --- a/lib/discourse_chat_integration/provider/webex/webex_provider.rb +++ b/lib/discourse_chat_integration/provider/webex/webex_provider.rb @@ -3,6 +3,7 @@ module DiscourseChatIntegration::Provider::WebexProvider PROVIDER_NAME = "webex".freeze PROVIDER_ENABLED_SETTING = :chat_integration_webex_enabled + CHANNEL_IDENTIFIER_KEY = "name".freeze CHANNEL_PARAMETERS = [ { key: "name", regex: '^\S+$', unique: true }, { @@ -71,4 +72,11 @@ def self.get_message(post) { markdown: markdown } end + + def self.get_channel_by_name(name) + DiscourseChatIntegration::Channel + .with_provider(PROVIDER_NAME) + .with_data_value(CHANNEL_IDENTIFIER_KEY, name) + .first + end end diff --git a/lib/discourse_chat_integration/provider/zulip/zulip_provider.rb b/lib/discourse_chat_integration/provider/zulip/zulip_provider.rb index 36e400ee..9e33cc0d 100644 --- a/lib/discourse_chat_integration/provider/zulip/zulip_provider.rb +++ b/lib/discourse_chat_integration/provider/zulip/zulip_provider.rb @@ -5,6 +5,7 @@ module Provider module ZulipProvider PROVIDER_NAME = "zulip".freeze PROVIDER_ENABLED_SETTING = :chat_integration_zulip_enabled + CHANNEL_IDENTIFIER_KEY = "stream".freeze CHANNEL_PARAMETERS = [ { key: "stream", unique: true, regex: '^\S+' }, { key: "subject", unique: true, regex: '^\S+' }, @@ -71,6 +72,13 @@ def self.trigger_notification(post, channel, rule) } end end + + def self.get_channel_by_name(name) + DiscourseChatIntegration::Channel + .with_provider(PROVIDER_NAME) + .with_data_value(CHANNEL_IDENTIFIER_KEY, name) + .first + end end end end diff --git a/plugin.rb b/plugin.rb index 3d846f6b..7c54ef64 100644 --- a/plugin.rb +++ b/plugin.rb @@ -16,6 +16,7 @@ # Site setting validators must be loaded before initialize require_relative "lib/discourse_chat_integration/provider/slack/slack_enabled_setting_validator" +require_relative "lib/discourse_chat_integration/chat_integration_reference_post" after_initialize do require_relative "app/initializers/discourse_chat_integration" @@ -84,5 +85,52 @@ end end end + + add_automation_scriptable("send_chat_integration_message") do + field :provider, + component: :choices, + extra: { + content: + DiscourseChatIntegration::Provider.enabled_provider_names.map do |provider| + { id: provider, name: "chat_integration.provider.#{provider}.title" } + end, + }, + required: true + field :channel_name, component: :text, required: true + + version 1 + + triggerables %i[topic_tags_changed] + + script do |context, fields, automation| + provider = fields.dig("provider", "value") + channel_name = fields.dig("channel_name", "value") + + post = + DiscourseChatIntegration::ChatIntegrationReferencePost.new( + user: context["user"], + topic: context["topic"], + kind: context["kind"], + context: { + "added_tags" => context["added_tags"], + "removed_tags" => context["removed_tags"], + }, + ) + provider = DiscourseChatIntegration::Provider.get_by_name(provider) + + channel = provider.get_channel_by_name(channel_name) # user must have created a channel in /admin/plugins/chat-integration/ page + + if channel.nil? + Rails.logger.warn "[discourse-automation] Channel not found. Automation ID: #{automation.id}" + next + end + + begin + provider.trigger_notification(post, channel, nil) + rescue StandardError => _ + Rails.logger.warn "[discourse-automation] Error while sending chat integration message. Automation ID: #{automation.id}" + end + end + end end end diff --git a/spec/dummy_provider.rb b/spec/dummy_provider.rb index c14832f2..fcd6b3a5 100644 --- a/spec/dummy_provider.rb +++ b/spec/dummy_provider.rb @@ -40,6 +40,7 @@ def self.set_raise_exception(bool) module ::DiscourseChatIntegration::Provider::Dummy2Provider PROVIDER_NAME = "dummy2".freeze PROVIDER_ENABLED_SETTING = :chat_integration_enabled # Tie to main plugin enabled setting + CHANNEL_IDENTIFIER_KEY = "val".freeze CHANNEL_PARAMETERS = [{ key: "val", regex: '^\S+$', unique: true }] @@sent_messages = [] @@ -51,8 +52,17 @@ def self.trigger_notification(post, channel, rule) def self.sent_messages @@sent_messages end + + def self.get_channel_by_name(name) + DiscourseChatIntegration::Channel + .with_provider(PROVIDER_NAME) + .with_data_value(CHANNEL_IDENTIFIER_KEY, name) + .first + end end end after(:each) { ::DiscourseChatIntegration::Provider.send(:remove_const, :Dummy2Provider) } + + let(:validated_provider) { ::DiscourseChatIntegration::Provider::Dummy2Provider } end diff --git a/spec/integration/automation_spec.rb b/spec/integration/automation_spec.rb new file mode 100644 index 00000000..8ba4c75f --- /dev/null +++ b/spec/integration/automation_spec.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true +require_relative "../dummy_provider" +RSpec.describe "Triggering notifications" do + include_context "with validated dummy provider" + + context "with automation installed", if: defined?(DiscourseAutomation) do + fab!(:admin) + fab!(:category) + fab!(:tag) + + fab!(:automation) do + Fabricate( + :automation, + script: "send_chat_integration_message", + trigger: "topic_tags_changed", + enabled: true, + ) + end + let(:channel1) do + DiscourseChatIntegration::Channel.create!(provider: "dummy2", data: { val: "channel" }) + end + + before do + SiteSetting.chat_integration_enabled = true + SiteSetting.discourse_automation_enabled = true + + SiteSetting.tagging_enabled = true + SiteSetting.create_tag_allowed_groups = Group::AUTO_GROUPS[:everyone] + SiteSetting.tag_topic_allowed_groups = Group::AUTO_GROUPS[:everyone] + + automation.upsert_field!( + "watching_categories", + "categories", + { "value" => [category.id] }, + target: "trigger", + ) + automation.upsert_field!( + "watching_tags", + "tags", + { "value" => [tag.name] }, + target: "trigger", + ) + automation.upsert_field!( + "provider", + "choices", + { "value" => channel1.provider }, + target: "script", + ) + automation.upsert_field!("channel_name", "text", { "value" => "channel" }, target: "script") + end + + it "triggers a notification" do + topic = Fabricate(:topic, user: admin, tags: [], category: category) + + DiscourseTagging.tag_topic_by_names(topic, Guardian.new(admin), [tag.name]) + + expect(validated_provider.sent_messages.length).to eq(1) + expect(validated_provider.sent_messages.first[:post]).to eq(topic.id) + expect(validated_provider.sent_messages.first[:channel]).to eq(channel1) + end + + it "only triggers for the correct tag" do + topic = Fabricate(:topic, user: admin, tags: [], category: category) + + DiscourseTagging.tag_topic_by_names(topic, Guardian.new(admin), ["other_tag"]) + + expect(validated_provider.sent_messages.length).to eq(0) + end + + it "only triggers for the correct category" do + topic = Fabricate(:topic, user: admin, tags: [], category: Fabricate(:category)) + + DiscourseTagging.tag_topic_by_names(topic, Guardian.new(admin), [tag.name]) + + expect(validated_provider.sent_messages.length).to eq(0) + end + end +end diff --git a/spec/lib/discourse_chat_integration/chat_integration_reference_post_spec.rb b/spec/lib/discourse_chat_integration/chat_integration_reference_post_spec.rb new file mode 100644 index 00000000..a2d5deb6 --- /dev/null +++ b/spec/lib/discourse_chat_integration/chat_integration_reference_post_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +RSpec.describe DiscourseChatIntegration::ChatIntegrationReferencePost do + fab!(:topic) + fab!(:first_post) { Fabricate(:post, topic: topic) } + let!(:context) do + { + "user" => Fabricate(:user), + "topic" => topic, + # every rule will add a kind and their context params + } + end + + describe "when creating when topic tags change" do + before do + context["kind"] = DiscourseAutomation::Triggers::TOPIC_TAGS_CHANGED + context["added_tags"] = %w[tag1 tag2] + context["removed_tags"] = %w[tag3 tag4] + end + + it "creates a post with the correct raw" do + post = + described_class.new( + user: context["user"], + topic: context["topic"], + kind: context["kind"], + context: { + "added_tags" => context["added_tags"], + "removed_tags" => context["removed_tags"], + }, + ) + expect(post.raw).to eq("Added #tag1, #tag2 and removed #tag3, #tag4") + end + + it "has a working excerpt" do + post = + described_class.new( + user: context["user"], + topic: context["topic"], + kind: context["kind"], + context: { + "added_tags" => context["added_tags"], + "removed_tags" => context["removed_tags"], + }, + ) + expect(post.excerpt).to eq("Added #tag1, #tag2 and removed #tag3, #tag4") + end + end +end diff --git a/spec/lib/discourse_chat_integration/provider/discord/discord_provider_spec.rb b/spec/lib/discourse_chat_integration/provider/discord/discord_provider_spec.rb index 2dde375f..42b67255 100644 --- a/spec/lib/discourse_chat_integration/provider/discord/discord_provider_spec.rb +++ b/spec/lib/discourse_chat_integration/provider/discord/discord_provider_spec.rb @@ -49,4 +49,18 @@ expect(stub1).to have_been_requested.once end end + + describe ".get_channel_by_name" do + it "returns the right channel" do + expected = + DiscourseChatIntegration::Channel.create!( + provider: "discord", + data: { + name: "Awesome Channel", + webhook_url: "https://discord.com/api/webhooks/1234/abcd", + }, + ) + expect(described_class.get_channel_by_name("Awesome Channel")).to eq(expected) + end + end end diff --git a/spec/lib/discourse_chat_integration/provider/flowdock/flowdock_provider_spec.rb b/spec/lib/discourse_chat_integration/provider/flowdock/flowdock_provider_spec.rb index 92b62b97..f19949d1 100644 --- a/spec/lib/discourse_chat_integration/provider/flowdock/flowdock_provider_spec.rb +++ b/spec/lib/discourse_chat_integration/provider/flowdock/flowdock_provider_spec.rb @@ -36,4 +36,19 @@ expect(stub1).to have_been_requested.once end end + + describe ".get_channel_by_name" do + it "returns the right channel" do + expected = + DiscourseChatIntegration::Channel.create!( + provider: "flowdock", + data: { + flow_token: "5d1fe04cf66e078d6a2b579ddb8a465b", + }, + ) + expect(described_class.get_channel_by_name("5d1fe04cf66e078d6a2b579ddb8a465b")).to eq( + expected, + ) + end + end end diff --git a/spec/lib/discourse_chat_integration/provider/gitter/gitter_provider_spec.rb b/spec/lib/discourse_chat_integration/provider/gitter/gitter_provider_spec.rb index 0914129d..06bac81c 100644 --- a/spec/lib/discourse_chat_integration/provider/gitter/gitter_provider_spec.rb +++ b/spec/lib/discourse_chat_integration/provider/gitter/gitter_provider_spec.rb @@ -37,4 +37,18 @@ expect(stub1).to have_been_requested.once end end + + describe ".get_channel_by_name" do + it "returns the right channel" do + expected = + DiscourseChatIntegration::Channel.create!( + provider: "gitter", + data: { + name: "gitterHQ/services", + webhook_url: "https://webhooks.gitter.im/e/a1e2i3o4u5", + }, + ) + expect(described_class.get_channel_by_name("gitterHQ/services")).to eq(expected) + end + end end diff --git a/spec/lib/discourse_chat_integration/provider/google/google_provider_spec.rb b/spec/lib/discourse_chat_integration/provider/google/google_provider_spec.rb index 80d1de16..c06053eb 100644 --- a/spec/lib/discourse_chat_integration/provider/google/google_provider_spec.rb +++ b/spec/lib/discourse_chat_integration/provider/google/google_provider_spec.rb @@ -33,4 +33,18 @@ expect(stub1).to have_been_requested.once end end + + describe ".get_channel_by_name" do + it "returns the right channel" do + expected = + DiscourseChatIntegration::Channel.create!( + provider: "google", + data: { + name: "discourse", + webhook_url: "https://chat.googleapis.com/v1/abcdefg", + }, + ) + expect(described_class.get_channel_by_name("discourse")).to eq(expected) + end + end end diff --git a/spec/lib/discourse_chat_integration/provider/groupme/groupme_provider_spec.rb b/spec/lib/discourse_chat_integration/provider/groupme/groupme_provider_spec.rb index b55e51e7..0d827470 100644 --- a/spec/lib/discourse_chat_integration/provider/groupme/groupme_provider_spec.rb +++ b/spec/lib/discourse_chat_integration/provider/groupme/groupme_provider_spec.rb @@ -39,4 +39,17 @@ expect(stub1).to have_been_requested.once end end + + describe ".get_channel_by_name" do + it "returns the right channel" do + expected = + DiscourseChatIntegration::Channel.create!( + provider: "groupme", + data: { + groupme_instance_name: "my instance", + }, + ) + expect(described_class.get_channel_by_name("my instance")).to eq(expected) + end + end end diff --git a/spec/lib/discourse_chat_integration/provider/guilded/guilded_provider_spec.rb b/spec/lib/discourse_chat_integration/provider/guilded/guilded_provider_spec.rb index 5a1a2fda..9fa7b49f 100644 --- a/spec/lib/discourse_chat_integration/provider/guilded/guilded_provider_spec.rb +++ b/spec/lib/discourse_chat_integration/provider/guilded/guilded_provider_spec.rb @@ -35,4 +35,18 @@ expect(stub1).to have_been_requested.once end end + + describe ".get_channel_by_name" do + it "returns the right channel" do + expected = + DiscourseChatIntegration::Channel.create!( + provider: "guilded", + data: { + name: "Awesome Channel", + webhook_url: "https://media.guilded.gg/webhooks/1234/abcd", + }, + ) + expect(described_class.get_channel_by_name("Awesome Channel")).to eq(expected) + end + end end diff --git a/spec/lib/discourse_chat_integration/provider/matrix/matrix_provider_spec.rb b/spec/lib/discourse_chat_integration/provider/matrix/matrix_provider_spec.rb index 0733a662..0605ed49 100644 --- a/spec/lib/discourse_chat_integration/provider/matrix/matrix_provider_spec.rb +++ b/spec/lib/discourse_chat_integration/provider/matrix/matrix_provider_spec.rb @@ -44,4 +44,18 @@ expect(stub1).to have_been_requested.once end end + + describe ".get_channel_by_name" do + it "returns the right channel" do + expected = + DiscourseChatIntegration::Channel.create!( + provider: "matrix", + data: { + name: "Awesome Channel", + room_id: "!blah:matrix.org", + }, + ) + expect(described_class.get_channel_by_name("Awesome Channel")).to eq(expected) + end + end end diff --git a/spec/lib/discourse_chat_integration/provider/mattermost/mattermost_provider_spec.rb b/spec/lib/discourse_chat_integration/provider/mattermost/mattermost_provider_spec.rb index 855fd02c..425fcee6 100644 --- a/spec/lib/discourse_chat_integration/provider/mattermost/mattermost_provider_spec.rb +++ b/spec/lib/discourse_chat_integration/provider/mattermost/mattermost_provider_spec.rb @@ -57,4 +57,17 @@ expect(stub1).to have_been_requested.once end end + + describe ".get_channel_by_name" do + it "returns the right channel" do + expected = + DiscourseChatIntegration::Channel.create!( + provider: "mattermost", + data: { + identifier: "#awesomechannel", + }, + ) + expect(described_class.get_channel_by_name("#awesomechannel")).to eq(expected) + end + end end diff --git a/spec/lib/discourse_chat_integration/provider/powerautomate/powerautomate_provider_spec.rb b/spec/lib/discourse_chat_integration/provider/powerautomate/powerautomate_provider_spec.rb index 34aec866..b5c9e568 100644 --- a/spec/lib/discourse_chat_integration/provider/powerautomate/powerautomate_provider_spec.rb +++ b/spec/lib/discourse_chat_integration/provider/powerautomate/powerautomate_provider_spec.rb @@ -34,4 +34,19 @@ expect(stub1).to have_been_requested.once end end + + describe ".get_channel_by_name" do + it "returns the right channel" do + expected = + DiscourseChatIntegration::Channel.create!( + provider: "powerautomate", + data: { + name: "discourse", + webhook_url: + "https://prod-189.westus.logic.azure.com:443/workflows/c94b462906e64fe8a7299043706be96e/triggers/manual/paths/invoke?api-version=2016-06-01&sp=%2Ftriggers%2Fmanual%2Frun&sv=1.0&sig=-cmkg1oG-88dP3Yqdh62yTG1LUtJFcB91rQisorfw_w", + }, + ) + expect(described_class.get_channel_by_name("discourse")).to eq(expected) + end + end end diff --git a/spec/lib/discourse_chat_integration/provider/rocketchat/rocketchat_provider_spec.rb b/spec/lib/discourse_chat_integration/provider/rocketchat/rocketchat_provider_spec.rb index 46521728..73585dd5 100644 --- a/spec/lib/discourse_chat_integration/provider/rocketchat/rocketchat_provider_spec.rb +++ b/spec/lib/discourse_chat_integration/provider/rocketchat/rocketchat_provider_spec.rb @@ -35,4 +35,17 @@ expect(stub1).to have_been_requested.once end end + + describe ".get_channel_by_name" do + it "returns the right channel" do + expected = + DiscourseChatIntegration::Channel.create!( + provider: "rocketchat", + data: { + identifier: "#general", + }, + ) + expect(described_class.get_channel_by_name("#general")).to eq(expected) + end + end end diff --git a/spec/lib/discourse_chat_integration/provider/slack/slack_provider_spec.rb b/spec/lib/discourse_chat_integration/provider/slack/slack_provider_spec.rb index 93245857..d3c8a97a 100644 --- a/spec/lib/discourse_chat_integration/provider/slack/slack_provider_spec.rb +++ b/spec/lib/discourse_chat_integration/provider/slack/slack_provider_spec.rb @@ -346,4 +346,17 @@ }.to raise_error(StandardError) end end + + describe ".get_channel_by_name" do + it "returns the right channel" do + expected = + DiscourseChatIntegration::Channel.create!( + provider: "slack", + data: { + identifier: "#general", + }, + ) + expect(described_class.get_channel_by_name("#general")).to eq(expected) + end + end end diff --git a/spec/lib/discourse_chat_integration/provider/teams/teams_provider_spec.rb b/spec/lib/discourse_chat_integration/provider/teams/teams_provider_spec.rb index dbf8d95b..cb4b5c92 100644 --- a/spec/lib/discourse_chat_integration/provider/teams/teams_provider_spec.rb +++ b/spec/lib/discourse_chat_integration/provider/teams/teams_provider_spec.rb @@ -44,4 +44,19 @@ end end end + + describe ".get_channel_by_name" do + it "returns the right channel" do + expected = + DiscourseChatIntegration::Channel.create!( + provider: "teams", + data: { + name: "discourse", + webhook_url: + "https://outlook.office.com/webhook/677980e4-e03b-4a5e-ad29-dc1ee0c32a80@9e9b5238-5ab2-496a-8e6a-e9cf05c7eb5c/IncomingWebhook/e7a1006ded44478992769d0c4f391e34/e028ca8a-e9c8-4c6c-a4d8-578f881a3cff", + }, + ) + expect(described_class.get_channel_by_name("discourse")).to eq(expected) + end + end end diff --git a/spec/lib/discourse_chat_integration/provider/telegram/telegram_provider_spec.rb b/spec/lib/discourse_chat_integration/provider/telegram/telegram_provider_spec.rb index aec7115f..08f94ed0 100644 --- a/spec/lib/discourse_chat_integration/provider/telegram/telegram_provider_spec.rb +++ b/spec/lib/discourse_chat_integration/provider/telegram/telegram_provider_spec.rb @@ -48,4 +48,18 @@ expect(stub1).to have_been_requested.once end end + + describe ".get_channel_by_name" do + it "returns the right channel" do + expected = + DiscourseChatIntegration::Channel.create!( + provider: "telegram", + data: { + name: "Awesome Channel", + chat_id: "123", + }, + ) + expect(described_class.get_channel_by_name("Awesome Channel")).to eq(expected) + end + end end diff --git a/spec/lib/discourse_chat_integration/provider/webex/webex_provider_spec.rb b/spec/lib/discourse_chat_integration/provider/webex/webex_provider_spec.rb index d645c014..0239c703 100644 --- a/spec/lib/discourse_chat_integration/provider/webex/webex_provider_spec.rb +++ b/spec/lib/discourse_chat_integration/provider/webex/webex_provider_spec.rb @@ -34,4 +34,19 @@ expect(stub1).to have_been_requested.once end end + + describe ".get_channel_by_name" do + it "returns the right channel" do + expected = + DiscourseChatIntegration::Channel.create!( + provider: "webex", + data: { + name: "discourse", + webhook_url: + "https://webexapis.com/v1/webhooks/incoming/jAHJjVVQ1cgEwb4ikQQawIrGdUtlocKA9fSNvIyADQoYo0mI70pztWUDOu22gDRPJOEJtCsc688zi1RMa", + }, + ) + expect(described_class.get_channel_by_name("discourse")).to eq(expected) + end + end end diff --git a/spec/lib/discourse_chat_integration/provider/zulip/zulip_provider_spec.rb b/spec/lib/discourse_chat_integration/provider/zulip/zulip_provider_spec.rb index 77c5f47c..005bd103 100644 --- a/spec/lib/discourse_chat_integration/provider/zulip/zulip_provider_spec.rb +++ b/spec/lib/discourse_chat_integration/provider/zulip/zulip_provider_spec.rb @@ -42,4 +42,19 @@ expect(stub1).to have_been_requested.once end end + + describe ".get_channel_by_name" do + it "returns the right channel" do + created = + DiscourseChatIntegration::Channel.create!( + provider: "zulip", + data: { + stream: "foo", + subject: "Discourse Notifications", + }, + ) + channel = described_class.get_channel_by_name("foo") + expect(channel).to eq(created) + end + end end