Merge pull request #20 from davidtaylorhq/private_slash_commands
Slash command and transcript improvements
This commit is contained in:
commit
d481205999
|
@ -6,6 +6,8 @@ services:
|
||||||
before_install:
|
before_install:
|
||||||
- plugin_name=${PWD##*/} && echo $plugin_name
|
- plugin_name=${PWD##*/} && echo $plugin_name
|
||||||
|
|
||||||
|
install: true # Prevent travis doing bundle install
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- >
|
- >
|
||||||
docker run
|
docker run
|
||||||
|
|
|
@ -26,8 +26,6 @@ module DiscourseChat::Provider::SlackProvider
|
||||||
case params[:channel_name]
|
case params[:channel_name]
|
||||||
when 'directmessage'
|
when 'directmessage'
|
||||||
"@#{params[:user_name]}"
|
"@#{params[:user_name]}"
|
||||||
when 'privategroup'
|
|
||||||
params[:channel_id]
|
|
||||||
else
|
else
|
||||||
"##{params[:channel_name]}"
|
"##{params[:channel_name]}"
|
||||||
end
|
end
|
||||||
|
@ -58,9 +56,16 @@ module DiscourseChat::Provider::SlackProvider
|
||||||
Scheduler::Defer.later "Processing slack transcript request" do
|
Scheduler::Defer.later "Processing slack transcript request" do
|
||||||
requested_messages = nil
|
requested_messages = nil
|
||||||
first_message_ts = 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})\/?$/
|
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, '.')
|
first_message_ts = match.captures[0].insert(10, '.')
|
||||||
elsif tokens.size > 1
|
elsif tokens.size > 1
|
||||||
begin
|
begin
|
||||||
|
@ -72,7 +77,7 @@ module DiscourseChat::Provider::SlackProvider
|
||||||
|
|
||||||
error_message = { text: I18n.t("chat_integration.provider.slack.transcript.error") }
|
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_user_data
|
||||||
return error_message unless transcript.load_chat_history
|
return error_message unless transcript.load_chat_history
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,10 @@ module DiscourseChat::Provider::SlackProvider
|
||||||
class SlackTranscript
|
class SlackTranscript
|
||||||
attr_reader :users, :channel_id, :messages
|
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_name = channel_name
|
||||||
@channel_id = channel_id
|
@channel_id = channel_id
|
||||||
|
@requested_thread_ts = requested_thread_ts
|
||||||
|
|
||||||
@first_message_index = 0
|
@first_message_index = 0
|
||||||
@last_message_index = -1 # We can use negative array indicies to select the last message - fancy!
|
@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
|
# 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
|
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]
|
possible_first_messages = @messages[0..-skip_messages]
|
||||||
|
|
||||||
|
@ -121,6 +123,8 @@ module DiscourseChat::Provider::SlackProvider
|
||||||
secret = DiscourseChat::Helper.save_transcript(post_content)
|
secret = DiscourseChat::Helper.save_transcript(post_content)
|
||||||
link = "#{Discourse.base_url}/chat-transcript/#{secret}"
|
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")}>",
|
text: "<#{link}|#{I18n.t("chat_integration.provider.slack.transcript.post_to_discourse")}>",
|
||||||
attachments: [
|
attachments: [
|
||||||
|
@ -201,29 +205,35 @@ module DiscourseChat::Provider::SlackProvider
|
||||||
http = Net::HTTP.new("slack.com", 443)
|
http = Net::HTTP.new("slack.com", 443)
|
||||||
http.use_ssl = true
|
http.use_ssl = true
|
||||||
|
|
||||||
req = Net::HTTP::Post.new(URI('https://slack.com/api/channels.history'))
|
endpoint = @requested_thread_ts ? "replies" : "history"
|
||||||
|
|
||||||
|
req = Net::HTTP::Post.new(URI("https://slack.com/api/conversations.#{endpoint}"))
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
token: SiteSetting.chat_integration_slack_access_token,
|
token: SiteSetting.chat_integration_slack_access_token,
|
||||||
channel: @channel_id,
|
channel: @channel_id,
|
||||||
count: count
|
limit: count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data[:ts] = @requested_thread_ts if @requested_thread_ts
|
||||||
|
|
||||||
req.set_form_data(data)
|
req.set_form_data(data)
|
||||||
response = http.request(req)
|
response = http.request(req)
|
||||||
return false unless response.kind_of? Net::HTTPSuccess
|
return false unless response.kind_of? Net::HTTPSuccess
|
||||||
json = JSON.parse(response.body)
|
json = JSON.parse(response.body)
|
||||||
return false unless json['ok']
|
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
|
# Build some message objects
|
||||||
@messages = []
|
@messages = []
|
||||||
raw_messages.each_with_index do |message, index|
|
raw_messages.each_with_index do |message, index|
|
||||||
# Only load messages
|
# Only load messages
|
||||||
next unless message["type"] == "message"
|
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)
|
this_message = SlackMessage.new(message, self)
|
||||||
@messages << this_message
|
@messages << this_message
|
||||||
|
|
|
@ -196,7 +196,7 @@ describe 'Slack Command Controller', type: :request do
|
||||||
context "with valid slack responses" do
|
context "with valid slack responses" do
|
||||||
before do
|
before do
|
||||||
stub_request(:post, "https://slack.com/api/users.list").to_return(body: '{"ok":true,"members":[{"id":"U5Z773QLS","name":"david","profile":{"icon_24":"https://example.com/avatar"}}]}')
|
stub_request(:post, "https://slack.com/api/users.list").to_return(body: '{"ok":true,"members":[{"id":"U5Z773QLS","name":"david","profile":{"icon_24":"https://example.com/avatar"}}]}')
|
||||||
stub_request(:post, "https://slack.com/api/channels.history").to_return(body: { ok: true, messages: messages_fixture }.to_json)
|
stub_request(:post, "https://slack.com/api/conversations.history").to_return(body: { ok: true, messages: messages_fixture }.to_json)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'generates the transcript UI properly' do
|
it 'generates the transcript UI properly' do
|
||||||
|
@ -231,6 +231,46 @@ describe 'Slack Command Controller', type: :request do
|
||||||
expect(command_stub).to have_been_requested
|
expect(command_stub).to have_been_requested
|
||||||
end
|
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
|
it 'can select by count' do
|
||||||
command_stub = stub_request(:post, "https://slack.com/commands/1234")
|
command_stub = stub_request(:post, "https://slack.com/commands/1234")
|
||||||
.with(body: /1501801629\.052212/)
|
.with(body: /1501801629\.052212/)
|
||||||
|
|
|
@ -105,7 +105,7 @@ RSpec.describe DiscourseChat::Provider::SlackProvider::SlackTranscript do
|
||||||
|
|
||||||
describe 'loading history' do
|
describe 'loading history' do
|
||||||
it 'loads messages correctly' do
|
it 'loads messages correctly' do
|
||||||
stub_request(:post, "https://slack.com/api/channels.history")
|
stub_request(:post, "https://slack.com/api/conversations.history")
|
||||||
.with(body: hash_including(token: "abcde", channel: 'G1234'))
|
.with(body: hash_including(token: "abcde", channel: 'G1234'))
|
||||||
.to_return(status: 200, body: { ok: true, messages: messages_fixture }.to_json)
|
.to_return(status: 200, body: { ok: true, messages: messages_fixture }.to_json)
|
||||||
|
|
||||||
|
@ -113,23 +113,43 @@ RSpec.describe DiscourseChat::Provider::SlackProvider::SlackTranscript do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'handles failed connection' do
|
it 'handles failed connection' do
|
||||||
stub_request(:post, "https://slack.com/api/channels.history")
|
stub_request(:post, "https://slack.com/api/conversations.history")
|
||||||
.to_return(status: 500, body: {}.to_json)
|
.to_return(status: 500, body: {}.to_json)
|
||||||
|
|
||||||
expect(transcript.load_chat_history).to be_falsey
|
expect(transcript.load_chat_history).to be_falsey
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'handles slack failure' do
|
it 'handles slack failure' do
|
||||||
stub_request(:post, "https://slack.com/api/channels.history")
|
stub_request(:post, "https://slack.com/api/conversations.history")
|
||||||
.to_return(status: 200, body: { ok: false }.to_json)
|
.to_return(status: 200, body: { ok: false }.to_json)
|
||||||
|
|
||||||
expect(transcript.load_chat_history).to be_falsey
|
expect(transcript.load_chat_history).to be_falsey
|
||||||
end
|
end
|
||||||
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
|
context 'with loaded messages' do
|
||||||
before do
|
before do
|
||||||
stub_request(:post, "https://slack.com/api/channels.history")
|
stub_request(:post, "https://slack.com/api/conversations.history")
|
||||||
.with(body: hash_including(token: "abcde", channel: 'G1234'))
|
.with(body: hash_including(token: "abcde", channel: 'G1234'))
|
||||||
.to_return(status: 200, body: { ok: true, messages: messages_fixture }.to_json)
|
.to_return(status: 200, body: { ok: true, messages: messages_fixture }.to_json)
|
||||||
transcript.load_chat_history
|
transcript.load_chat_history
|
||||||
|
|
Loading…
Reference in New Issue