Commit b4742f97 authored by Brian Kintz's avatar Brian Kintz Committed by Mayra Cabrera

Add chatops responder for mattermost

parent 2bbc2053
...@@ -36,6 +36,10 @@ class MattermostSlashCommandsService < SlashCommandsService ...@@ -36,6 +36,10 @@ class MattermostSlashCommandsService < SlashCommandsService
[[], e.message] [[], e.message]
end end
def chat_responder
::Gitlab::Chat::Responder::Mattermost
end
private private
def command(params) def command(params)
......
---
title: Add responding to ChatOps jobs triggered in Mattermost
merge_request: 29366
author: Brian Kintz
type: added
# frozen_string_literal: true
module Gitlab
module Chat
module Responder
class Mattermost < Responder::Base
SUCCESS_COLOR = '#00c100'
FAILURE_COLOR = '#e40303'
# Slack breaks messages apart if they're around 4 KB in size. We use a
# slightly smaller limit here to account for user mentions.
MESSAGE_SIZE_LIMIT = 3.5.kilobytes
# Sends a response back to Mattermost
#
# body - The message payload to send back to Mattermost, as a Hash.
def send_response(body)
Gitlab::HTTP.post(
pipeline.chat_data.response_url,
{
headers: { 'Content-Type': 'application/json' },
body: body.to_json
}
)
end
# Sends the output for a build that completed successfully.
#
# output - The output produced by the chat command.
def success(output)
return if output.empty?
send_response(
response_type: :in_channel,
attachments: [
{
color: SUCCESS_COLOR,
text: "ChatOps job started by #{user_ref} completed successfully",
fields: [
{
short: true,
title: "ID",
value: "#{build_ref}"
},
{
short: true,
title: "Name",
value: build.name
},
{
short: false,
title: "Output",
value: success_message(output)
}
]
}
]
)
end
# Sends the output for a build that failed.
def failure
send_response(
response_type: :in_channel,
attachments: [
{
color: FAILURE_COLOR,
text: "ChatOps job started by #{user_ref} failed!",
fields: [
{
short: true,
title: "ID",
value: "#{build_ref}"
},
{
short: true,
title: "Name",
value: build.name
}
]
}
]
)
end
# Returns the output to send back after a command has been scheduled.
def scheduled_output
{
response_type: :ephemeral,
text: "Your ChatOps job #{build_ref} has been created!"
}
end
private
def success_message(output)
<<~HEREDOC.chomp
```shell
#{strip_ansi_colorcodes(limit_output(output))}
```
HEREDOC
end
def limit_output(output)
if output.bytesize <= MESSAGE_SIZE_LIMIT
output
else
"The output is too large to be sent back directly!"
end
end
def strip_ansi_colorcodes(output)
output.gsub(/\x1b\[[0-9;]*m/, '')
end
def user_ref
user = pipeline.chat_data.chat_name.user
user_url = ::Gitlab::Routing.url_helpers.user_url(user)
"[#{user.name}](#{user_url})"
end
def build_ref
build_url = ::Gitlab::Routing.url_helpers.project_build_url(project, build)
"[##{build.id}](#{build_url})"
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Chat::Responder::Mattermost do
let(:chat_name) { create(:chat_name, chat_id: 'U123') }
let(:pipeline) do
pipeline = create(:ci_pipeline)
pipeline.create_chat_data!(
response_url: 'http://example.com',
chat_name_id: chat_name.id
)
pipeline
end
let(:build) { create(:ci_build, pipeline: pipeline) }
let(:responder) { described_class.new(build) }
describe '#send_response' do
it 'sends a response back to Slack' do
expect(Gitlab::HTTP).to receive(:post).with(
'http://example.com',
{ headers: { 'Content-Type': 'application/json' }, body: 'hello'.to_json }
)
responder.send_response('hello')
end
end
describe '#success' do
it 'returns the output for a successful build' do
expect(responder)
.to receive(:send_response)
.with(
hash_including(
response_type: :in_channel,
attachments: array_including(
a_hash_including(
text: /#{pipeline.chat_data.chat_name.user.name}.*completed successfully/,
fields: array_including(
a_hash_including(value: /##{build.id}/),
a_hash_including(value: build.name),
a_hash_including(value: "```shell\nscript output\n```")
)
)
)
)
)
responder.success('script output')
end
it 'limits the output to a fixed size' do
expect(responder)
.to receive(:send_response)
.with(
hash_including(
response_type: :in_channel,
attachments: array_including(
a_hash_including(
fields: array_including(
a_hash_including(value: /The output is too large/)
)
)
)
)
)
responder.success('a' * 4000)
end
it 'does not send a response if the output is empty' do
expect(responder).not_to receive(:send_response)
responder.success('')
end
end
describe '#failure' do
it 'returns the output for a failed build' do
expect(responder)
.to receive(:send_response)
.with(
hash_including(
response_type: :in_channel,
attachments: array_including(
a_hash_including(
text: /#{pipeline.chat_data.chat_name.user.name}.*failed/,
fields: array_including(
a_hash_including(value: /##{build.id}/),
a_hash_including(value: build.name)
)
)
)
)
)
responder.failure
end
end
describe '#scheduled_output' do
it 'returns the output for a scheduled build' do
output = responder.scheduled_output
expect(output).to match(
hash_including(
response_type: :ephemeral,
text: /##{build.id}/
)
)
end
end
end
...@@ -121,5 +121,12 @@ describe MattermostSlashCommandsService do ...@@ -121,5 +121,12 @@ describe MattermostSlashCommandsService do
end end
end end
end end
describe '#chat_responder' do
it 'returns the responder to use for Mattermost' do
expect(described_class.new.chat_responder)
.to eq(Gitlab::Chat::Responder::Mattermost)
end
end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment