FEATURE: Enable optional support for threading slack posts (#38)
When creating a new Discourse post from slack with the `post` feature, record the slack `ts` thread ID for the resulting topic post using an HTML comment to pass the `ts` through. When notifying slack of new Discourse posts, record the slack `ts` thread ID in the post's topic if it has not yet been recorded. (Normally, this will be done for the topic post, except where notifications are being posted for old topics before this feature was created.) Add a new rule filter `thread` which posts threaded responses to slack if there is a `ts` recorded for the post topic. Modify the `trigger_notifications` interface to enable other integrations to implement similar functionality. Present the `thread` rule in the help text and admin UI only for the slack providers. https://meta.discourse.org/t/optionally-threading-posts-to-parent-topic-in-slack-integration/150759
This commit is contained in:
parent
b2daff34eb
commit
da9106127a
|
@ -34,7 +34,7 @@ class DiscourseChat::ChatController < ApplicationController
|
|||
|
||||
post = Topic.find(topic_id.to_i).posts.first
|
||||
|
||||
provider.trigger_notification(post, channel)
|
||||
provider.trigger_notification(post, channel, nil)
|
||||
|
||||
render json: success_json
|
||||
rescue Discourse::InvalidParameters, ActiveRecord::RecordNotFound => e
|
||||
|
|
|
@ -13,7 +13,7 @@ module DiscourseChat
|
|||
error_text = I18n.t("chat_integration.provider.#{provider}.parse_error")
|
||||
|
||||
case cmd
|
||||
when "watch", "follow", "mute"
|
||||
when "thread", "watch", "follow", "mute"
|
||||
return error_text if tokens.empty?
|
||||
# If the first token in the command is a tag, this rule applies to all categories
|
||||
category_name = tokens[0].start_with?('tag:') ? nil : tokens.shift
|
||||
|
|
|
@ -31,15 +31,16 @@ class DiscourseChat::Rule < DiscourseChat::PluginModel
|
|||
"
|
||||
CASE
|
||||
WHEN value::json->>'filter' = 'mute' THEN 1
|
||||
WHEN value::json->>'filter' = 'watch' THEN 2
|
||||
WHEN value::json->>'filter' = 'follow' THEN 3
|
||||
WHEN value::json->>'filter' = 'thread' THEN 2
|
||||
WHEN value::json->>'filter' = 'watch' THEN 3
|
||||
WHEN value::json->>'filter' = 'follow' THEN 4
|
||||
END
|
||||
")
|
||||
}
|
||||
|
||||
after_initialize :init_filter
|
||||
|
||||
validates :filter, inclusion: { in: %w(watch follow mute),
|
||||
validates :filter, inclusion: { in: %w(thread watch follow mute),
|
||||
message: "%{value} is not a valid filter" }
|
||||
|
||||
validates :type, inclusion: { in: %w(normal group_message group_mention),
|
||||
|
|
|
@ -52,7 +52,7 @@ module DiscourseChat
|
|||
|
||||
# Sort by order of precedence
|
||||
t_prec = { 'group_message' => 0, 'group_mention' => 1, 'normal' => 2 } # Group things win
|
||||
f_prec = { 'mute' => 0, 'watch' => 1, 'follow' => 2 } #(mute always wins; watch beats follow)
|
||||
f_prec = { 'mute' => 0, 'thread' => 1, 'watch' => 2, 'follow' => 3 } #(mute always wins; thread beats watch beats follow)
|
||||
sort_func = proc { |a, b| [t_prec[a.type], f_prec[a.filter]] <=> [t_prec[b.type], f_prec[b.filter]] }
|
||||
matching_rules = matching_rules.sort(&sort_func)
|
||||
|
||||
|
@ -80,7 +80,7 @@ module DiscourseChat
|
|||
next unless is_enabled = ::DiscourseChat::Provider.is_enabled(provider)
|
||||
|
||||
begin
|
||||
provider.trigger_notification(post, channel)
|
||||
provider.trigger_notification(post, channel, rule)
|
||||
channel.update_attribute('error_key', nil) if channel.error_key
|
||||
rescue => e
|
||||
if e.class == (DiscourseChat::ProviderError) && e.info.key?(:error_key) && !e.info[:error_key].nil?
|
||||
|
|
|
@ -6,23 +6,38 @@ import {
|
|||
} from "discourse-common/utils/decorators";
|
||||
|
||||
export default RestModel.extend({
|
||||
available_filters: [
|
||||
{
|
||||
id: "watch",
|
||||
name: I18n.t("chat_integration.filter.watch"),
|
||||
icon: "exclamation-circle"
|
||||
},
|
||||
{
|
||||
id: "follow",
|
||||
name: I18n.t("chat_integration.filter.follow"),
|
||||
icon: "circle"
|
||||
},
|
||||
{
|
||||
id: "mute",
|
||||
name: I18n.t("chat_integration.filter.mute"),
|
||||
icon: "times-circle"
|
||||
@computed("channel.provider")
|
||||
available_filters(provider) {
|
||||
const available = [];
|
||||
|
||||
if (provider === "slack") {
|
||||
available.push({
|
||||
id: "thread",
|
||||
name: I18n.t("chat_integration.filter.thread"),
|
||||
icon: "chevron-right"
|
||||
});
|
||||
}
|
||||
],
|
||||
|
||||
available.push(
|
||||
{
|
||||
id: "watch",
|
||||
name: I18n.t("chat_integration.filter.watch"),
|
||||
icon: "exclamation-circle"
|
||||
},
|
||||
{
|
||||
id: "follow",
|
||||
name: I18n.t("chat_integration.filter.follow"),
|
||||
icon: "circle"
|
||||
},
|
||||
{
|
||||
id: "mute",
|
||||
name: I18n.t("chat_integration.filter.mute"),
|
||||
icon: "times-circle"
|
||||
}
|
||||
);
|
||||
|
||||
return available;
|
||||
},
|
||||
|
||||
available_types: [
|
||||
{ id: "normal", name: I18n.t("chat_integration.type.normal") },
|
||||
|
|
|
@ -18,7 +18,7 @@ export default DiscourseRoute.extend({
|
|||
"rules",
|
||||
channel.rules.map(rule => {
|
||||
rule = this.store.createRecord("rule", rule);
|
||||
rule.channel = channel;
|
||||
rule.set("channel", channel);
|
||||
return rule;
|
||||
})
|
||||
);
|
||||
|
|
|
@ -36,6 +36,7 @@ en:
|
|||
mute: 'Mute'
|
||||
follow: 'First post only'
|
||||
watch: 'All posts and replies'
|
||||
thread: 'All posts with threaded replies'
|
||||
rule_table:
|
||||
filter: "Filter"
|
||||
category: "Category"
|
||||
|
|
|
@ -122,8 +122,9 @@ en:
|
|||
tag: "The *%{name}* tag cannot be found."
|
||||
category: "The *%{name}* category cannot be found. Available categories: *%{list}*"
|
||||
help: |
|
||||
*New rule:* `/discourse [watch|follow|mute] [category] [tag:name]`
|
||||
*New rule:* `/discourse [thread|watch|follow|mute] [category] [tag:name]`
|
||||
(you must specify a rule type and at least one category or tag)
|
||||
- *thread* – notify this channel for new topics, thread replies if possible
|
||||
- *watch* – notify this channel for new topics and new replies
|
||||
- *follow* – notify this channel for new topics
|
||||
- *mute* – block notifications to this channel
|
||||
|
|
|
@ -63,7 +63,7 @@ module DiscourseChat
|
|||
message
|
||||
end
|
||||
|
||||
def self.trigger_notification(post, channel)
|
||||
def self.trigger_notification(post, channel, rule)
|
||||
# Adding ?wait=true means that we actually get a success/failure response, rather than returning asynchronously
|
||||
webhook_url = "#{channel.data['webhook_url']}?wait=true"
|
||||
message = generate_discord_message(post)
|
||||
|
|
|
@ -48,7 +48,7 @@ module DiscourseChat::Provider::FlowdockProvider
|
|||
message
|
||||
end
|
||||
|
||||
def self.trigger_notification(post, channel)
|
||||
def self.trigger_notification(post, channel, rule)
|
||||
flow_token = channel.data["flow_token"]
|
||||
message = generate_flowdock_message(post, flow_token)
|
||||
response = send_message("https://api.flowdock.com/messages", message)
|
||||
|
|
|
@ -10,7 +10,7 @@ module DiscourseChat
|
|||
{ key: "webhook_url", regex: '^https://webhooks\.gitter\.im/e/\S+$', unique: true, hidden: true }
|
||||
]
|
||||
|
||||
def self.trigger_notification(post, channel)
|
||||
def self.trigger_notification(post, channel, rule)
|
||||
message = gitter_message(post)
|
||||
response = Net::HTTP.post_form(URI(channel.data['webhook_url']), message: message)
|
||||
unless response.kind_of? Net::HTTPSuccess
|
||||
|
|
|
@ -71,7 +71,7 @@ module DiscourseChat::Provider::GroupmeProvider
|
|||
end
|
||||
end
|
||||
|
||||
def self.trigger_notification(post, channel)
|
||||
def self.trigger_notification(post, channel, rule)
|
||||
data_package = generate_groupme_message(post)
|
||||
self.send_via_webhook(data_package, channel)
|
||||
end
|
||||
|
|
|
@ -56,7 +56,7 @@ module DiscourseChat
|
|||
message
|
||||
end
|
||||
|
||||
def self.trigger_notification(post, channel)
|
||||
def self.trigger_notification(post, channel, rule)
|
||||
message = generate_matrix_message(post)
|
||||
|
||||
response = send_message(channel.data['room_id'], message)
|
||||
|
|
|
@ -75,7 +75,7 @@ module DiscourseChat
|
|||
message
|
||||
end
|
||||
|
||||
def self.trigger_notification(post, channel)
|
||||
def self.trigger_notification(post, channel, rule)
|
||||
channel_id = channel.data['identifier']
|
||||
message = mattermost_message(post, channel_id)
|
||||
|
||||
|
|
|
@ -68,7 +68,7 @@ module DiscourseChat::Provider::RocketchatProvider
|
|||
|
||||
end
|
||||
|
||||
def self.trigger_notification(post, channel)
|
||||
def self.trigger_notification(post, channel, rule)
|
||||
channel_id = channel.data['identifier']
|
||||
message = rocketchat_message(post, channel_id)
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
module DiscourseChat::Provider::SlackProvider
|
||||
PROVIDER_NAME = "slack".freeze
|
||||
THREAD = "thread".freeze
|
||||
|
||||
PROVIDER_ENABLED_SETTING = :chat_integration_slack_enabled
|
||||
|
||||
|
@ -9,6 +10,18 @@ module DiscourseChat::Provider::SlackProvider
|
|||
{ key: "identifier", regex: '^[@#]?\S*$', unique: true }
|
||||
]
|
||||
|
||||
require_dependency 'topic'
|
||||
::Topic.register_custom_field_type(DiscourseChat::Provider::SlackProvider::THREAD, :text)
|
||||
|
||||
class ::Topic
|
||||
def slack_thread_id=(ts)
|
||||
self.custom_fields[DiscourseChat::Provider::SlackProvider::THREAD] = ts
|
||||
end
|
||||
def slack_thread_id
|
||||
self.custom_fields[DiscourseChat::Provider::SlackProvider::THREAD]
|
||||
end
|
||||
end
|
||||
|
||||
def self.excerpt(post, max_length = SiteSetting.chat_integration_slack_excerpt_length)
|
||||
doc = Nokogiri::HTML.fragment(post.excerpt(max_length,
|
||||
remap_emoji: true,
|
||||
|
@ -18,7 +31,7 @@ module DiscourseChat::Provider::SlackProvider
|
|||
SlackMessageFormatter.format(doc.to_html)
|
||||
end
|
||||
|
||||
def self.slack_message(post, channel)
|
||||
def self.slack_message(post, channel, filter)
|
||||
display_name = "@#{post.user.username}"
|
||||
full_name = post.user.name || ""
|
||||
|
||||
|
@ -56,6 +69,10 @@ module DiscourseChat::Provider::SlackProvider
|
|||
attachments: []
|
||||
}
|
||||
|
||||
if filter == "thread" && thread_ts = topic.slack_thread_id
|
||||
message[:thread_ts] = thread_ts if not thread_ts.nil?
|
||||
end
|
||||
|
||||
summary = {
|
||||
fallback: "#{topic.title} - #{display_name}",
|
||||
author_name: display_name,
|
||||
|
@ -79,11 +96,9 @@ module DiscourseChat::Provider::SlackProvider
|
|||
|
||||
response = nil
|
||||
uri = ""
|
||||
record = DiscourseChat.pstore_get("slack_topic_#{post.topic.id}_#{channel}")
|
||||
|
||||
data = {
|
||||
token: SiteSetting.chat_integration_slack_access_token,
|
||||
}
|
||||
# <!--SLACK_CHANNEL_ID=#{@channel_id};SLACK_TS=#{@requested_thread_ts}-->
|
||||
slack_thread_regex = /<!--SLACK_CHANNEL_ID=(\w+);SLACK_TS=([0-9]{10}.[0-9]{6})-->/
|
||||
|
||||
req = Net::HTTP::Post.new(URI('https://slack.com/api/chat.postMessage'))
|
||||
|
||||
|
@ -94,6 +109,12 @@ module DiscourseChat::Provider::SlackProvider
|
|||
channel: message[:channel].gsub('#', ''),
|
||||
attachments: message[:attachments].to_json
|
||||
}
|
||||
if message.key?(:thread_ts)
|
||||
data[:thread_ts] = message[:thread_ts]
|
||||
elsif match = slack_thread_regex.match(post.raw)
|
||||
post.topic.slack_thread_id = match.captures[1]
|
||||
post.topic.save_custom_fields
|
||||
end
|
||||
|
||||
req.set_form_data(data)
|
||||
|
||||
|
@ -114,6 +135,12 @@ module DiscourseChat::Provider::SlackProvider
|
|||
raise ::DiscourseChat::ProviderError.new info: { error_key: error_key, request: uri, response_code: response.code, response_body: response.body }
|
||||
end
|
||||
|
||||
ts = json["ts"]
|
||||
if !ts.nil? && post.topic.slack_thread_id.nil?
|
||||
post.topic.slack_thread_id = ts
|
||||
post.topic.save_custom_fields
|
||||
end
|
||||
|
||||
response
|
||||
end
|
||||
|
||||
|
@ -137,9 +164,10 @@ module DiscourseChat::Provider::SlackProvider
|
|||
|
||||
end
|
||||
|
||||
def self.trigger_notification(post, channel)
|
||||
def self.trigger_notification(post, channel, rule)
|
||||
channel_id = channel.data['identifier']
|
||||
message = slack_message(post, channel_id)
|
||||
filter = rule.nil? ? "" : rule.filter
|
||||
message = slack_message(post, channel_id, filter)
|
||||
|
||||
if SiteSetting.chat_integration_slack_access_token.empty?
|
||||
self.send_via_webhook(message)
|
||||
|
|
|
@ -118,6 +118,10 @@ module DiscourseChat::Provider::SlackProvider
|
|||
post_content << "[#{username}]: #{url}\n"
|
||||
end
|
||||
|
||||
if not @requested_thread_ts.nil?
|
||||
post_content << "<!--SLACK_CHANNEL_ID=#{@channel_id};SLACK_TS=#{@requested_thread_ts}-->"
|
||||
end
|
||||
|
||||
post_content
|
||||
end
|
||||
|
||||
|
|
|
@ -79,7 +79,7 @@ module DiscourseChat
|
|||
|
||||
end
|
||||
|
||||
def self.trigger_notification(post, channel)
|
||||
def self.trigger_notification(post, channel, rule)
|
||||
chat_id = channel.data['chat_id']
|
||||
|
||||
message = {
|
||||
|
|
|
@ -46,7 +46,7 @@ module DiscourseChat
|
|||
}
|
||||
end
|
||||
|
||||
def self.trigger_notification(post, channel)
|
||||
def self.trigger_notification(post, channel, rule)
|
||||
|
||||
stream = channel.data['stream']
|
||||
subject = channel.data['subject']
|
||||
|
|
|
@ -11,6 +11,7 @@ enabled_site_setting :chat_integration_enabled
|
|||
register_asset "stylesheets/chat-integration-admin.scss"
|
||||
|
||||
register_svg_icon "rocket" if respond_to?(:register_svg_icon)
|
||||
register_svg_icon "fa-arrow-circle-o-right" if respond_to?(:register_svg_icon)
|
||||
|
||||
# Site setting validators must be loaded before initialize
|
||||
require_relative "lib/discourse_chat/provider/slack/slack_enabled_setting_validator"
|
||||
|
|
|
@ -10,7 +10,7 @@ RSpec.shared_context "dummy provider" do
|
|||
@@sent_messages = []
|
||||
@@raise_exception = nil
|
||||
|
||||
def self.trigger_notification(post, channel)
|
||||
def self.trigger_notification(post, channel, rule)
|
||||
if @@raise_exception
|
||||
raise @@raise_exception
|
||||
end
|
||||
|
@ -50,7 +50,7 @@ RSpec.shared_context "validated dummy provider" do
|
|||
|
||||
@@sent_messages = []
|
||||
|
||||
def self.trigger_notification(post, channel)
|
||||
def self.trigger_notification(post, channel, rule)
|
||||
@@sent_messages.push(post: post.id, channel: channel)
|
||||
end
|
||||
|
||||
|
|
|
@ -32,7 +32,12 @@ RSpec.describe DiscourseChat::Manager do
|
|||
expect(rule.tags).to eq(nil)
|
||||
end
|
||||
|
||||
it 'should work with all three filter types' do
|
||||
it 'should work with all four filter types' do
|
||||
response = DiscourseChat::Helper.process_command(chan1, ['thread', category.slug])
|
||||
|
||||
rule = DiscourseChat::Rule.all.first
|
||||
expect(rule.filter).to eq('thread')
|
||||
|
||||
response = DiscourseChat::Helper.process_command(chan1, ['watch', category.slug])
|
||||
|
||||
rule = DiscourseChat::Rule.all.first
|
||||
|
|
|
@ -14,7 +14,7 @@ RSpec.describe DiscourseChat::Provider::DiscordProvider do
|
|||
|
||||
it 'sends a webhook request' do
|
||||
stub1 = stub_request(:post, 'https://discordapp.com/api/webhooks/1234/abcd?wait=true').to_return(status: 200)
|
||||
described_class.trigger_notification(post, chan1)
|
||||
described_class.trigger_notification(post, chan1, nil)
|
||||
expect(stub1).to have_been_requested.once
|
||||
end
|
||||
|
||||
|
@ -22,14 +22,14 @@ RSpec.describe DiscourseChat::Provider::DiscordProvider do
|
|||
stub1 = stub_request(:post, 'https://discordapp.com/api/webhooks/1234/abcd?wait=true')
|
||||
.with(body: hash_including(embeds: [hash_including(author: hash_including(url: /^https?:\/\//))]))
|
||||
.to_return(status: 200)
|
||||
described_class.trigger_notification(post, chan1)
|
||||
described_class.trigger_notification(post, chan1, nil)
|
||||
expect(stub1).to have_been_requested.once
|
||||
end
|
||||
|
||||
it 'handles errors correctly' do
|
||||
stub1 = stub_request(:post, "https://discordapp.com/api/webhooks/1234/abcd?wait=true").to_return(status: 400)
|
||||
expect(stub1).to have_been_requested.times(0)
|
||||
expect { described_class.trigger_notification(post, chan1) }.to raise_exception(::DiscourseChat::ProviderError)
|
||||
expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception(::DiscourseChat::ProviderError)
|
||||
expect(stub1).to have_been_requested.once
|
||||
end
|
||||
|
||||
|
|
|
@ -14,14 +14,14 @@ RSpec.describe DiscourseChat::Provider::FlowdockProvider do
|
|||
|
||||
it 'sends a request' do
|
||||
stub1 = stub_request(:post, "https://api.flowdock.com/messages").to_return(status: 200)
|
||||
described_class.trigger_notification(post, chan1)
|
||||
described_class.trigger_notification(post, chan1, nil)
|
||||
expect(stub1).to have_been_requested.once
|
||||
end
|
||||
|
||||
it 'handles errors correctly' do
|
||||
stub1 = stub_request(:post, "https://api.flowdock.com/messages").to_return(status: 404, body: "{ \"error\": \"Not Found\"}")
|
||||
expect(stub1).to have_been_requested.times(0)
|
||||
expect { described_class.trigger_notification(post, chan1) }.to raise_exception(::DiscourseChat::ProviderError)
|
||||
expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception(::DiscourseChat::ProviderError)
|
||||
expect(stub1).to have_been_requested.once
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,14 +14,14 @@ RSpec.describe DiscourseChat::Provider::GitterProvider do
|
|||
|
||||
it 'sends a webhook request' do
|
||||
stub1 = stub_request(:post, chan1.data['webhook_url']).to_return(body: "OK")
|
||||
described_class.trigger_notification(post, chan1)
|
||||
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: 404, body: "{ \"error\": \"Not Found\"}")
|
||||
expect(stub1).to have_been_requested.times(0)
|
||||
expect { described_class.trigger_notification(post, chan1) }.to raise_exception(::DiscourseChat::ProviderError)
|
||||
expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception(::DiscourseChat::ProviderError)
|
||||
expect(stub1).to have_been_requested.once
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,14 +15,14 @@ RSpec.describe DiscourseChat::Provider::GroupmeProvider do
|
|||
|
||||
it 'sends a request' do
|
||||
stub1 = stub_request(:post, "https://api.groupme.com/v3/bots/post").to_return(status: 200)
|
||||
described_class.trigger_notification(post, chan1)
|
||||
described_class.trigger_notification(post, chan1, nil)
|
||||
expect(stub1).to have_been_requested.once
|
||||
end
|
||||
|
||||
it 'handles errors correctly' do
|
||||
stub1 = stub_request(:post, "https://api.groupme.com/v3/bots/post").to_return(status: 404, body: "{ \"error\": \"Not Found\"}")
|
||||
expect(stub1).to have_been_requested.times(0)
|
||||
expect { described_class.trigger_notification(post, chan1) }.to raise_exception(::DiscourseChat::ProviderError)
|
||||
expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception(::DiscourseChat::ProviderError)
|
||||
expect(stub1).to have_been_requested.once
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,14 +15,14 @@ RSpec.describe DiscourseChat::Provider::MatrixProvider do
|
|||
|
||||
it 'sends the message' do
|
||||
stub1 = stub_request(:put, %r{https://matrix.org/_matrix/client/r0/rooms/!blah:matrix.org/send/m.room.message/*}).to_return(status: 200)
|
||||
described_class.trigger_notification(post, chan1)
|
||||
described_class.trigger_notification(post, chan1, nil)
|
||||
expect(stub1).to have_been_requested.once
|
||||
end
|
||||
|
||||
it 'handles errors correctly' do
|
||||
stub1 = stub_request(:put, %r{https://matrix.org/_matrix/client/r0/rooms/!blah:matrix.org/send/m.room.message/*}).to_return(status: 400, body: '{"errmsg":"M_UNKNOWN"}')
|
||||
expect(stub1).to have_been_requested.times(0)
|
||||
expect { described_class.trigger_notification(post, chan1) }.to raise_exception(::DiscourseChat::ProviderError)
|
||||
expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception(::DiscourseChat::ProviderError)
|
||||
expect(stub1).to have_been_requested.once
|
||||
end
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ RSpec.describe DiscourseChat::Provider::MattermostProvider do
|
|||
|
||||
it 'sends a webhook request' do
|
||||
stub1 = stub_request(:post, 'https://mattermost.blah/hook/abcd').to_return(status: 200)
|
||||
described_class.trigger_notification(post, chan1)
|
||||
described_class.trigger_notification(post, chan1, nil)
|
||||
expect(stub1).to have_been_requested.once
|
||||
end
|
||||
|
||||
|
@ -40,7 +40,7 @@ RSpec.describe DiscourseChat::Provider::MattermostProvider do
|
|||
it 'handles errors correctly' do
|
||||
stub1 = stub_request(:post, "https://mattermost.blah/hook/abcd").to_return(status: 500, body: "error")
|
||||
expect(stub1).to have_been_requested.times(0)
|
||||
expect { described_class.trigger_notification(post, chan1) }.to raise_exception(::DiscourseChat::ProviderError)
|
||||
expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception(::DiscourseChat::ProviderError)
|
||||
expect(stub1).to have_been_requested.once
|
||||
end
|
||||
|
||||
|
|
|
@ -15,14 +15,14 @@ RSpec.describe DiscourseChat::Provider::RocketchatProvider do
|
|||
|
||||
it 'sends a webhook request' do
|
||||
stub1 = stub_request(:post, 'https://example.com/abcd').to_return(body: "{\"success\":true}")
|
||||
described_class.trigger_notification(post, chan1)
|
||||
described_class.trigger_notification(post, chan1, nil)
|
||||
expect(stub1).to have_been_requested.once
|
||||
end
|
||||
|
||||
it 'handles errors correctly' do
|
||||
stub1 = stub_request(:post, 'https://example.com/abcd').to_return(status: 400, body: "{}")
|
||||
expect(stub1).to have_been_requested.times(0)
|
||||
expect { described_class.trigger_notification(post, chan1) }.to raise_exception(::DiscourseChat::ProviderError)
|
||||
expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception(::DiscourseChat::ProviderError)
|
||||
expect(stub1).to have_been_requested.once
|
||||
end
|
||||
|
||||
|
|
|
@ -75,36 +75,65 @@ RSpec.describe DiscourseChat::Provider::SlackProvider do
|
|||
|
||||
it 'sends a webhook request' do
|
||||
stub1 = stub_request(:post, SiteSetting.chat_integration_slack_outbound_webhook_url).to_return(body: "success")
|
||||
described_class.trigger_notification(post, chan1)
|
||||
described_class.trigger_notification(post, chan1, nil)
|
||||
expect(stub1).to have_been_requested.once
|
||||
end
|
||||
|
||||
it 'handles errors correctly' do
|
||||
stub1 = stub_request(:post, SiteSetting.chat_integration_slack_outbound_webhook_url).to_return(status: 400, body: "error")
|
||||
expect(stub1).to have_been_requested.times(0)
|
||||
expect { described_class.trigger_notification(post, chan1) }.to raise_exception(::DiscourseChat::ProviderError)
|
||||
expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception(::DiscourseChat::ProviderError)
|
||||
expect(stub1).to have_been_requested.once
|
||||
end
|
||||
|
||||
describe 'with api token' do
|
||||
before do
|
||||
SiteSetting.chat_integration_slack_access_token = "magic"
|
||||
@ts = "#{Time.now.to_i}.012345"
|
||||
@stub1 = stub_request(:post, SiteSetting.chat_integration_slack_outbound_webhook_url).to_return(body: "success")
|
||||
@stub2 = stub_request(:post, %r{https://slack.com/api/chat.postMessage}).to_return(body: "{\"ok\":true, \"ts\": \"#{Time.now.to_i}.012345\", \"message\": {\"attachments\": [], \"username\":\"blah\", \"text\":\"blah2\"} }", headers: { 'Content-Type' => 'application/json' })
|
||||
@stub3 = stub_request(:post, %r{https://slack.com/api/chat.update}).to_return(body: '{"ok":true, "ts": "some_message_id"}', headers: { 'Content-Type' => 'application/json' })
|
||||
@thread_stub = stub_request(:post, %r{https://slack.com/api/chat.postMessage}).with(body: hash_including("thread_ts" => @ts)).to_return(body: "{\"ok\":true, \"ts\": \"12345.67890\", \"message\": {\"attachments\": [], \"username\":\"blah\", \"text\":\"blah2\"} }", headers: { 'Content-Type' => 'application/json' })
|
||||
@stub2 = stub_request(:post, %r{https://slack.com/api/chat.postMessage}).to_return(body: "{\"ok\":true, \"ts\": \"#{@ts}\", \"message\": {\"attachments\": [], \"username\":\"blah\", \"text\":\"blah2\"} }", headers: { 'Content-Type' => 'application/json' })
|
||||
@channel = DiscourseChat::Channel.create(provider: 'dummy')
|
||||
end
|
||||
|
||||
it 'sends an api request' do
|
||||
expect(@stub2).to have_been_requested.times(0)
|
||||
expect(@thread_stub).to have_been_requested.times(0)
|
||||
|
||||
described_class.trigger_notification(post, chan1)
|
||||
described_class.trigger_notification(post, chan1, nil)
|
||||
expect(@stub1).to have_been_requested.times(0)
|
||||
expect(@stub2).to have_been_requested.once
|
||||
expect(post.topic.slack_thread_id).to eq(@ts)
|
||||
expect(@thread_stub).to have_been_requested.times(0)
|
||||
end
|
||||
|
||||
it 'sends thread id for thread' do
|
||||
expect(@thread_stub).to have_been_requested.times(0)
|
||||
|
||||
rule = DiscourseChat::Rule.create(channel: @channel, filter: "thread")
|
||||
post.topic.slack_thread_id = @ts
|
||||
|
||||
described_class.trigger_notification(post, chan1, rule)
|
||||
expect(@thread_stub).to have_been_requested.once
|
||||
end
|
||||
|
||||
it 'recognizes slack thread ts in comment' do
|
||||
post.update!(cooked: "cooked", raw: <<~RAW
|
||||
My fingers are typing words that improve `raw_quality`
|
||||
<!--SLACK_CHANNEL_ID=UIGNOREFORNOW;SLACK_TS=1501801629.052212-->
|
||||
RAW
|
||||
)
|
||||
|
||||
rule = DiscourseChat::Rule.create(channel: @channel, filter: "thread")
|
||||
post.topic.slack_thread_id = nil
|
||||
|
||||
described_class.trigger_notification(post, chan1, rule)
|
||||
expect(post.topic.slack_thread_id).to eq('1501801629.052212')
|
||||
end
|
||||
|
||||
it 'handles errors correctly' do
|
||||
@stub2 = stub_request(:post, %r{https://slack.com/api/chat.postMessage}).to_return(body: "{\"ok\":false }", headers: { 'Content-Type' => 'application/json' })
|
||||
expect { described_class.trigger_notification(post, chan1) }.to raise_exception(::DiscourseChat::ProviderError)
|
||||
expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception(::DiscourseChat::ProviderError)
|
||||
expect(@stub2).to have_been_requested.once
|
||||
end
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ RSpec.describe DiscourseChat::Provider::SlackProvider::SlackTranscript do
|
|||
"type": "message",
|
||||
"user": "U6E2W7R8C",
|
||||
"text": "Which one?",
|
||||
"ts": "1501801634.053761"
|
||||
"ts": "1501801635.053761"
|
||||
},
|
||||
{
|
||||
"type": "message",
|
||||
|
@ -32,7 +32,7 @@ RSpec.describe DiscourseChat::Provider::SlackProvider::SlackTranscript do
|
|||
},
|
||||
{
|
||||
"type": "message",
|
||||
"user": "U6E2W7R8C",
|
||||
"user": "U820GH3LA",
|
||||
"text": "I'm interested!!",
|
||||
"ts": "1501801634.053761",
|
||||
"thread_ts": "1501801629.052212"
|
||||
|
@ -70,6 +70,24 @@ RSpec.describe DiscourseChat::Provider::SlackProvider::SlackTranscript do
|
|||
|
||||
let(:users_fixture) {
|
||||
[
|
||||
{
|
||||
id: "U6JSSESES",
|
||||
name: "threader",
|
||||
profile: {
|
||||
image_24: "https://example.com/avatar",
|
||||
display_name: "Threader",
|
||||
real_name: "A. Threader"
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "U820GH3LA",
|
||||
name: "responder",
|
||||
profile: {
|
||||
image_24: "https://example.com/avatar",
|
||||
display_name: "Responder",
|
||||
real_name: "A. Responder"
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "U5Z773QLS",
|
||||
name: "awesomeguyemail",
|
||||
|
@ -160,18 +178,24 @@ RSpec.describe DiscourseChat::Provider::SlackProvider::SlackTranscript do
|
|||
let(:thread_transcript) { described_class.new(channel_name: "#general", channel_id: "G1234", requested_thread_ts: "1501801629.052212") }
|
||||
|
||||
before do
|
||||
thread_transcript.load_user_data
|
||||
stub_request(:post, "https://slack.com/api/conversations.replies")
|
||||
.with(body: hash_including(token: "abcde", channel: 'G1234', ts: "1501801629.052212"))
|
||||
.to_return(status: 200, body: { ok: true, messages: messages_fixture }.to_json)
|
||||
.to_return(status: 200, body: { ok: true, messages: messages_fixture[3..4] }.to_json)
|
||||
thread_transcript.load_chat_history
|
||||
end
|
||||
|
||||
it 'includes messages in a thread' do
|
||||
expect(thread_transcript.messages.length).to eq(7)
|
||||
expect(thread_transcript.messages.length).to eq(2)
|
||||
end
|
||||
|
||||
it 'loads in chronological order' do # replies API presents messages in actual chronological order
|
||||
expect(thread_transcript.messages.first.ts).to eq('1501801665.062694')
|
||||
expect(thread_transcript.messages.first.ts).to eq('1501801629.052212')
|
||||
end
|
||||
|
||||
it 'includes slack thread identifiers in body' do
|
||||
text = thread_transcript.build_transcript
|
||||
expect(text).to include("<!--SLACK_CHANNEL_ID=G1234;SLACK_TS=1501801629.052212-->")
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -249,7 +273,7 @@ RSpec.describe DiscourseChat::Provider::SlackProvider::SlackTranscript do
|
|||
it 'handles usernames correctly' do
|
||||
expect(transcript.first_message.username).to eq('awesomeguy') # Normal user
|
||||
expect(transcript.messages[1].username).to eq('Test_Community') # Bot user
|
||||
expect(transcript.messages[2].username).to eq(nil) # Unknown normal user
|
||||
expect(transcript.messages[3].username).to eq(nil) # Unknown normal user
|
||||
# Normal user, display_name not set (fall back to real_name)
|
||||
expect(transcript.messages[4].username).to eq('another_guy')
|
||||
end
|
||||
|
|
|
@ -16,14 +16,14 @@ RSpec.describe DiscourseChat::Provider::TelegramProvider do
|
|||
|
||||
it 'sends a webhook request' do
|
||||
stub1 = stub_request(:post, 'https://api.telegram.org/botTOKEN/sendMessage').to_return(body: "{\"ok\":true}")
|
||||
described_class.trigger_notification(post, chan1)
|
||||
described_class.trigger_notification(post, chan1, nil)
|
||||
expect(stub1).to have_been_requested.once
|
||||
end
|
||||
|
||||
it 'handles errors correctly' do
|
||||
stub1 = stub_request(:post, 'https://api.telegram.org/botTOKEN/sendMessage').to_return(body: "{\"ok\":false, \"description\":\"chat not found\"}")
|
||||
expect(stub1).to have_been_requested.times(0)
|
||||
expect { described_class.trigger_notification(post, chan1) }.to raise_exception(::DiscourseChat::ProviderError)
|
||||
expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception(::DiscourseChat::ProviderError)
|
||||
expect(stub1).to have_been_requested.once
|
||||
end
|
||||
|
||||
|
|
|
@ -17,14 +17,14 @@ RSpec.describe DiscourseChat::Provider::ZulipProvider do
|
|||
|
||||
it 'sends a webhook request' do
|
||||
stub1 = stub_request(:post, 'https://hello.world/api/v1/messages').to_return(status: 200)
|
||||
described_class.trigger_notification(post, chan1)
|
||||
described_class.trigger_notification(post, chan1, nil)
|
||||
expect(stub1).to have_been_requested.once
|
||||
end
|
||||
|
||||
it 'handles errors correctly' do
|
||||
stub1 = stub_request(:post, 'https://hello.world/api/v1/messages').to_return(status: 400, body: '{}')
|
||||
expect(stub1).to have_been_requested.times(0)
|
||||
expect { described_class.trigger_notification(post, chan1) }.to raise_exception(::DiscourseChat::ProviderError)
|
||||
expect { described_class.trigger_notification(post, chan1, nil) }.to raise_exception(::DiscourseChat::ProviderError)
|
||||
expect(stub1).to have_been_requested.once
|
||||
end
|
||||
|
||||
|
|
|
@ -141,11 +141,12 @@ RSpec.describe DiscourseChat::Rule do
|
|||
it 'can be sorted by precedence' do
|
||||
rule2 = DiscourseChat::Rule.create(channel: channel, filter: 'mute')
|
||||
rule3 = DiscourseChat::Rule.create(channel: channel, filter: 'follow')
|
||||
rule4 = DiscourseChat::Rule.create(channel: channel, filter: 'mute')
|
||||
rule4 = DiscourseChat::Rule.create(channel: channel, filter: 'thread')
|
||||
rule5 = DiscourseChat::Rule.create(channel: channel, filter: 'mute')
|
||||
|
||||
expect(DiscourseChat::Rule.all.length).to eq(4)
|
||||
expect(DiscourseChat::Rule.all.length).to eq(5)
|
||||
|
||||
expect(DiscourseChat::Rule.all.order_by_precedence.map(&:filter)).to eq(["mute", "mute", "watch", "follow"])
|
||||
expect(DiscourseChat::Rule.all.order_by_precedence.map(&:filter)).to eq(["mute", "mute", "thread", "watch", "follow"])
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -190,6 +191,8 @@ RSpec.describe DiscourseChat::Rule do
|
|||
end
|
||||
|
||||
it 'validates filter correctly' do
|
||||
expect(rule.valid?).to eq(true)
|
||||
rule.filter = 'thread'
|
||||
expect(rule.valid?).to eq(true)
|
||||
rule.filter = 'follow'
|
||||
expect(rule.valid?).to eq(true)
|
||||
|
|
|
@ -93,7 +93,7 @@ RSpec.describe DiscourseChat::Manager do
|
|||
end
|
||||
|
||||
it "should respect watch over follow" do
|
||||
DiscourseChat::Rule.create!(channel: chan1, filter: 'follow', category_id: nil) # Wildcard watch
|
||||
DiscourseChat::Rule.create!(channel: chan1, filter: 'follow', category_id: nil) # Wildcard follow
|
||||
DiscourseChat::Rule.create!(channel: chan1, filter: 'watch', category_id: category.id) # Specific watch
|
||||
|
||||
manager.trigger_notifications(second_post.id)
|
||||
|
@ -101,6 +101,15 @@ RSpec.describe DiscourseChat::Manager do
|
|||
expect(provider.sent_to_channel_ids).to contain_exactly(chan1.id)
|
||||
end
|
||||
|
||||
it "should respect thread over watch" do
|
||||
DiscourseChat::Rule.create!(channel: chan1, filter: 'watch', category_id: nil) # Wildcard watch
|
||||
DiscourseChat::Rule.create!(channel: chan1, filter: 'thread', category_id: category.id) # Specific thread
|
||||
|
||||
manager.trigger_notifications(second_post.id)
|
||||
|
||||
expect(provider.sent_to_channel_ids).to contain_exactly(chan1.id)
|
||||
end
|
||||
|
||||
it "should not notify about private messages" do
|
||||
DiscourseChat::Rule.create!(channel: chan1, filter: 'follow', category_id: nil) # Wildcard watch
|
||||
|
||||
|
|
Loading…
Reference in New Issue