Merge branch 'pipeline-hooks' into 'master'
Implement Slack integration for pipeline hooks ## What does this MR do? Add pipeline events to Slack integration ## Does this MR meet the acceptance criteria? - [x] [CHANGELOG](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CHANGELOG) entry added - [ ] [Documentation created/updated](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/development/doc_styleguide.md) - Tests - [x] Added for this feature/bug See merge request !5525
This commit is contained in:
commit
4c833a1d4e
8 changed files with 266 additions and 51 deletions
|
@ -66,6 +66,7 @@ v 8.12.0 (unreleased)
|
|||
- Align add button on repository view (ClemMakesApps)
|
||||
- Fix contributions calendar month label truncation (ClemMakesApps)
|
||||
- Added tests for diff notes
|
||||
- Add pipeline events to Slack integration !5525
|
||||
- Add a button to download latest successful artifacts for branches and tags !5142
|
||||
- Remove redundant pipeline tooltips (ClemMakesApps)
|
||||
- Expire commit info views after one day, instead of two weeks, to allow for user email updates
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
class SlackService < Service
|
||||
prop_accessor :webhook, :username, :channel
|
||||
boolean_accessor :notify_only_broken_builds
|
||||
boolean_accessor :notify_only_broken_builds, :notify_only_broken_pipelines
|
||||
validates :webhook, presence: true, url: true, if: :activated?
|
||||
|
||||
def initialize_properties
|
||||
|
@ -10,6 +10,7 @@ class SlackService < Service
|
|||
if properties.nil?
|
||||
self.properties = {}
|
||||
self.notify_only_broken_builds = true
|
||||
self.notify_only_broken_pipelines = true
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -38,13 +39,15 @@ class SlackService < Service
|
|||
{ type: 'text', name: 'username', placeholder: 'username' },
|
||||
{ type: 'text', name: 'channel', placeholder: "#general" },
|
||||
{ type: 'checkbox', name: 'notify_only_broken_builds' },
|
||||
{ type: 'checkbox', name: 'notify_only_broken_pipelines' },
|
||||
]
|
||||
|
||||
default_fields + build_event_channels
|
||||
end
|
||||
|
||||
def supported_events
|
||||
%w(push issue confidential_issue merge_request note tag_push build wiki_page)
|
||||
%w[push issue confidential_issue merge_request note tag_push
|
||||
build pipeline wiki_page]
|
||||
end
|
||||
|
||||
def execute(data)
|
||||
|
@ -62,32 +65,22 @@ class SlackService < Service
|
|||
# 'close' action. Ignore update events for now to prevent duplicate
|
||||
# messages from arriving.
|
||||
|
||||
message = \
|
||||
case object_kind
|
||||
when "push", "tag_push"
|
||||
PushMessage.new(data)
|
||||
when "issue"
|
||||
IssueMessage.new(data) unless is_update?(data)
|
||||
when "merge_request"
|
||||
MergeMessage.new(data) unless is_update?(data)
|
||||
when "note"
|
||||
NoteMessage.new(data)
|
||||
when "build"
|
||||
BuildMessage.new(data) if should_build_be_notified?(data)
|
||||
when "wiki_page"
|
||||
WikiPageMessage.new(data)
|
||||
end
|
||||
|
||||
opt = {}
|
||||
|
||||
event_channel = get_channel_field(object_kind) || channel
|
||||
|
||||
opt[:channel] = event_channel if event_channel
|
||||
opt[:username] = username if username
|
||||
message = get_message(object_kind, data)
|
||||
|
||||
if message
|
||||
opt = {}
|
||||
|
||||
event_channel = get_channel_field(object_kind) || channel
|
||||
|
||||
opt[:channel] = event_channel if event_channel
|
||||
opt[:username] = username if username
|
||||
|
||||
notifier = Slack::Notifier.new(webhook, opt)
|
||||
notifier.ping(message.pretext, attachments: message.attachments, fallback: message.fallback)
|
||||
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -105,6 +98,25 @@ class SlackService < Service
|
|||
|
||||
private
|
||||
|
||||
def get_message(object_kind, data)
|
||||
case object_kind
|
||||
when "push", "tag_push"
|
||||
PushMessage.new(data)
|
||||
when "issue"
|
||||
IssueMessage.new(data) unless is_update?(data)
|
||||
when "merge_request"
|
||||
MergeMessage.new(data) unless is_update?(data)
|
||||
when "note"
|
||||
NoteMessage.new(data)
|
||||
when "build"
|
||||
BuildMessage.new(data) if should_build_be_notified?(data)
|
||||
when "pipeline"
|
||||
PipelineMessage.new(data) if should_pipeline_be_notified?(data)
|
||||
when "wiki_page"
|
||||
WikiPageMessage.new(data)
|
||||
end
|
||||
end
|
||||
|
||||
def get_channel_field(event)
|
||||
field_name = event_channel_name(event)
|
||||
self.public_send(field_name)
|
||||
|
@ -142,6 +154,17 @@ class SlackService < Service
|
|||
false
|
||||
end
|
||||
end
|
||||
|
||||
def should_pipeline_be_notified?(data)
|
||||
case data[:object_attributes][:status]
|
||||
when 'success'
|
||||
!notify_only_broken_pipelines?
|
||||
when 'failed'
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require "slack_service/issue_message"
|
||||
|
@ -149,4 +172,5 @@ require "slack_service/push_message"
|
|||
require "slack_service/merge_message"
|
||||
require "slack_service/note_message"
|
||||
require "slack_service/build_message"
|
||||
require "slack_service/pipeline_message"
|
||||
require "slack_service/wiki_page_message"
|
||||
|
|
|
@ -9,7 +9,7 @@ class SlackService
|
|||
attr_reader :user_name
|
||||
attr_reader :duration
|
||||
|
||||
def initialize(params, commit = true)
|
||||
def initialize(params)
|
||||
@sha = params[:sha]
|
||||
@ref_type = params[:tag] ? 'tag' : 'branch'
|
||||
@ref = params[:ref]
|
||||
|
@ -36,7 +36,7 @@ class SlackService
|
|||
|
||||
def message
|
||||
"#{project_link}: Commit #{commit_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status} in #{duration} #{'second'.pluralize(duration)}"
|
||||
end
|
||||
end
|
||||
|
||||
def format(string)
|
||||
Slack::Notifier::LinkFormatter.format(string)
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
class SlackService
|
||||
class PipelineMessage < BaseMessage
|
||||
attr_reader :sha, :ref_type, :ref, :status, :project_name, :project_url,
|
||||
:user_name, :duration, :pipeline_id
|
||||
|
||||
def initialize(data)
|
||||
pipeline_attributes = data[:object_attributes]
|
||||
@sha = pipeline_attributes[:sha]
|
||||
@ref_type = pipeline_attributes[:tag] ? 'tag' : 'branch'
|
||||
@ref = pipeline_attributes[:ref]
|
||||
@status = pipeline_attributes[:status]
|
||||
@duration = pipeline_attributes[:duration]
|
||||
@pipeline_id = pipeline_attributes[:id]
|
||||
|
||||
@project_name = data[:project][:path_with_namespace]
|
||||
@project_url = data[:project][:web_url]
|
||||
@user_name = data[:commit] && data[:commit][:author_name]
|
||||
end
|
||||
|
||||
def pretext
|
||||
''
|
||||
end
|
||||
|
||||
def fallback
|
||||
format(message)
|
||||
end
|
||||
|
||||
def attachments
|
||||
[{ text: format(message), color: attachment_color }]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def message
|
||||
"#{project_link}: Pipeline #{pipeline_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status} in #{duration} #{'second'.pluralize(duration)}"
|
||||
end
|
||||
|
||||
def format(string)
|
||||
Slack::Notifier::LinkFormatter.format(string)
|
||||
end
|
||||
|
||||
def humanized_status
|
||||
case status
|
||||
when 'success'
|
||||
'passed'
|
||||
else
|
||||
status
|
||||
end
|
||||
end
|
||||
|
||||
def attachment_color
|
||||
if status == 'success'
|
||||
'good'
|
||||
else
|
||||
'danger'
|
||||
end
|
||||
end
|
||||
|
||||
def branch_url
|
||||
"#{project_url}/commits/#{ref}"
|
||||
end
|
||||
|
||||
def branch_link
|
||||
"[#{ref}](#{branch_url})"
|
||||
end
|
||||
|
||||
def project_link
|
||||
"[#{project_name}](#{project_url})"
|
||||
end
|
||||
|
||||
def pipeline_url
|
||||
"#{project_url}/pipelines/#{pipeline_id}"
|
||||
end
|
||||
|
||||
def pipeline_link
|
||||
"[#{Commit.truncate_sha(sha)}](#{pipeline_url})"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -33,6 +33,7 @@ class Spinach::Features::AdminSettings < Spinach::FeatureSteps
|
|||
page.check('Issue')
|
||||
page.check('Merge request')
|
||||
page.check('Build')
|
||||
page.check('Pipeline')
|
||||
click_on 'Save'
|
||||
end
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ describe SlackService::BuildMessage do
|
|||
tag: false,
|
||||
|
||||
project_name: 'project_name',
|
||||
project_url: 'somewhere.com',
|
||||
project_url: 'example.gitlab.com',
|
||||
|
||||
commit: {
|
||||
status: status,
|
||||
|
@ -20,42 +20,38 @@ describe SlackService::BuildMessage do
|
|||
}
|
||||
end
|
||||
|
||||
context 'succeeded' do
|
||||
let(:message) { build_message }
|
||||
|
||||
context 'build succeeded' do
|
||||
let(:status) { 'success' }
|
||||
let(:color) { 'good' }
|
||||
let(:duration) { 10 }
|
||||
|
||||
let(:message) { build_message('passed') }
|
||||
|
||||
it 'returns a message with information about succeeded build' do
|
||||
message = '<somewhere.com|project_name>: Commit <somewhere.com/commit/97de212e80737a608d939f648d959671fb0a0142/builds|97de212e> of <somewhere.com/commits/develop|develop> branch by hacker passed in 10 seconds'
|
||||
expect(subject.pretext).to be_empty
|
||||
expect(subject.fallback).to eq(message)
|
||||
expect(subject.attachments).to eq([text: message, color: color])
|
||||
end
|
||||
end
|
||||
|
||||
context 'failed' do
|
||||
context 'build failed' do
|
||||
let(:status) { 'failed' }
|
||||
let(:color) { 'danger' }
|
||||
let(:duration) { 10 }
|
||||
|
||||
it 'returns a message with information about failed build' do
|
||||
message = '<somewhere.com|project_name>: Commit <somewhere.com/commit/97de212e80737a608d939f648d959671fb0a0142/builds|97de212e> of <somewhere.com/commits/develop|develop> branch by hacker failed in 10 seconds'
|
||||
expect(subject.pretext).to be_empty
|
||||
expect(subject.fallback).to eq(message)
|
||||
expect(subject.attachments).to eq([text: message, color: color])
|
||||
end
|
||||
end
|
||||
|
||||
describe '#seconds_name' do
|
||||
let(:status) { 'failed' }
|
||||
let(:color) { 'danger' }
|
||||
let(:duration) { 1 }
|
||||
|
||||
it 'returns seconds as singular when there is only one' do
|
||||
message = '<somewhere.com|project_name>: Commit <somewhere.com/commit/97de212e80737a608d939f648d959671fb0a0142/builds|97de212e> of <somewhere.com/commits/develop|develop> branch by hacker failed in 1 second'
|
||||
expect(subject.pretext).to be_empty
|
||||
expect(subject.fallback).to eq(message)
|
||||
expect(subject.attachments).to eq([text: message, color: color])
|
||||
end
|
||||
end
|
||||
|
||||
def build_message(status_text = status)
|
||||
"<example.gitlab.com|project_name>:" \
|
||||
" Commit <example.gitlab.com/commit/" \
|
||||
"97de212e80737a608d939f648d959671fb0a0142/builds|97de212e>" \
|
||||
" of <example.gitlab.com/commits/develop|develop> branch" \
|
||||
" by hacker #{status_text} in #{duration} #{'second'.pluralize(duration)}"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe SlackService::PipelineMessage do
|
||||
subject { SlackService::PipelineMessage.new(args) }
|
||||
|
||||
let(:args) do
|
||||
{
|
||||
object_attributes: {
|
||||
id: 123,
|
||||
sha: '97de212e80737a608d939f648d959671fb0a0142',
|
||||
tag: false,
|
||||
ref: 'develop',
|
||||
status: status,
|
||||
duration: duration
|
||||
},
|
||||
project: { path_with_namespace: 'project_name',
|
||||
web_url: 'example.gitlab.com' },
|
||||
commit: { author_name: 'hacker' }
|
||||
}
|
||||
end
|
||||
|
||||
let(:message) { build_message }
|
||||
|
||||
context 'pipeline succeeded' do
|
||||
let(:status) { 'success' }
|
||||
let(:color) { 'good' }
|
||||
let(:duration) { 10 }
|
||||
let(:message) { build_message('passed') }
|
||||
|
||||
it 'returns a message with information about succeeded build' do
|
||||
expect(subject.pretext).to be_empty
|
||||
expect(subject.fallback).to eq(message)
|
||||
expect(subject.attachments).to eq([text: message, color: color])
|
||||
end
|
||||
end
|
||||
|
||||
context 'pipeline failed' do
|
||||
let(:status) { 'failed' }
|
||||
let(:color) { 'danger' }
|
||||
let(:duration) { 10 }
|
||||
|
||||
it 'returns a message with information about failed build' do
|
||||
expect(subject.pretext).to be_empty
|
||||
expect(subject.fallback).to eq(message)
|
||||
expect(subject.attachments).to eq([text: message, color: color])
|
||||
end
|
||||
end
|
||||
|
||||
def build_message(status_text = status)
|
||||
"<example.gitlab.com|project_name>:" \
|
||||
" Pipeline <example.gitlab.com/pipelines/123|97de212e>" \
|
||||
" of <example.gitlab.com/commits/develop|develop> branch" \
|
||||
" by hacker #{status_text} in #{duration} #{'second'.pluralize(duration)}"
|
||||
end
|
||||
end
|
|
@ -21,6 +21,9 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe SlackService, models: true do
|
||||
let(:slack) { SlackService.new }
|
||||
let(:webhook_url) { 'https://example.gitlab.com/' }
|
||||
|
||||
describe "Associations" do
|
||||
it { is_expected.to belong_to :project }
|
||||
it { is_expected.to have_one :service_hook }
|
||||
|
@ -42,15 +45,14 @@ describe SlackService, models: true do
|
|||
end
|
||||
|
||||
describe "Execute" do
|
||||
let(:slack) { SlackService.new }
|
||||
let(:user) { create(:user) }
|
||||
let(:project) { create(:project) }
|
||||
let(:username) { 'slack_username' }
|
||||
let(:channel) { 'slack_channel' }
|
||||
|
||||
let(:push_sample_data) do
|
||||
Gitlab::DataBuilder::Push.build_sample(project, user)
|
||||
end
|
||||
let(:webhook_url) { 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685' }
|
||||
let(:username) { 'slack_username' }
|
||||
let(:channel) { 'slack_channel' }
|
||||
|
||||
before do
|
||||
allow(slack).to receive_messages(
|
||||
|
@ -212,10 +214,8 @@ describe SlackService, models: true do
|
|||
end
|
||||
|
||||
describe "Note events" do
|
||||
let(:slack) { SlackService.new }
|
||||
let(:user) { create(:user) }
|
||||
let(:project) { create(:project, creator_id: user.id) }
|
||||
let(:webhook_url) { 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685' }
|
||||
|
||||
before do
|
||||
allow(slack).to receive_messages(
|
||||
|
@ -285,4 +285,63 @@ describe SlackService, models: true do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Pipeline events' do
|
||||
let(:user) { create(:user) }
|
||||
let(:project) { create(:project) }
|
||||
|
||||
let(:pipeline) do
|
||||
create(:ci_pipeline,
|
||||
project: project, status: status,
|
||||
sha: project.commit.sha, ref: project.default_branch)
|
||||
end
|
||||
|
||||
before do
|
||||
allow(slack).to receive_messages(
|
||||
project: project,
|
||||
service_hook: true,
|
||||
webhook: webhook_url
|
||||
)
|
||||
end
|
||||
|
||||
shared_examples 'call Slack API' do
|
||||
before do
|
||||
WebMock.stub_request(:post, webhook_url)
|
||||
end
|
||||
|
||||
it 'calls Slack API for pipeline events' do
|
||||
data = Gitlab::DataBuilder::Pipeline.build(pipeline)
|
||||
slack.execute(data)
|
||||
|
||||
expect(WebMock).to have_requested(:post, webhook_url).once
|
||||
end
|
||||
end
|
||||
|
||||
context 'with failed pipeline' do
|
||||
let(:status) { 'failed' }
|
||||
|
||||
it_behaves_like 'call Slack API'
|
||||
end
|
||||
|
||||
context 'with succeeded pipeline' do
|
||||
let(:status) { 'success' }
|
||||
|
||||
context 'with default to notify_only_broken_pipelines' do
|
||||
it 'does not call Slack API for pipeline events' do
|
||||
data = Gitlab::DataBuilder::Pipeline.build(pipeline)
|
||||
result = slack.execute(data)
|
||||
|
||||
expect(result).to be_falsy
|
||||
end
|
||||
end
|
||||
|
||||
context 'with setting notify_only_broken_pipelines to false' do
|
||||
before do
|
||||
slack.notify_only_broken_pipelines = false
|
||||
end
|
||||
|
||||
it_behaves_like 'call Slack API'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue