From 47a6a89e5ab4b9f35307bdbc284f5031ac6b3c51 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Fri, 15 Sep 2017 23:12:02 +0100 Subject: [PATCH] Defer processing of transcripts to avoid timeouts --- config/locales/server.en.yml | 1 + .../slack/slack_command_controller.rb | 83 +++++++++++-------- .../slack/slack_command_controller_spec.rb | 37 ++++++--- 3 files changed, 76 insertions(+), 45 deletions(-) diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index b6716bd..b2eb018 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -137,6 +137,7 @@ en: posted_in: "posted in %{name}" change_first_message: "Change first message..." change_last_message: "Change last message..." + loading: "Loading the transcript..." ####################################### ########## TELEGRAM STRINGS ########### diff --git a/lib/discourse_chat/provider/slack/slack_command_controller.rb b/lib/discourse_chat/provider/slack/slack_command_controller.rb index e1be95f..3a008b4 100644 --- a/lib/discourse_chat/provider/slack/slack_command_controller.rb +++ b/lib/discourse_chat/provider/slack/slack_command_controller.rb @@ -40,54 +40,63 @@ module DiscourseChat::Provider::SlackProvider channel ||= DiscourseChat::Channel.create!(provider: provider, data: { identifier: channel_id }) if tokens[0] == 'post' - return process_post_request(channel, tokens, params[:channel_id], channel_id) + return process_post_request(channel, tokens, params[:channel_id], channel_id, params[:response_url]) end return { text: ::DiscourseChat::Helper.process_command(channel, tokens) } end - def process_post_request(channel, tokens, slack_channel_id, channel_name) + def process_post_request(channel, tokens, slack_channel_id, channel_name, response_url) if SiteSetting.chat_integration_slack_access_token.empty? return { text: I18n.t("chat_integration.provider.slack.transcript.api_required") } end - requested_messages = nil - first_message_ts = nil + Scheduler::Defer.later "Processing slack transcript request" do + requested_messages = nil + first_message_ts = nil - slack_url_regex = /^https:\/\/\S+\.slack\.com\/archives\/\S+\/p([0-9]{16})\/?$/ - if tokens.size > 1 && match = slack_url_regex.match(tokens[1]) - first_message_ts = match.captures[0].insert(10, '.') - elsif tokens.size > 1 - begin - requested_messages = Integer(tokens[1], 10) - rescue ArgumentError - return { text: I18n.t("chat_integration.provider.slack.parse_error") } + slack_url_regex = /^https:\/\/\S+\.slack\.com\/archives\/\S+\/p([0-9]{16})\/?$/ + if tokens.size > 1 && match = slack_url_regex.match(tokens[1]) + first_message_ts = match.captures[0].insert(10, '.') + elsif tokens.size > 1 + begin + requested_messages = Integer(tokens[1], 10) + rescue ArgumentError + return { text: I18n.t("chat_integration.provider.slack.parse_error") } + end end + + 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.load_user_data + return error_message unless transcript.load_chat_history + + if first_message_ts + return error_message unless transcript.set_first_message_by_ts(first_message_ts) + elsif requested_messages + transcript.set_first_message_by_index(-requested_messages) + else + transcript.set_first_message_by_index(-10) unless transcript.guess_first_message + end + + http = Net::HTTP.new("slack.com", 443) + http.use_ssl = true + req = Net::HTTP::Post.new(URI(response_url), 'Content-Type' => 'application/json') + req.body = transcript.build_slack_ui.to_json + response = http.request(req) end - 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.load_user_data - return error_message unless transcript.load_chat_history - - if first_message_ts - return error_message unless transcript.set_first_message_by_ts(first_message_ts) - elsif requested_messages - transcript.set_first_message_by_index(-requested_messages) - else - transcript.set_first_message_by_index(-10) unless transcript.guess_first_message - end - - return transcript.build_slack_ui + return { text: I18n.t("chat_integration.provider.slack.transcript.loading") } end def interactive json = JSON.parse(params[:payload], symbolize_names: true) + process_interactive(json) - render json: process_interactive(json) + render nothing: true, status: 200 end def process_interactive(json) @@ -101,14 +110,20 @@ module DiscourseChat::Provider::SlackProvider error_message = { text: I18n.t("chat_integration.provider.slack.transcript.error") } - return error_message unless transcript = SlackTranscript.new(channel_name: "##{json[:channel][:name]}", channel_id: json[:channel][:id]) - return error_message unless transcript.load_user_data - return error_message unless transcript.load_chat_history + Scheduler::Defer.later "Processing slack transcript update" do + return error_message unless transcript = SlackTranscript.new(channel_name: "##{json[:channel][:name]}", channel_id: json[:channel][:id]) + return error_message unless transcript.load_user_data + return error_message unless transcript.load_chat_history - return error_message unless transcript.set_first_message_by_ts(first_message) - return error_message unless transcript.set_last_message_by_ts(last_message) + return error_message unless transcript.set_first_message_by_ts(first_message) + return error_message unless transcript.set_last_message_by_ts(last_message) - return transcript.build_slack_ui + http = Net::HTTP.new("slack.com", 443) + http.use_ssl = true + req = Net::HTTP::Post.new(URI(json[:response_url]), 'Content-Type' => 'application/json') + req.body = transcript.build_slack_ui.to_json + response = http.request(req) + end end def slack_token_valid? 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 a11b502..35f2727 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 @@ -180,50 +180,63 @@ describe 'Slack Command Controller', type: :request do end it 'generates the transcript UI properly' do + command_stub = stub_request(:post, "https://slack.com/commands/1234") + .with(body: /attachments/) + .to_return(body: { ok: true }.to_json) + post "/chat-integration/slack/command.json", text: "post", + response_url: 'https://hooks.slack.com/commands/1234', channel_name: 'general', channel_id: 'C6029G78F', token: token - json = JSON.parse(response.body) - expect(json["attachments"].length).to eq(2) + expect(command_stub).to have_been_requested end it 'can select by url' do + command_stub = stub_request(:post, "https://slack.com/commands/1234") + .with(body: /1501801629\.052212/) + .to_return(body: { ok: true }.to_json) + post "/chat-integration/slack/command.json", text: "post https://sometestslack.slack.com/archives/C6029G78F/p1501801629052212", + response_url: 'https://hooks.slack.com/commands/1234', channel_name: 'general', channel_id: 'C6029G78F', token: token - json = JSON.parse(response.body) - expect(json["attachments"].length).to eq(2) - expect(json["attachments"][0]["ts"]).to eq("1501801629.052212") + expect(command_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/) + .to_return(body: { ok: true }.to_json) + post "/chat-integration/slack/command.json", text: "post 4", + response_url: 'https://hooks.slack.com/commands/1234', channel_name: 'general', channel_id: 'C6029G78F', token: token - json = JSON.parse(response.body) - expect(json["attachments"].length).to eq(2) - expect(json["attachments"][0]["ts"]).to eq("1501801629.052212") + expect(command_stub).to have_been_requested end it 'can auto select' do + command_stub = stub_request(:post, "https://slack.com/commands/1234") + .with(body: /1501615820\.949638/) + .to_return(body: { ok: true }.to_json) + post "/chat-integration/slack/command.json", text: "post", + response_url: 'https://hooks.slack.com/commands/1234', channel_name: 'general', channel_id: 'C6029G78F', token: token - json = JSON.parse(response.body) - expect(json["attachments"].length).to eq(2) - expect(json["attachments"][0]["ts"]).to eq("1501615820.949638") + expect(command_stub).to have_been_requested end end @@ -232,6 +245,7 @@ describe 'Slack Command Controller', type: :request do post "/chat-integration/slack/command.json", text: "post 2", + response_url: 'https://hooks.slack.com/commands/1234', channel_name: 'general', channel_id: 'C6029G78F', token: token @@ -246,6 +260,7 @@ describe 'Slack Command Controller', type: :request do post "/chat-integration/slack/command.json", text: "post 2", + response_url: 'https://hooks.slack.com/commands/1234', channel_name: 'general', channel_id: 'C6029G78F', token: token