Michael K Johnson da9106127a
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
2020-06-15 16:45:25 +01:00

109 lines
4.5 KiB
Ruby

# frozen_string_literal: true
module DiscourseChat
module Manager
def self.guardian
Guardian.new(User.find_by(username: SiteSetting.chat_integration_discourse_username))
end
def self.trigger_notifications(post_id)
post = Post.find_by(id: post_id)
# Abort if the chat_user doesn't have permission to see the post
return if !guardian.can_see?(post)
# Abort if the post is blank, or is non-regular (e.g. a "topic closed" notification)
return if post.blank? || post.post_type != Post.types[:regular]
topic = post.topic
return if topic.blank?
# If it's a private message, filter rules by groups, otherwise filter rules by category
if topic.archetype == Archetype.private_message
group_ids_with_access = topic.topic_allowed_groups.pluck(:group_id)
return if group_ids_with_access.empty?
matching_rules = DiscourseChat::Rule.with_type('group_message').with_group_ids(group_ids_with_access)
else
matching_rules = DiscourseChat::Rule.with_type('normal').with_category_id(topic.category_id)
if topic.category # Also load the rules for the wildcard category
matching_rules += DiscourseChat::Rule.with_type('normal').with_category_id(nil)
end
# If groups are mentioned, check for any matching rules and append them
mentions = post.raw_mentions
if mentions && mentions.length > 0
groups = Group.where('LOWER(name) IN (?)', mentions)
if groups.exists?
matching_rules += DiscourseChat::Rule.with_type('group_mention').with_group_ids(groups.map(&:id))
end
end
end
# If tagging is enabled, thow away rules that don't apply to this topic
if SiteSetting.tagging_enabled
topic_tags = topic.tags.present? ? topic.tags.pluck(:name) : []
matching_rules = matching_rules.select do |rule|
next true if rule.tags.nil? || rule.tags.empty? # Filter has no tags specified
any_tags_match = !((rule.tags & topic_tags).empty?)
next any_tags_match # If any tags match, keep this filter, otherwise throw away
end
end
# Sort by order of precedence
t_prec = { 'group_message' => 0, 'group_mention' => 1, 'normal' => 2 } # Group things win
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)
# Take the first rule for each channel
uniq_func = proc { |rule| [rule.channel_id] }
matching_rules = matching_rules.uniq(&uniq_func)
# If a matching rule is set to mute, we can discard it now
matching_rules = matching_rules.select { |rule| rule.filter != "mute" }
# If this is not the first post, discard all "follow" rules
if !post.is_first_post?
matching_rules = matching_rules.select { |rule| rule.filter != "follow" }
end
# All remaining rules now require a notification to be sent
# If there are none left, abort
return false if matching_rules.empty?
# Loop through each rule, and trigger appropriate notifications
matching_rules.each do |rule|
# If there are any issues, skip to the next rule
next unless channel = rule.channel
next unless provider = ::DiscourseChat::Provider.get_by_name(channel.provider)
next unless is_enabled = ::DiscourseChat::Provider.is_enabled(provider)
begin
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?
channel.update_attribute('error_key', e.info[:error_key])
else
channel.update_attribute('error_key', 'chat_integration.channel_exception')
end
channel.update_attribute('error_info', JSON.pretty_generate(e.try(:info)))
# Log the error
# Discourse.handle_job_exception(e,
# message: "Triggering notifications failed",
# extra: { provider_name: provider::PROVIDER_NAME,
# channel: rule.channel,
# post_id: post.id,
# error_info: e.class == DiscourseChat::ProviderError ? e.info : nil }
# )
end
end
end
end
end