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

132 lines
3.4 KiB
Ruby

# frozen_string_literal: true
class DiscourseChat::Rule < DiscourseChat::PluginModel
# Setup ActiveRecord::Store to use the JSON field to read/write these values
store :value, accessors: [ :channel_id, :type, :group_id, :category_id, :tags, :filter ], coder: JSON
scope :with_type, ->(type) { where("value::json->>'type'=?", type.to_s) }
scope :with_channel, ->(channel) { with_channel_id(channel.id) }
scope :with_channel_id, ->(channel_id) { where("value::json->>'channel_id'=?", channel_id.to_s) }
scope :with_category_id, ->(category_id) do
if category_id.nil?
where("(value::json->'category_id') IS NULL OR json_typeof(value::json->'category_id')='null'")
else
where("value::json->>'category_id'=?", category_id.to_s)
end
end
scope :with_group_ids, ->(group_id) do
where("value::json->>'group_id' IN (?)", group_id.map!(&:to_s))
end
scope :order_by_precedence, -> {
order("
CASE
WHEN value::json->>'type' = 'group_mention' THEN 1
WHEN value::json->>'type' = 'group_message' THEN 2
ELSE 3
END
",
"
CASE
WHEN value::json->>'filter' = 'mute' THEN 1
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(thread watch follow mute),
message: "%{value} is not a valid filter" }
validates :type, inclusion: { in: %w(normal group_message group_mention),
message: "%{value} is not a valid filter" }
validate :channel_valid?, :category_valid?, :group_valid?, :tags_valid?
def self.key_prefix
'rule:'.freeze
end
# We never want an empty array, set it to nil instead
def tags=(array)
if array.nil? || array.empty?
super(nil)
else
super(array)
end
end
# These are only allowed to be integers
%w(channel_id category_id group_id).each do |name|
define_method "#{name}=" do |val|
if val.nil? || val.blank?
super(nil)
else
super(val.to_i)
end
end
end
# Mock foreign key
# Could return nil
def channel
DiscourseChat::Channel.find_by(id: channel_id)
end
def channel=(val)
self.channel_id = val.id
end
private
def channel_valid?
if !(DiscourseChat::Channel.where(id: channel_id).exists?)
errors.add(:channel_id, "#{channel_id} is not a valid channel id")
end
end
def category_valid?
if type != 'normal' && !category_id.nil?
errors.add(:category_id, "cannot be specified for that type of rule")
end
return unless type == 'normal'
if !(category_id.nil? || Category.where(id: category_id).exists?)
errors.add(:category_id, "#{category_id} is not a valid category id")
end
end
def group_valid?
if type == 'normal' && !group_id.nil?
errors.add(:group_id, "cannot be specified for that type of rule")
end
return if type == 'normal'
if !Group.where(id: group_id).exists?
errors.add(:group_id, "#{group_id} is not a valid group id")
end
end
def tags_valid?
return if tags.nil?
tags.each do |tag|
if !Tag.where(name: tag).exists?
errors.add(:tags, "#{tag} is not a valid tag")
end
end
end
def init_filter
self.filter ||= 'watch'
self.type ||= 'normal'
end
end