diff --git a/.gitignore b/.gitignore index 3c3629e..fd4f2b0 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ node_modules +.DS_Store diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 98b1ee9..d9641d5 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -71,7 +71,6 @@ en: tags: "If specified, this rule will only apply to topics which have at least one of these tags" provider: - ####################################### ########### SLACK STRINGS ############# ####################################### @@ -229,6 +228,23 @@ en: help: "The URL provided when you create a new incoming webhook" errors: invalid_channel: "That channel does not exist on Microsoft Teams" + ##################################################### + ######### MICROSOFT POWER AUTOMATE STRINGS ########## + ##################################################### + powerautomate: + title: "Microsoft Power Automate" + param: + name: + title: "Name" + help: "A name for the channel (only shown in the Discourse admin interface)" + webhook_url: + title: "Webhook URL" + help: "The URL provided when you create a new incoming webhook" + errors: + invalid_webhook: "That webhook URL is not valid." + ######################################## + ######### CISCO WEBEX STRINGS ########## + ######################################## webex: title: "Webex Teams" param: diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 815ac0a..29d3691 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -99,6 +99,12 @@ en: chat_integration_teams_enabled: "Enable the Microsoft Teams chat integration provider" chat_integration_teams_excerpt_length: "Microsoft Team post excerpt length" + #################################################### + ######## MICROSOFT POWER AUTOMATE SETTINGS ######### + #################################################### + chat_integration_powerautomate_enabled: "Enable the Microsoft Power Automate integration provider" + chat_integration_powerautomate_excerpt_length: "Microsoft Power Automate post excerpt length" + ########################################### ######## WEBEX TEAMS SETTINGS ######### ########################################### diff --git a/config/settings.yml b/config/settings.yml index 33488e4..7e38302 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -148,9 +148,16 @@ chat_integration: default: false chat_integration_teams_excerpt_length: default: 400 -########################################### -######## WEBEX TEAMS SETTINGS ######### -########################################### +#################################################### +######## MICROSOFT POWER AUTOMATE SETTINGS ######### +#################################################### + chat_integration_powerautomate_enabled: + default: false + chat_integration_powerautomate_excerpt_length: + default: 400 +################################# +######## WEBEX SETTINGS ######### +################################# chat_integration_webex_enabled: default: false chat_integration_webex_excerpt_length: diff --git a/lib/discourse_chat_integration/provider.rb b/lib/discourse_chat_integration/provider.rb index 1010f71..28427d7 100644 --- a/lib/discourse_chat_integration/provider.rb +++ b/lib/discourse_chat_integration/provider.rb @@ -99,6 +99,7 @@ require_relative "provider/gitter/gitter_provider" require_relative "provider/flowdock/flowdock_provider" require_relative "provider/groupme/groupme_provider" require_relative "provider/teams/teams_provider" +require_relative "provider/powerautomate/powerautomate_provider" require_relative "provider/webex/webex_provider" require_relative "provider/google/google_provider" require_relative "provider/guilded/guilded_provider" diff --git a/lib/discourse_chat_integration/provider/powerautomate/powerautomate_provider.rb b/lib/discourse_chat_integration/provider/powerautomate/powerautomate_provider.rb new file mode 100644 index 0000000..9118c7e --- /dev/null +++ b/lib/discourse_chat_integration/provider/powerautomate/powerautomate_provider.rb @@ -0,0 +1,133 @@ +# frozen_string_literal: true + +module DiscourseChatIntegration::Provider::PowerAutomateProvider + PROVIDER_NAME = "powerautomate" + PROVIDER_ENABLED_SETTING = :chat_integration_powerautomate_enabled + CHANNEL_PARAMETERS = [ + { key: "name", regex: '^\S+$', unique: true }, + { key: "webhook_url", regex: '^https:\/\/\S+$', unique: true, hidden: true }, + ] + + def self.trigger_notification(post, channel, rule) + message = get_message(post) + uri = URI(channel.data["webhook_url"]) + + http = FinalDestination::HTTP.new(uri.host, uri.port) + http.use_ssl = (uri.scheme == "https") + + req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json") + req.body = message.to_json + response = http.request(req) + + unless response.kind_of? Net::HTTPSuccess + if response.body.include?("Invalid webhook URL") + error_key = "chat_integration.provider.powerautomate.errors.invalid_webhook" + else + error_key = nil + end + raise ::DiscourseChatIntegration::ProviderError.new info: { + error_key: error_key, + request: req.body, + response_code: response.code, + response_body: response.body, + } + end + end + + def self.get_message(post) + display_name = "@#{post.user.username}" + full_name = + if SiteSetting.enable_names && post.user.name.present? + post.user.name + else + "" + end + + topic = post.topic + + category = "" + if topic.category&.uncategorized? + category = "[#{I18n.t("uncategorized_category_name")}]" + elsif topic.category + category = + ( + if (topic.category.parent_category) + "[#{topic.category.parent_category.name}/#{topic.category.name}]" + else + "[#{topic.category.name}]" + end + ) + end + + message = { + type: "message", + attachments: [ + { + contentType: "application/vnd.microsoft.card.adaptive", + contentUrl: nil, + content: { + type: "AdaptiveCard", + body: [ + { + type: "TextBlock", + size: "Large", + weight: "Bolder", + text: + "[#{topic.title} #{category} #{topic.tags.present? ? topic.tags.map(&:name).join(", ") : ""}](#{post.full_url})", + wrap: true, + spacing: "None", + }, + { + type: "ColumnSet", + columns: [ + { + type: "Column", + items: [ + { + type: "Image", + style: "Person", + url: post.user.small_avatar_url, + altText: full_name, + size: " Small", + }, + ], + width: "auto", + }, + { + type: "Column", + items: [ + { type: "TextBlock", weight: "Bolder", text: full_name, wrap: true }, + { + type: "TextBlock", + spacing: "None", + text: display_name, + isSubtle: true, + wrap: true, + }, + ], + width: "stretch", + }, + ], + }, + { + type: "TextBlock", + text: + post.excerpt( + SiteSetting.chat_integration_powerautomate_excerpt_length, + text_entities: true, + strip_links: true, + remap_emoji: true, + ), + wrap: true, + }, + ], + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + version: "1.2", + }, + }, + ], + } + + message + 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 new file mode 100644 index 0000000..34aec86 --- /dev/null +++ b/spec/lib/discourse_chat_integration/provider/powerautomate/powerautomate_provider_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe DiscourseChatIntegration::Provider::PowerAutomateProvider do + let(:post) { Fabricate(:post) } + + describe ".trigger_notifications" do + before { SiteSetting.chat_integration_powerautomate_enabled = true } + + let(:chan1) do + 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", + }, + ) + end + + it "sends a webhook request" do + stub1 = stub_request(:post, chan1.data["webhook_url"]).to_return(body: "1") + described_class.trigger_notification(post, chan1, nil) + expect(stub1).to have_been_requested.once + end + + it "handles errors correctly" do + stub1 = stub_request(:post, chan1.data["webhook_url"]).to_return(status: 400, body: "{}") + expect(stub1).to have_been_requested.times(0) + expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception( + ::DiscourseChatIntegration::ProviderError, + ) + expect(stub1).to have_been_requested.once + end + end +end