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
254 lines
10 KiB
Ruby
254 lines
10 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'rails_helper'
|
|
require_dependency 'post_creator'
|
|
require_relative '../dummy_provider'
|
|
|
|
RSpec.describe DiscourseChat::Manager do
|
|
|
|
let(:manager) { ::DiscourseChat::Manager }
|
|
let(:category) { Fabricate(:category) }
|
|
let(:group) { Fabricate(:group) }
|
|
let(:group2) { Fabricate(:group) }
|
|
let(:topic) { Fabricate(:topic, category_id: category.id) }
|
|
let(:first_post) { Fabricate(:post, topic: topic) }
|
|
let(:second_post) { Fabricate(:post, topic: topic, post_number: 2) }
|
|
|
|
describe '.trigger_notifications' do
|
|
include_context "dummy provider"
|
|
|
|
let(:chan1) { DiscourseChat::Channel.create!(provider: 'dummy') }
|
|
let(:chan2) { DiscourseChat::Channel.create!(provider: 'dummy') }
|
|
let(:chan3) { DiscourseChat::Channel.create!(provider: 'dummy') }
|
|
|
|
before do
|
|
SiteSetting.chat_integration_enabled = true
|
|
end
|
|
|
|
it "should fail gracefully when a provider throws an exception" do
|
|
DiscourseChat::Rule.create!(channel: chan1, filter: 'watch', category_id: category.id)
|
|
|
|
# Triggering a ProviderError should set the error_key to the error message
|
|
provider.set_raise_exception(DiscourseChat::ProviderError.new info: { error_key: "hello" })
|
|
manager.trigger_notifications(first_post.id)
|
|
expect(provider.sent_to_channel_ids).to contain_exactly()
|
|
expect(DiscourseChat::Channel.all.first.error_key).to eq('hello')
|
|
|
|
# Triggering a different error should set the error_key to a generic message
|
|
provider.set_raise_exception(StandardError.new "hello")
|
|
manager.trigger_notifications(first_post.id)
|
|
expect(provider.sent_to_channel_ids).to contain_exactly()
|
|
expect(DiscourseChat::Channel.all.first.error_key).to eq('chat_integration.channel_exception')
|
|
|
|
provider.set_raise_exception(nil)
|
|
|
|
manager.trigger_notifications(first_post.id)
|
|
expect(DiscourseChat::Channel.all.first.error_key.nil?).to be true
|
|
end
|
|
|
|
it "should not send notifications when provider is disabled" do
|
|
SiteSetting.chat_integration_enabled = false
|
|
DiscourseChat::Rule.create!(channel: chan1, filter: 'watch', category_id: category.id)
|
|
|
|
manager.trigger_notifications(first_post.id)
|
|
|
|
expect(provider.sent_to_channel_ids).to contain_exactly()
|
|
end
|
|
|
|
it "should send a notification to watched and following channels for new topic" do
|
|
DiscourseChat::Rule.create!(channel: chan1, filter: 'watch', category_id: category.id)
|
|
DiscourseChat::Rule.create!(channel: chan2, filter: 'follow', category_id: category.id)
|
|
DiscourseChat::Rule.create!(channel: chan3, filter: 'mute', category_id: category.id)
|
|
|
|
manager.trigger_notifications(first_post.id)
|
|
|
|
expect(provider.sent_to_channel_ids).to contain_exactly(chan1.id, chan2.id)
|
|
end
|
|
|
|
it "should send a notification only to watched for reply" do
|
|
DiscourseChat::Rule.create!(channel: chan1, filter: 'watch', category_id: category.id)
|
|
DiscourseChat::Rule.create!(channel: chan2, filter: 'follow', category_id: category.id)
|
|
DiscourseChat::Rule.create!(channel: chan3, filter: 'mute', category_id: category.id)
|
|
|
|
manager.trigger_notifications(second_post.id)
|
|
|
|
expect(provider.sent_to_channel_ids).to contain_exactly(chan1.id)
|
|
end
|
|
|
|
it "should respect wildcard category settings" do
|
|
DiscourseChat::Rule.create!(channel: chan1, filter: 'watch', category_id: nil)
|
|
|
|
manager.trigger_notifications(first_post.id)
|
|
|
|
expect(provider.sent_to_channel_ids).to contain_exactly(chan1.id)
|
|
end
|
|
|
|
it "should respect mute over watch" do
|
|
DiscourseChat::Rule.create!(channel: chan1, filter: 'watch', category_id: nil) # Wildcard watch
|
|
DiscourseChat::Rule.create!(channel: chan1, filter: 'mute', category_id: category.id) # Specific mute
|
|
|
|
manager.trigger_notifications(first_post.id)
|
|
|
|
expect(provider.sent_to_channel_ids).to contain_exactly()
|
|
end
|
|
|
|
it "should respect watch over follow" do
|
|
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)
|
|
|
|
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
|
|
|
|
private_post = Fabricate(:private_message_post)
|
|
|
|
manager.trigger_notifications(private_post.id)
|
|
|
|
expect(provider.sent_to_channel_ids).to contain_exactly()
|
|
end
|
|
|
|
it "should work for group pms" do
|
|
DiscourseChat::Rule.create!(channel: chan1, filter: 'watch') # Wildcard watch
|
|
DiscourseChat::Rule.create!(channel: chan2, type: 'group_message', filter: 'watch', group_id: group.id) # Group watch
|
|
|
|
private_post = Fabricate(:private_message_post)
|
|
private_post.topic.invite_group(Fabricate(:user), group)
|
|
|
|
manager.trigger_notifications(private_post.id)
|
|
|
|
expect(provider.sent_to_channel_ids).to contain_exactly(chan2.id)
|
|
end
|
|
|
|
it "should work for pms with multiple groups" do
|
|
DiscourseChat::Rule.create!(channel: chan1, type: 'group_message', filter: 'watch', group_id: group.id)
|
|
DiscourseChat::Rule.create!(channel: chan2, type: 'group_message', filter: 'watch', group_id: group2.id)
|
|
|
|
private_post = Fabricate(:private_message_post)
|
|
private_post.topic.invite_group(Fabricate(:user), group)
|
|
private_post.topic.invite_group(Fabricate(:user), group2)
|
|
|
|
manager.trigger_notifications(private_post.id)
|
|
|
|
expect(provider.sent_to_channel_ids).to contain_exactly(chan1.id, chan2.id)
|
|
end
|
|
|
|
it "should work for group mentions" do
|
|
third_post = Fabricate(:post, topic: topic, post_number: 3, raw: "let's mention @#{group.name}")
|
|
|
|
DiscourseChat::Rule.create!(channel: chan1, filter: 'watch') # Wildcard watch
|
|
DiscourseChat::Rule.create!(channel: chan2, type: 'group_message', filter: 'watch', group_id: group.id)
|
|
DiscourseChat::Rule.create!(channel: chan3, type: 'group_mention', filter: 'watch', group_id: group.id)
|
|
|
|
manager.trigger_notifications(third_post.id)
|
|
expect(provider.sent_to_channel_ids).to contain_exactly(chan1.id, chan3.id)
|
|
end
|
|
|
|
it "should give group rule precedence over normal rules" do
|
|
third_post = Fabricate(:post, topic: topic, post_number: 3, raw: "let's mention @#{group.name}")
|
|
|
|
DiscourseChat::Rule.create!(channel: chan1, filter: 'mute', category_id: category.id) # Mute category
|
|
manager.trigger_notifications(third_post.id)
|
|
expect(provider.sent_to_channel_ids).to contain_exactly()
|
|
|
|
DiscourseChat::Rule.create!(channel: chan1, filter: 'watch', type: 'group_mention', group_id: group.id) # Watch mentions
|
|
manager.trigger_notifications(third_post.id)
|
|
expect(provider.sent_to_channel_ids).to contain_exactly(chan1.id)
|
|
end
|
|
|
|
it "should not notify about mentions in private messages" do
|
|
# Group 1 watching for messages on channel 1
|
|
DiscourseChat::Rule.create!(channel: chan1, filter: 'watch', type: 'group_message', group_id: group.id)
|
|
# Group 2 watching for mentions on channel 2
|
|
DiscourseChat::Rule.create!(channel: chan2, filter: 'watch', type: 'group_mention', group_id: group2.id)
|
|
|
|
# Make a private message only accessible to group 1
|
|
private_message = Fabricate(:private_message_post)
|
|
private_message.topic.invite_group(Fabricate(:user), group)
|
|
|
|
# Mention group 2 in the message
|
|
mention_post = Fabricate(:post, topic: private_message.topic, post_number: 2, raw: "let's mention @#{group2.name}")
|
|
|
|
# We expect that only group 1 receives a notification
|
|
manager.trigger_notifications(mention_post.id)
|
|
expect(provider.sent_to_channel_ids).to contain_exactly(chan1.id)
|
|
end
|
|
|
|
it "should not notify about posts the chat_user cannot see" do
|
|
DiscourseChat::Rule.create!(channel: chan1, filter: 'follow', category_id: nil) # Wildcard watch
|
|
|
|
# Create a group & user
|
|
group = Fabricate(:group, name: "friends")
|
|
user = Fabricate(:user, username: 'david')
|
|
group.add(user)
|
|
|
|
# Set the chat_user to the newly created non-admin user
|
|
SiteSetting.chat_integration_discourse_username = 'david'
|
|
|
|
# Create a category
|
|
category = Fabricate(:category, name: "Test category")
|
|
topic.category = category
|
|
topic.save!
|
|
|
|
# Restrict category to admins only
|
|
category.set_permissions(Group[:admins] => :full)
|
|
category.save!
|
|
|
|
# Check no notification sent
|
|
manager.trigger_notifications(first_post.id)
|
|
expect(provider.sent_to_channel_ids).to contain_exactly()
|
|
|
|
# Now expose category to new user
|
|
category.set_permissions(Group[:friends] => :full)
|
|
category.save!
|
|
|
|
# Check notification sent
|
|
manager.trigger_notifications(first_post.id)
|
|
expect(provider.sent_to_channel_ids).to contain_exactly(chan1.id)
|
|
|
|
end
|
|
|
|
describe 'with tags enabled' do
|
|
let(:tag) { Fabricate(:tag, name: 'gsoc') }
|
|
let(:tagged_topic) { Fabricate(:topic, category_id: category.id, tags: [tag]) }
|
|
let(:tagged_first_post) { Fabricate(:post, topic: tagged_topic) }
|
|
|
|
before(:each) do
|
|
SiteSetting.tagging_enabled = true
|
|
end
|
|
|
|
it 'should still work for rules without any tags specified' do
|
|
DiscourseChat::Rule.create!(channel: chan1, filter: 'follow', category_id: nil) # Wildcard watch
|
|
|
|
manager.trigger_notifications(first_post.id)
|
|
manager.trigger_notifications(tagged_first_post.id)
|
|
|
|
expect(provider.sent_to_channel_ids).to contain_exactly(chan1.id, chan1.id)
|
|
end
|
|
|
|
it 'should only match tagged topics when rule has tags' do
|
|
DiscourseChat::Rule.create!(channel: chan1, filter: 'follow', category_id: category.id, tags: [tag.name])
|
|
|
|
manager.trigger_notifications(first_post.id)
|
|
manager.trigger_notifications(tagged_first_post.id)
|
|
|
|
expect(provider.sent_to_channel_ids).to contain_exactly(chan1.id)
|
|
end
|
|
|
|
end
|
|
end
|
|
|
|
end
|