From 444158643674635f68bb28d7dfdb2dd445027d99 Mon Sep 17 00:00:00 2001 From: Benoit Desnoyers Date: Fri, 26 Jul 2024 10:38:23 -0400 Subject: [PATCH 1/3] Added tests and localization strings for Power Automate --- .gitignore | 1 + config/locales/client.en.yml | 18 ++- config/locales/server.en.yml | 6 + config/settings.yml | 13 +- lib/discourse_chat_integration/provider.rb | 1 + .../powerautomate/powerautomate_provider.rb | 139 ++++++++++++++++++ .../powerautomate_provider_spec.rb | 47 ++++++ 7 files changed, 221 insertions(+), 4 deletions(-) create mode 100644 lib/discourse_chat_integration/provider/powerautomate/powerautomate_provider.rb create mode 100644 spec/lib/discourse_chat_integration/provider/powerautomate/powerautomate_provider_spec.rb diff --git a/.gitignore b/.gitignore index 3c3629e6..fd4f2b06 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 007549d9..8b5b14fa 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -72,7 +72,6 @@ en: tags: "If specified, this rule will only apply to topics which have at least one of these tags" provider: - ####################################### ########### SLACK STRINGS ############# ####################################### @@ -230,6 +229,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 bd3f165b..3346b777 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 33488e43..7e383023 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 1010f719..28427d7a 100644 --- a/lib/discourse_chat_integration/provider.rb +++ b/lib/discourse_chat_integration/provider.rb @@ -99,6 +99,7 @@ def self.mount_engines 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 00000000..73ec4d84 --- /dev/null +++ b/lib/discourse_chat_integration/provider/powerautomate/powerautomate_provider.rb @@ -0,0 +1,139 @@ +# frozen_string_literal: true + +module DiscourseChatIntegration::Provider::PowerAutomateProvider + PROVIDER_NAME = "powerautomate".freeze + 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 00000000..812f5fbb --- /dev/null +++ b/spec/lib/discourse_chat_integration/provider/powerautomate/powerautomate_provider_spec.rb @@ -0,0 +1,47 @@ +# 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://outlook.office.com/webhook/677980e4-e03b-4a5e-ad29-dc1ee0c32a80@9e9b5238-5ab2-496a-8e6a-e9cf05c7eb5c/IncomingWebhook/e7a1006ded44478992769d0c4f391e34/e028ca8a-e9c8-4c6c-a4d8-578f881a3cff", + }, + ) + 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 + + describe "with nil user.name" do + before { post.user.update!(name: nil) } + + it "handles nil username correctly" do + message = described_class.get_message(post) + name = message[:sections].first[:facts].first[:name] + expect(name).to eq("") + end + end + end +end From 937a7a6998be705455b31341a6cf2ccb03c0c0ec Mon Sep 17 00:00:00 2001 From: Benoit Desnoyers Date: Sun, 18 Aug 2024 13:28:10 -0400 Subject: [PATCH 2/3] Fixed failing tests --- .../powerautomate/powerautomate_provider_spec.rb | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) 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 812f5fbb..34aec866 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 @@ -14,7 +14,7 @@ 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", + "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 @@ -33,15 +33,5 @@ ) expect(stub1).to have_been_requested.once end - - describe "with nil user.name" do - before { post.user.update!(name: nil) } - - it "handles nil username correctly" do - message = described_class.get_message(post) - name = message[:sections].first[:facts].first[:name] - expect(name).to eq("") - end - end end end From 097eb762787c5561ba6b5c050beb44239d093301 Mon Sep 17 00:00:00 2001 From: Benoit Desnoyers Date: Mon, 19 Aug 2024 10:51:43 -0400 Subject: [PATCH 3/3] Fixed Syntax Tree issues --- .../powerautomate/powerautomate_provider.rb | 86 +++++++++---------- 1 file changed, 40 insertions(+), 46 deletions(-) diff --git a/lib/discourse_chat_integration/provider/powerautomate/powerautomate_provider.rb b/lib/discourse_chat_integration/provider/powerautomate/powerautomate_provider.rb index 73ec4d84..9118c7ef 100644 --- a/lib/discourse_chat_integration/provider/powerautomate/powerautomate_provider.rb +++ b/lib/discourse_chat_integration/provider/powerautomate/powerautomate_provider.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseChatIntegration::Provider::PowerAutomateProvider - PROVIDER_NAME = "powerautomate".freeze + PROVIDER_NAME = "powerautomate" PROVIDER_ENABLED_SETTING = :chat_integration_powerautomate_enabled CHANNEL_PARAMETERS = [ { key: "name", regex: '^\S+$', unique: true }, @@ -59,79 +59,73 @@ def self.get_message(post) ) end - - - message = { type: "message", attachments: [ { contentType: "application/vnd.microsoft.card.adaptive", - contentUrl:nil, + contentUrl: nil, content: { type: "AdaptiveCard", - body:[ + body: [ { - type:"TextBlock", + type: "TextBlock", size: "Large", weight: "Bolder", - text:"[#{topic.title} #{category} #{topic.tags.present? ? topic.tags.map(&:name).join(", ") : ""}](#{post.full_url})", + text: + "[#{topic.title} #{category} #{topic.tags.present? ? topic.tags.map(&:name).join(", ") : ""}](#{post.full_url})", wrap: true, - spacing:"None" + spacing: "None", }, { - type:"ColumnSet", + type: "ColumnSet", columns: [ { - type:"Column", - items:[ + type: "Column", + items: [ { - type:"Image", - style:"Person", + type: "Image", + style: "Person", url: post.user.small_avatar_url, altText: full_name, - size:"Small" - } + size: " Small", + }, ], - width:"auto" + width: "auto", }, { - type:"Column", + type: "Column", items: [ + { type: "TextBlock", weight: "Bolder", text: full_name, wrap: true }, { - type:"TextBlock", - weight:"Bolder", - text:full_name, - wrap:true + type: "TextBlock", + spacing: "None", + text: display_name, + isSubtle: true, + wrap: true, }, - { - type:"TextBlock", - spacing:"None", - text:display_name, - isSubtle:true, - wrap:true - } ], - width:"stretch" - } - ] + width: "stretch", + }, + ], }, { - type:"TextBlock", - text:post.excerpt( - SiteSetting.chat_integration_powerautomate_excerpt_length, - text_entities: true, - strip_links: true, - remap_emoji: true, - ), - wrap:true - } + 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" - } - } - ] + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + version: "1.2", + }, + }, + ], } message