From 0ed634387479947da6bedf402aab95594a21c5da Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 8 Apr 2018 03:16:36 +0100 Subject: [PATCH] Add basic support for thread transcripts --- .../slack/slack_command_controller.rb | 11 ++++- .../provider/slack/slack_transcript.rb | 20 +++++++--- .../slack/slack_command_controller_spec.rb | 40 +++++++++++++++++++ .../provider/slack/slack_transcript_spec.rb | 20 ++++++++++ 4 files changed, 84 insertions(+), 7 deletions(-) diff --git a/lib/discourse_chat/provider/slack/slack_command_controller.rb b/lib/discourse_chat/provider/slack/slack_command_controller.rb index 3d8d86a..a504359 100644 --- a/lib/discourse_chat/provider/slack/slack_command_controller.rb +++ b/lib/discourse_chat/provider/slack/slack_command_controller.rb @@ -56,9 +56,16 @@ module DiscourseChat::Provider::SlackProvider Scheduler::Defer.later "Processing slack transcript request" do requested_messages = nil first_message_ts = nil + requested_thread_ts = nil + thread_url_regex = /^https:\/\/\S+\.slack\.com\/archives\/\S+\/p[0-9]{16}\?thread_ts=([0-9]{10}.[0-9]{6})\S*$/ slack_url_regex = /^https:\/\/\S+\.slack\.com\/archives\/\S+\/p([0-9]{16})\/?$/ - if tokens.size > 1 && match = slack_url_regex.match(tokens[1]) + + if tokens.size > 2 && tokens[1] == "thread" && match = slack_url_regex.match(tokens[2]) + requested_thread_ts = match.captures[0].insert(10, '.') + elsif tokens.size > 1 && match = thread_url_regex.match(tokens[1]) + requested_thread_ts = match.captures[0] + elsif tokens.size > 1 && match = slack_url_regex.match(tokens[1]) first_message_ts = match.captures[0].insert(10, '.') elsif tokens.size > 1 begin @@ -70,7 +77,7 @@ module DiscourseChat::Provider::SlackProvider error_message = { text: I18n.t("chat_integration.provider.slack.transcript.error") } - return error_message unless transcript = SlackTranscript.new(channel_name: channel_name, channel_id: slack_channel_id) + return error_message unless transcript = SlackTranscript.new(channel_name: channel_name, channel_id: slack_channel_id, requested_thread_ts: requested_thread_ts) return error_message unless transcript.load_user_data return error_message unless transcript.load_chat_history diff --git a/lib/discourse_chat/provider/slack/slack_transcript.rb b/lib/discourse_chat/provider/slack/slack_transcript.rb index 23dd610..fe6d1e4 100644 --- a/lib/discourse_chat/provider/slack/slack_transcript.rb +++ b/lib/discourse_chat/provider/slack/slack_transcript.rb @@ -2,9 +2,10 @@ module DiscourseChat::Provider::SlackProvider class SlackTranscript attr_reader :users, :channel_id, :messages - def initialize(channel_name:, channel_id:) + def initialize(channel_name:, channel_id:, requested_thread_ts: nil) @channel_name = channel_name @channel_id = channel_id + @requested_thread_ts = requested_thread_ts @first_message_index = 0 @last_message_index = -1 # We can use negative array indicies to select the last message - fancy! @@ -30,6 +31,7 @@ module DiscourseChat::Provider::SlackProvider # Apply a heuristic to decide which is the first message in the current conversation def guess_first_message(skip_messages: 5) # Can skip the last n messages + return true if @requested_thread_ts # Always start thread on first message possible_first_messages = @messages[0..-skip_messages] @@ -121,6 +123,8 @@ module DiscourseChat::Provider::SlackProvider secret = DiscourseChat::Helper.save_transcript(post_content) link = "#{Discourse.base_url}/chat-transcript/#{secret}" + return { text: "<#{link}|#{I18n.t("chat_integration.provider.slack.transcript.post_to_discourse")}>" } if @requested_thread_ts + { text: "<#{link}|#{I18n.t("chat_integration.provider.slack.transcript.post_to_discourse")}>", attachments: [ @@ -201,7 +205,9 @@ module DiscourseChat::Provider::SlackProvider http = Net::HTTP.new("slack.com", 443) http.use_ssl = true - req = Net::HTTP::Post.new(URI('https://slack.com/api/conversations.history')) + endpoint = @requested_thread_ts ? "replies" : "history" + + req = Net::HTTP::Post.new(URI("https://slack.com/api/conversations.#{endpoint}")) data = { token: SiteSetting.chat_integration_slack_access_token, @@ -209,21 +215,25 @@ module DiscourseChat::Provider::SlackProvider limit: count } + data[:ts] = @requested_thread_ts if @requested_thread_ts + req.set_form_data(data) response = http.request(req) return false unless response.kind_of? Net::HTTPSuccess json = JSON.parse(response.body) return false unless json['ok'] - raw_messages = json['messages'].reverse + raw_messages = json['messages'] + raw_messages = raw_messages.reverse unless @requested_thread_ts # Build some message objects @messages = [] raw_messages.each_with_index do |message, index| # Only load messages next unless message["type"] == "message" - # Don't load responses to threads (if ts==thread_ts then it's the thread parent) - next if message["thread_ts"] && message["thread_ts"] != message["ts"] + + # Don't load responses to threads unless specifically requested (if ts==thread_ts then it's the thread parent) + next if !@requested_thread_ts && message["thread_ts"] && message["thread_ts"] != message["ts"] this_message = SlackMessage.new(message, self) @messages << this_message diff --git a/spec/lib/discourse_chat/provider/slack/slack_command_controller_spec.rb b/spec/lib/discourse_chat/provider/slack/slack_command_controller_spec.rb index f116879..9b74604 100644 --- a/spec/lib/discourse_chat/provider/slack/slack_command_controller_spec.rb +++ b/spec/lib/discourse_chat/provider/slack/slack_command_controller_spec.rb @@ -231,6 +231,46 @@ describe 'Slack Command Controller', type: :request do expect(command_stub).to have_been_requested end + it 'can select by url with thread parameter' do + replies_stub = stub_request(:post, "https://slack.com/api/conversations.replies") + .with(body: /1501801629\.052212/) + .to_return(body: { ok: true, messages: messages_fixture }.to_json) + + command_stub = stub_request(:post, "https://slack.com/commands/1234") + .to_return(body: { ok: true }.to_json) + + post "/chat-integration/slack/command.json", params: { + text: "post https://sometestslack.slack.com/archives/C6029G78F/p1501201669054212?thread_ts=1501801629.052212", + response_url: 'https://hooks.slack.com/commands/1234', + channel_name: 'general', + channel_id: 'C6029G78F', + token: token + } + + expect(command_stub).to have_been_requested + expect(replies_stub).to have_been_requested + end + + it 'can select by thread' do + replies_stub = stub_request(:post, "https://slack.com/api/conversations.replies") + .with(body: /1501801629\.052212/) + .to_return(body: { ok: true, messages: messages_fixture }.to_json) + + command_stub = stub_request(:post, "https://slack.com/commands/1234") + .to_return(body: { ok: true }.to_json) + + post "/chat-integration/slack/command.json", params: { + text: "post thread https://sometestslack.slack.com/archives/C6029G78F/p1501801629052212", + response_url: 'https://hooks.slack.com/commands/1234', + channel_name: 'general', + channel_id: 'C6029G78F', + token: token + } + + expect(command_stub).to have_been_requested + expect(replies_stub).to have_been_requested + end + it 'can select by count' do command_stub = stub_request(:post, "https://slack.com/commands/1234") .with(body: /1501801629\.052212/) diff --git a/spec/lib/discourse_chat/provider/slack/slack_transcript_spec.rb b/spec/lib/discourse_chat/provider/slack/slack_transcript_spec.rb index 86ad694..a764c70 100644 --- a/spec/lib/discourse_chat/provider/slack/slack_transcript_spec.rb +++ b/spec/lib/discourse_chat/provider/slack/slack_transcript_spec.rb @@ -127,6 +127,26 @@ RSpec.describe DiscourseChat::Provider::SlackProvider::SlackTranscript do end end + context 'with thread_ts specified' do + let(:thread_transcript) { described_class.new(channel_name: "#general", channel_id: "G1234", requested_thread_ts: "1501801629.052212") } + + before do + 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) + thread_transcript.load_chat_history + end + + it 'includes messages in a thread' do + expect(thread_transcript.messages.length).to eq(7) + 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') + end + + end + context 'with loaded messages' do before do stub_request(:post, "https://slack.com/api/conversations.history")