Merge branch '32495-improve-slack-notification-on-pipeline-status' into 'master'
Make pipeline failure Slack notifications prettier and more informative Closes #32495 See merge request gitlab-org/gitlab-ce!27683
This commit is contained in:
commit
c712340f41
|
@ -1,24 +1,47 @@
|
|||
# frozen_string_literal: true
|
||||
require 'slack-notifier'
|
||||
|
||||
module ChatMessage
|
||||
class PipelineMessage < BaseMessage
|
||||
MAX_VISIBLE_JOBS = 10
|
||||
|
||||
attr_reader :user
|
||||
attr_reader :ref_type
|
||||
attr_reader :ref
|
||||
attr_reader :status
|
||||
attr_reader :detailed_status
|
||||
attr_reader :duration
|
||||
attr_reader :finished_at
|
||||
attr_reader :pipeline_id
|
||||
attr_reader :failed_stages
|
||||
attr_reader :failed_jobs
|
||||
|
||||
attr_reader :project
|
||||
attr_reader :commit
|
||||
attr_reader :committer
|
||||
attr_reader :pipeline
|
||||
|
||||
def initialize(data)
|
||||
super
|
||||
|
||||
@user = data[:user]
|
||||
@user_name = data.dig(:user, :username) || 'API'
|
||||
|
||||
pipeline_attributes = data[:object_attributes]
|
||||
@ref_type = pipeline_attributes[:tag] ? 'tag' : 'branch'
|
||||
@ref = pipeline_attributes[:ref]
|
||||
@status = pipeline_attributes[:status]
|
||||
@detailed_status = pipeline_attributes[:detailed_status]
|
||||
@duration = pipeline_attributes[:duration].to_i
|
||||
@finished_at = pipeline_attributes[:finished_at] ? Time.parse(pipeline_attributes[:finished_at]).to_i : nil
|
||||
@pipeline_id = pipeline_attributes[:id]
|
||||
@failed_jobs = Array(data[:builds]).select { |b| b[:status] == 'failed' }.reverse # Show failed jobs from oldest to newest
|
||||
@failed_stages = @failed_jobs.map { |j| j[:stage] }.uniq
|
||||
|
||||
@project = Project.find(data[:project][:id])
|
||||
@commit = project.commit_by(oid: data[:commit][:id])
|
||||
@committer = commit.committer
|
||||
@pipeline = Ci::Pipeline.find(pipeline_id)
|
||||
end
|
||||
|
||||
def pretext
|
||||
|
@ -28,38 +51,145 @@ module ChatMessage
|
|||
def attachments
|
||||
return message if markdown
|
||||
|
||||
[{ text: format(message), color: attachment_color }]
|
||||
return [{ text: format(message), color: attachment_color }] unless fancy_notifications?
|
||||
|
||||
[{
|
||||
fallback: format(message),
|
||||
color: attachment_color,
|
||||
author_name: user_combined_name,
|
||||
author_icon: user_avatar,
|
||||
author_link: author_url,
|
||||
title: s_("ChatMessage|Pipeline #%{pipeline_id} %{humanized_status} in %{duration}") %
|
||||
{
|
||||
pipeline_id: pipeline_id,
|
||||
humanized_status: humanized_status,
|
||||
duration: pretty_duration(duration)
|
||||
},
|
||||
title_link: pipeline_url,
|
||||
fields: attachments_fields,
|
||||
footer: project.name,
|
||||
footer_icon: project.avatar_url,
|
||||
ts: finished_at
|
||||
}]
|
||||
end
|
||||
|
||||
def activity
|
||||
{
|
||||
title: "Pipeline #{pipeline_link} of #{ref_type} #{branch_link} by #{user_combined_name} #{humanized_status}",
|
||||
subtitle: "in #{project_link}",
|
||||
text: "in #{pretty_duration(duration)}",
|
||||
title: s_("ChatMessage|Pipeline %{pipeline_link} of %{ref_type} %{branch_link} by %{user_combined_name} %{humanized_status}") %
|
||||
{
|
||||
pipeline_link: pipeline_link,
|
||||
ref_type: ref_type,
|
||||
branch_link: branch_link,
|
||||
user_combined_name: user_combined_name,
|
||||
humanized_status: humanized_status
|
||||
},
|
||||
subtitle: s_("ChatMessage|in %{project_link}") % { project_link: project_link },
|
||||
text: s_("ChatMessage|in %{duration}") % { duration: pretty_duration(duration) },
|
||||
image: user_avatar || ''
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fancy_notifications?
|
||||
Feature.enabled?(:fancy_pipeline_slack_notifications, default_enabled: true)
|
||||
end
|
||||
|
||||
def failed_stages_field
|
||||
{
|
||||
title: s_("ChatMessage|Failed stage").pluralize(failed_stages.length),
|
||||
value: Slack::Notifier::LinkFormatter.format(failed_stages_links),
|
||||
short: true
|
||||
}
|
||||
end
|
||||
|
||||
def failed_jobs_field
|
||||
{
|
||||
title: s_("ChatMessage|Failed job").pluralize(failed_jobs.length),
|
||||
value: Slack::Notifier::LinkFormatter.format(failed_jobs_links),
|
||||
short: true
|
||||
}
|
||||
end
|
||||
|
||||
def yaml_error_field
|
||||
{
|
||||
title: s_("ChatMessage|Invalid CI config YAML file"),
|
||||
value: pipeline.yaml_errors,
|
||||
short: false
|
||||
}
|
||||
end
|
||||
|
||||
def attachments_fields
|
||||
fields = [
|
||||
{
|
||||
title: ref_type == "tag" ? s_("ChatMessage|Tag") : s_("ChatMessage|Branch"),
|
||||
value: Slack::Notifier::LinkFormatter.format(ref_name_link),
|
||||
short: true
|
||||
},
|
||||
{
|
||||
title: s_("ChatMessage|Commit"),
|
||||
value: Slack::Notifier::LinkFormatter.format(commit_link),
|
||||
short: true
|
||||
}
|
||||
]
|
||||
|
||||
fields << failed_stages_field if failed_stages.any?
|
||||
fields << failed_jobs_field if failed_jobs.any?
|
||||
fields << yaml_error_field if pipeline.has_yaml_errors?
|
||||
|
||||
fields
|
||||
end
|
||||
|
||||
def message
|
||||
"#{project_link}: Pipeline #{pipeline_link} of #{ref_type} #{branch_link} by #{user_combined_name} #{humanized_status} in #{pretty_duration(duration)}"
|
||||
s_("ChatMessage|%{project_link}: Pipeline %{pipeline_link} of %{ref_type} %{branch_link} by %{user_combined_name} %{humanized_status} in %{duration}") %
|
||||
{
|
||||
project_link: project_link,
|
||||
pipeline_link: pipeline_link,
|
||||
ref_type: ref_type,
|
||||
branch_link: branch_link,
|
||||
user_combined_name: user_combined_name,
|
||||
humanized_status: humanized_status,
|
||||
duration: pretty_duration(duration)
|
||||
}
|
||||
end
|
||||
|
||||
def humanized_status
|
||||
case status
|
||||
when 'success'
|
||||
'passed'
|
||||
if fancy_notifications?
|
||||
case status
|
||||
when 'success'
|
||||
detailed_status == "passed with warnings" ? s_("ChatMessage|has passed with warnings") : s_("ChatMessage|has passed")
|
||||
when 'failed'
|
||||
s_("ChatMessage|has failed")
|
||||
else
|
||||
status
|
||||
end
|
||||
else
|
||||
status
|
||||
case status
|
||||
when 'success'
|
||||
s_("ChatMessage|passed")
|
||||
when 'failed'
|
||||
s_("ChatMessage|failed")
|
||||
else
|
||||
status
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def attachment_color
|
||||
if status == 'success'
|
||||
'good'
|
||||
if fancy_notifications?
|
||||
case status
|
||||
when 'success'
|
||||
detailed_status == 'passed with warnings' ? 'warning' : 'good'
|
||||
else
|
||||
'danger'
|
||||
end
|
||||
else
|
||||
'danger'
|
||||
case status
|
||||
when 'success'
|
||||
'good'
|
||||
else
|
||||
'danger'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -71,16 +201,83 @@ module ChatMessage
|
|||
"[#{ref}](#{branch_url})"
|
||||
end
|
||||
|
||||
def project_url
|
||||
project.web_url
|
||||
end
|
||||
|
||||
def project_link
|
||||
"[#{project_name}](#{project_url})"
|
||||
"[#{project.name}](#{project_url})"
|
||||
end
|
||||
|
||||
def pipeline_failed_jobs_url
|
||||
"#{project_url}/pipelines/#{pipeline_id}/failures"
|
||||
end
|
||||
|
||||
def pipeline_url
|
||||
"#{project_url}/pipelines/#{pipeline_id}"
|
||||
if fancy_notifications? && failed_jobs.any?
|
||||
pipeline_failed_jobs_url
|
||||
else
|
||||
"#{project_url}/pipelines/#{pipeline_id}"
|
||||
end
|
||||
end
|
||||
|
||||
def pipeline_link
|
||||
"[##{pipeline_id}](#{pipeline_url})"
|
||||
end
|
||||
|
||||
def job_url(job)
|
||||
"#{project_url}/-/jobs/#{job[:id]}"
|
||||
end
|
||||
|
||||
def job_link(job)
|
||||
"[#{job[:name]}](#{job_url(job)})"
|
||||
end
|
||||
|
||||
def failed_jobs_links
|
||||
failed = failed_jobs.slice(0, MAX_VISIBLE_JOBS)
|
||||
truncated = failed_jobs.slice(MAX_VISIBLE_JOBS, failed_jobs.size)
|
||||
|
||||
failed_links = failed.map { |job| job_link(job) }
|
||||
|
||||
unless truncated.blank?
|
||||
failed_links << s_("ChatMessage|and [%{count} more](%{pipeline_failed_jobs_url})") % {
|
||||
count: truncated.size,
|
||||
pipeline_failed_jobs_url: pipeline_failed_jobs_url
|
||||
}
|
||||
end
|
||||
|
||||
failed_links.join(I18n.translate(:'support.array.words_connector'))
|
||||
end
|
||||
|
||||
def stage_link(stage)
|
||||
# All stages link to the pipeline page
|
||||
"[#{stage}](#{pipeline_url})"
|
||||
end
|
||||
|
||||
def failed_stages_links
|
||||
failed_stages.map { |s| stage_link(s) }.join(I18n.translate(:'support.array.words_connector'))
|
||||
end
|
||||
|
||||
def commit_url
|
||||
Gitlab::UrlBuilder.build(commit)
|
||||
end
|
||||
|
||||
def commit_link
|
||||
"[#{commit.title}](#{commit_url})"
|
||||
end
|
||||
|
||||
def commits_page_url
|
||||
"#{project_url}/commits/#{ref}"
|
||||
end
|
||||
|
||||
def ref_name_link
|
||||
"[#{ref}](#{commits_page_url})"
|
||||
end
|
||||
|
||||
def author_url
|
||||
return unless user && committer
|
||||
|
||||
Gitlab::UrlBuilder.build(committer)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Improve pipeline status Slack notifications
|
||||
merge_request: 27683
|
||||
author:
|
||||
type: added
|
|
@ -2020,6 +2020,57 @@ msgstr ""
|
|||
msgid "Chat"
|
||||
msgstr ""
|
||||
|
||||
msgid "ChatMessage|%{project_link}: Pipeline %{pipeline_link} of %{ref_type} %{branch_link} by %{user_combined_name} %{humanized_status} in %{duration}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ChatMessage|Branch"
|
||||
msgstr ""
|
||||
|
||||
msgid "ChatMessage|Commit"
|
||||
msgstr ""
|
||||
|
||||
msgid "ChatMessage|Failed job"
|
||||
msgstr ""
|
||||
|
||||
msgid "ChatMessage|Failed stage"
|
||||
msgstr ""
|
||||
|
||||
msgid "ChatMessage|Invalid CI config YAML file"
|
||||
msgstr ""
|
||||
|
||||
msgid "ChatMessage|Pipeline #%{pipeline_id} %{humanized_status} in %{duration}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ChatMessage|Pipeline %{pipeline_link} of %{ref_type} %{branch_link} by %{user_combined_name} %{humanized_status}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ChatMessage|Tag"
|
||||
msgstr ""
|
||||
|
||||
msgid "ChatMessage|and [%{count} more](%{pipeline_failed_jobs_url})"
|
||||
msgstr ""
|
||||
|
||||
msgid "ChatMessage|failed"
|
||||
msgstr ""
|
||||
|
||||
msgid "ChatMessage|has failed"
|
||||
msgstr ""
|
||||
|
||||
msgid "ChatMessage|has passed"
|
||||
msgstr ""
|
||||
|
||||
msgid "ChatMessage|has passed with warnings"
|
||||
msgstr ""
|
||||
|
||||
msgid "ChatMessage|in %{duration}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ChatMessage|in %{project_link}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ChatMessage|passed"
|
||||
msgstr ""
|
||||
|
||||
msgid "Check again"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe ChatMessage::PipelineMessage do
|
||||
subject { described_class.new(args) }
|
||||
|
||||
let(:user) { { name: "The Hacker", username: 'hacker' } }
|
||||
let(:duration) { 7210 }
|
||||
let(:args) do
|
||||
{
|
||||
object_attributes: {
|
||||
|
@ -14,122 +11,437 @@ describe ChatMessage::PipelineMessage do
|
|||
sha: '97de212e80737a608d939f648d959671fb0a0142',
|
||||
tag: false,
|
||||
ref: 'develop',
|
||||
status: status,
|
||||
duration: duration
|
||||
status: 'success',
|
||||
detailed_status: nil,
|
||||
duration: 7210,
|
||||
finished_at: "2019-05-27 11:56:36 -0300"
|
||||
},
|
||||
project: {
|
||||
path_with_namespace: 'project_name',
|
||||
web_url: 'http://example.gitlab.com'
|
||||
id: 234,
|
||||
name: "project_name",
|
||||
path_with_namespace: 'group/project_name',
|
||||
web_url: 'http://example.gitlab.com',
|
||||
avatar_url: 'http://example.com/project_avatar'
|
||||
},
|
||||
user: user
|
||||
user: {
|
||||
id: 345,
|
||||
name: "The Hacker",
|
||||
username: "hacker",
|
||||
email: "hacker@example.gitlab.com",
|
||||
avatar_url: "http://example.com/avatar"
|
||||
},
|
||||
commit: {
|
||||
id: "abcdef"
|
||||
},
|
||||
builds: nil,
|
||||
markdown: false
|
||||
}
|
||||
end
|
||||
let(:combined_name) { "The Hacker (hacker)" }
|
||||
|
||||
context 'without markdown' do
|
||||
context 'pipeline succeeded' do
|
||||
let(:status) { 'success' }
|
||||
let(:color) { 'good' }
|
||||
let(:message) { build_message('passed', combined_name) }
|
||||
let(:has_yaml_errors) { false }
|
||||
|
||||
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])
|
||||
before do
|
||||
test_commit = double("A test commit", committer: args[:user], title: "A test commit message")
|
||||
test_project = double("A test project",
|
||||
commit_by: test_commit, name: args[:project][:name],
|
||||
web_url: args[:project][:web_url], avatar_url: args[:project][:avatar_url])
|
||||
allow(Project).to receive(:find) { test_project }
|
||||
|
||||
test_pipeline = double("A test pipeline", has_yaml_errors?: has_yaml_errors,
|
||||
yaml_errors: "yaml error description here")
|
||||
allow(Ci::Pipeline).to receive(:find) { test_pipeline }
|
||||
|
||||
allow(Gitlab::UrlBuilder).to receive(:build).with(test_commit).and_return("http://example.com/commit")
|
||||
allow(Gitlab::UrlBuilder).to receive(:build).with(args[:user]).and_return("http://example.gitlab.com/hacker")
|
||||
end
|
||||
|
||||
context 'when the fancy_pipeline_slack_notifications feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(fancy_pipeline_slack_notifications: false)
|
||||
end
|
||||
|
||||
it 'returns an empty pretext' do
|
||||
expect(subject.pretext).to be_empty
|
||||
end
|
||||
|
||||
it "returns the pipeline summary in the activity's title" do
|
||||
expect(subject.activity[:title]).to eq(
|
||||
"Pipeline [#123](http://example.gitlab.com/pipelines/123)" \
|
||||
" of branch [develop](http://example.gitlab.com/commits/develop)" \
|
||||
" by The Hacker (hacker) passed"
|
||||
)
|
||||
end
|
||||
|
||||
context "when the pipeline failed" do
|
||||
before do
|
||||
args[:object_attributes][:status] = 'failed'
|
||||
end
|
||||
|
||||
it "returns the summary with a 'failed' status" do
|
||||
expect(subject.activity[:title]).to eq(
|
||||
"Pipeline [#123](http://example.gitlab.com/pipelines/123)" \
|
||||
" of branch [develop](http://example.gitlab.com/commits/develop)" \
|
||||
" by The Hacker (hacker) failed"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'pipeline failed' do
|
||||
let(:status) { 'failed' }
|
||||
let(:color) { 'danger' }
|
||||
let(:message) { build_message(status, combined_name) }
|
||||
|
||||
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])
|
||||
context 'when no user is provided because the pipeline was triggered by the API' do
|
||||
before do
|
||||
args[:user] = nil
|
||||
end
|
||||
|
||||
context 'when triggered by API therefore lacking user' do
|
||||
let(:user) { nil }
|
||||
let(:message) { build_message(status, 'API') }
|
||||
|
||||
it 'returns a message stating it is by API' do
|
||||
expect(subject.pretext).to be_empty
|
||||
expect(subject.fallback).to eq(message)
|
||||
expect(subject.attachments).to eq([text: message, color: color])
|
||||
end
|
||||
it "returns the summary with 'API' as the username" do
|
||||
expect(subject.activity[:title]).to eq(
|
||||
"Pipeline [#123](http://example.gitlab.com/pipelines/123)" \
|
||||
" of branch [develop](http://example.gitlab.com/commits/develop)" \
|
||||
" by API passed"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def build_message(status_text = status, name = user[:name])
|
||||
"<http://example.gitlab.com|project_name>:" \
|
||||
" Pipeline <http://example.gitlab.com/pipelines/123|#123>" \
|
||||
" of branch <http://example.gitlab.com/commits/develop|develop>" \
|
||||
" by #{name} #{status_text} in 02:00:10"
|
||||
it "returns a link to the project in the activity's subtitle" do
|
||||
expect(subject.activity[:subtitle]).to eq("in [project_name](http://example.gitlab.com)")
|
||||
end
|
||||
|
||||
it "returns the build duration in the activity's text property" do
|
||||
expect(subject.activity[:text]).to eq("in 02:00:10")
|
||||
end
|
||||
|
||||
it "returns the user's avatar image URL in the activity's image property" do
|
||||
expect(subject.activity[:image]).to eq("http://example.com/avatar")
|
||||
end
|
||||
|
||||
context 'when the user does not have an avatar' do
|
||||
before do
|
||||
args[:user][:avatar_url] = nil
|
||||
end
|
||||
|
||||
it "returns an empty string in the activity's image property" do
|
||||
expect(subject.activity[:image]).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
it "returns the pipeline summary as the attachment's text property" do
|
||||
expect(subject.attachments.first[:text]).to eq(
|
||||
"<http://example.gitlab.com|project_name>:" \
|
||||
" Pipeline <http://example.gitlab.com/pipelines/123|#123>" \
|
||||
" of branch <http://example.gitlab.com/commits/develop|develop>" \
|
||||
" by The Hacker (hacker) passed in 02:00:10"
|
||||
)
|
||||
end
|
||||
|
||||
it "returns 'good' as the attachment's color property" do
|
||||
expect(subject.attachments.first[:color]).to eq('good')
|
||||
end
|
||||
|
||||
context "when the pipeline failed" do
|
||||
before do
|
||||
args[:object_attributes][:status] = 'failed'
|
||||
end
|
||||
|
||||
it "returns 'danger' as the attachment's color property" do
|
||||
expect(subject.attachments.first[:color]).to eq('danger')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when rendering markdown' do
|
||||
before do
|
||||
args[:markdown] = true
|
||||
end
|
||||
|
||||
it 'returns the pipeline summary as the attachments in markdown format' do
|
||||
expect(subject.attachments).to eq(
|
||||
"[project_name](http://example.gitlab.com):" \
|
||||
" Pipeline [#123](http://example.gitlab.com/pipelines/123)" \
|
||||
" of branch [develop](http://example.gitlab.com/commits/develop)" \
|
||||
" by The Hacker (hacker) passed in 02:00:10"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with markdown' do
|
||||
context 'when the fancy_pipeline_slack_notifications feature flag is enabled' do
|
||||
before do
|
||||
args[:markdown] = true
|
||||
stub_feature_flags(fancy_pipeline_slack_notifications: true)
|
||||
end
|
||||
|
||||
context 'pipeline succeeded' do
|
||||
let(:status) { 'success' }
|
||||
let(:color) { 'good' }
|
||||
let(:message) { build_markdown_message('passed', combined_name) }
|
||||
it 'returns an empty pretext' do
|
||||
expect(subject.pretext).to be_empty
|
||||
end
|
||||
|
||||
it 'returns a message with information about succeeded build' do
|
||||
expect(subject.pretext).to be_empty
|
||||
expect(subject.attachments).to eq(message)
|
||||
expect(subject.activity).to eq({
|
||||
title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of branch [develop](http://example.gitlab.com/commits/develop) by The Hacker (hacker) passed',
|
||||
subtitle: 'in [project_name](http://example.gitlab.com)',
|
||||
text: 'in 02:00:10',
|
||||
image: ''
|
||||
it "returns the pipeline summary in the activity's title" do
|
||||
expect(subject.activity[:title]).to eq(
|
||||
"Pipeline [#123](http://example.gitlab.com/pipelines/123)" \
|
||||
" of branch [develop](http://example.gitlab.com/commits/develop)" \
|
||||
" by The Hacker (hacker) has passed"
|
||||
)
|
||||
end
|
||||
|
||||
context "when the pipeline failed" do
|
||||
before do
|
||||
args[:object_attributes][:status] = 'failed'
|
||||
end
|
||||
|
||||
it "returns the summary with a 'failed' status" do
|
||||
expect(subject.activity[:title]).to eq(
|
||||
"Pipeline [#123](http://example.gitlab.com/pipelines/123)" \
|
||||
" of branch [develop](http://example.gitlab.com/commits/develop)" \
|
||||
" by The Hacker (hacker) has failed"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context "when the pipeline passed with warnings" do
|
||||
before do
|
||||
args[:object_attributes][:detailed_status] = 'passed with warnings'
|
||||
end
|
||||
|
||||
it "returns the summary with a 'passed with warnings' status" do
|
||||
expect(subject.activity[:title]).to eq(
|
||||
"Pipeline [#123](http://example.gitlab.com/pipelines/123)" \
|
||||
" of branch [develop](http://example.gitlab.com/commits/develop)" \
|
||||
" by The Hacker (hacker) has passed with warnings"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when no user is provided because the pipeline was triggered by the API' do
|
||||
before do
|
||||
args[:user] = nil
|
||||
end
|
||||
|
||||
it "returns the summary with 'API' as the username" do
|
||||
expect(subject.activity[:title]).to eq(
|
||||
"Pipeline [#123](http://example.gitlab.com/pipelines/123)" \
|
||||
" of branch [develop](http://example.gitlab.com/commits/develop)" \
|
||||
" by API has passed"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
it "returns a link to the project in the activity's subtitle" do
|
||||
expect(subject.activity[:subtitle]).to eq("in [project_name](http://example.gitlab.com)")
|
||||
end
|
||||
|
||||
it "returns the build duration in the activity's text property" do
|
||||
expect(subject.activity[:text]).to eq("in 02:00:10")
|
||||
end
|
||||
|
||||
it "returns the user's avatar image URL in the activity's image property" do
|
||||
expect(subject.activity[:image]).to eq("http://example.com/avatar")
|
||||
end
|
||||
|
||||
context 'when the user does not have an avatar' do
|
||||
before do
|
||||
args[:user][:avatar_url] = nil
|
||||
end
|
||||
|
||||
it "returns an empty string in the activity's image property" do
|
||||
expect(subject.activity[:image]).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
it "returns the pipeline summary as the attachment's fallback property" do
|
||||
expect(subject.attachments.first[:fallback]).to eq(
|
||||
"<http://example.gitlab.com|project_name>:" \
|
||||
" Pipeline <http://example.gitlab.com/pipelines/123|#123>" \
|
||||
" of branch <http://example.gitlab.com/commits/develop|develop>" \
|
||||
" by The Hacker (hacker) has passed in 02:00:10"
|
||||
)
|
||||
end
|
||||
|
||||
it "returns 'good' as the attachment's color property" do
|
||||
expect(subject.attachments.first[:color]).to eq('good')
|
||||
end
|
||||
|
||||
context "when the pipeline failed" do
|
||||
before do
|
||||
args[:object_attributes][:status] = 'failed'
|
||||
end
|
||||
|
||||
it "returns 'danger' as the attachment's color property" do
|
||||
expect(subject.attachments.first[:color]).to eq('danger')
|
||||
end
|
||||
end
|
||||
|
||||
context "when the pipeline passed with warnings" do
|
||||
before do
|
||||
args[:object_attributes][:detailed_status] = 'passed with warnings'
|
||||
end
|
||||
|
||||
it "returns 'warning' as the attachment's color property" do
|
||||
expect(subject.attachments.first[:color]).to eq('warning')
|
||||
end
|
||||
end
|
||||
|
||||
it "returns the committer's name and username as the attachment's author_name property" do
|
||||
expect(subject.attachments.first[:author_name]).to eq('The Hacker (hacker)')
|
||||
end
|
||||
|
||||
it "returns the committer's avatar URL as the attachment's author_icon property" do
|
||||
expect(subject.attachments.first[:author_icon]).to eq('http://example.com/avatar')
|
||||
end
|
||||
|
||||
it "returns the committer's GitLab profile URL as the attachment's author_link property" do
|
||||
expect(subject.attachments.first[:author_link]).to eq('http://example.gitlab.com/hacker')
|
||||
end
|
||||
|
||||
context 'when no user is provided because the pipeline was triggered by the API' do
|
||||
before do
|
||||
args[:user] = nil
|
||||
end
|
||||
|
||||
it "returns the committer's name and username as the attachment's author_name property" do
|
||||
expect(subject.attachments.first[:author_name]).to eq('API')
|
||||
end
|
||||
|
||||
it "returns nil as the attachment's author_icon property" do
|
||||
expect(subject.attachments.first[:author_icon]).to be_nil
|
||||
end
|
||||
|
||||
it "returns nil as the attachment's author_link property" do
|
||||
expect(subject.attachments.first[:author_link]).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
it "returns the pipeline ID, status, and duration as the attachment's title property" do
|
||||
expect(subject.attachments.first[:title]).to eq("Pipeline #123 has passed in 02:00:10")
|
||||
end
|
||||
|
||||
it "returns the pipeline URL as the attachment's title_link property" do
|
||||
expect(subject.attachments.first[:title_link]).to eq("http://example.gitlab.com/pipelines/123")
|
||||
end
|
||||
|
||||
it "returns two attachment fields" do
|
||||
expect(subject.attachments.first[:fields].count).to eq(2)
|
||||
end
|
||||
|
||||
it "returns the commit message as the attachment's second field property" do
|
||||
expect(subject.attachments.first[:fields][0]).to eq({
|
||||
title: "Branch",
|
||||
value: "<http://example.gitlab.com/commits/develop|develop>",
|
||||
short: true
|
||||
})
|
||||
end
|
||||
|
||||
it "returns the ref name and link as the attachment's second field property" do
|
||||
expect(subject.attachments.first[:fields][1]).to eq({
|
||||
title: "Commit",
|
||||
value: "<http://example.com/commit|A test commit message>",
|
||||
short: true
|
||||
})
|
||||
end
|
||||
|
||||
context "when a job in the pipeline fails" do
|
||||
before do
|
||||
args[:builds] = [
|
||||
{ id: 1, name: "rspec", status: "failed", stage: "test" },
|
||||
{ id: 2, name: "karma", status: "success", stage: "test" }
|
||||
]
|
||||
end
|
||||
|
||||
it "returns four attachment fields" do
|
||||
expect(subject.attachments.first[:fields].count).to eq(4)
|
||||
end
|
||||
|
||||
it "returns the stage name and link to the 'Failed jobs' tab on the pipeline's page as the attachment's third field property" do
|
||||
expect(subject.attachments.first[:fields][2]).to eq({
|
||||
title: "Failed stage",
|
||||
value: "<http://example.gitlab.com/pipelines/123/failures|test>",
|
||||
short: true
|
||||
})
|
||||
end
|
||||
|
||||
it "returns the job name and link as the attachment's fourth field property" do
|
||||
expect(subject.attachments.first[:fields][3]).to eq({
|
||||
title: "Failed job",
|
||||
value: "<http://example.gitlab.com/-/jobs/1|rspec>",
|
||||
short: true
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
context 'pipeline failed' do
|
||||
let(:status) { 'failed' }
|
||||
let(:color) { 'danger' }
|
||||
let(:message) { build_markdown_message(status, combined_name) }
|
||||
|
||||
it 'returns a message with information about failed build' do
|
||||
expect(subject.pretext).to be_empty
|
||||
expect(subject.attachments).to eq(message)
|
||||
expect(subject.activity).to eq({
|
||||
title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of branch [develop](http://example.gitlab.com/commits/develop) by The Hacker (hacker) failed',
|
||||
subtitle: 'in [project_name](http://example.gitlab.com)',
|
||||
text: 'in 02:00:10',
|
||||
image: ''
|
||||
})
|
||||
end
|
||||
|
||||
context 'when triggered by API therefore lacking user' do
|
||||
let(:user) { nil }
|
||||
let(:message) { build_markdown_message(status, 'API') }
|
||||
|
||||
it 'returns a message stating it is by API' do
|
||||
expect(subject.pretext).to be_empty
|
||||
expect(subject.attachments).to eq(message)
|
||||
expect(subject.activity).to eq({
|
||||
title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of branch [develop](http://example.gitlab.com/commits/develop) by API failed',
|
||||
subtitle: 'in [project_name](http://example.gitlab.com)',
|
||||
text: 'in 02:00:10',
|
||||
image: ''
|
||||
})
|
||||
context "when lots of jobs across multiple stages fail" do
|
||||
before do
|
||||
args[:builds] = (1..25).map do |i|
|
||||
{ id: i, name: "job-#{i}", status: "failed", stage: "stage-" + ((i % 3) + 1).to_s }
|
||||
end
|
||||
end
|
||||
|
||||
it "returns the stage names and links to the 'Failed jobs' tab on the pipeline's page as the attachment's third field property" do
|
||||
expect(subject.attachments.first[:fields][2]).to eq({
|
||||
title: "Failed stages",
|
||||
value: "<http://example.gitlab.com/pipelines/123/failures|stage-2>, <http://example.gitlab.com/pipelines/123/failures|stage-1>, <http://example.gitlab.com/pipelines/123/failures|stage-3>",
|
||||
short: true
|
||||
})
|
||||
end
|
||||
|
||||
it "returns the job names and links as the attachment's fourth field property" do
|
||||
expected_jobs = 25.downto(16).map do |i|
|
||||
"<http://example.gitlab.com/-/jobs/#{i}|job-#{i}>"
|
||||
end
|
||||
|
||||
expected_jobs << "and <http://example.gitlab.com/pipelines/123/failures|15 more>"
|
||||
|
||||
expect(subject.attachments.first[:fields][3]).to eq({
|
||||
title: "Failed jobs",
|
||||
value: expected_jobs.join(", "),
|
||||
short: true
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
def build_markdown_message(status_text = status, name = user[:name])
|
||||
"[project_name](http://example.gitlab.com):" \
|
||||
" Pipeline [#123](http://example.gitlab.com/pipelines/123)" \
|
||||
" of branch [develop](http://example.gitlab.com/commits/develop)" \
|
||||
" by #{name} #{status_text} in 02:00:10"
|
||||
context "when the CI config file contains a YAML error" do
|
||||
let(:has_yaml_errors) { true }
|
||||
|
||||
it "returns three attachment fields" do
|
||||
expect(subject.attachments.first[:fields].count).to eq(3)
|
||||
end
|
||||
|
||||
it "returns the YAML error deatils as the attachment's third field property" do
|
||||
expect(subject.attachments.first[:fields][2]).to eq({
|
||||
title: "Invalid CI config YAML file",
|
||||
value: "yaml error description here",
|
||||
short: false
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
it "returns the stage name and link as the attachment's second field property" do
|
||||
expect(subject.attachments.first[:fields][1]).to eq({
|
||||
title: "Commit",
|
||||
value: "<http://example.com/commit|A test commit message>",
|
||||
short: true
|
||||
})
|
||||
end
|
||||
|
||||
it "returns the project's name as the attachment's footer property" do
|
||||
expect(subject.attachments.first[:footer]).to eq("project_name")
|
||||
end
|
||||
|
||||
it "returns the project's avatar URL as the attachment's footer_icon property" do
|
||||
expect(subject.attachments.first[:footer_icon]).to eq("http://example.com/project_avatar")
|
||||
end
|
||||
|
||||
it "returns the pipeline's timestamp as the attachment's ts property" do
|
||||
expected_ts = Time.parse(args[:object_attributes][:finished_at]).to_i
|
||||
expect(subject.attachments.first[:ts]).to eq(expected_ts)
|
||||
end
|
||||
|
||||
context 'when rendering markdown' do
|
||||
before do
|
||||
args[:markdown] = true
|
||||
end
|
||||
|
||||
it 'returns the pipeline summary as the attachments in markdown format' do
|
||||
expect(subject.attachments).to eq(
|
||||
"[project_name](http://example.gitlab.com):" \
|
||||
" Pipeline [#123](http://example.gitlab.com/pipelines/123)" \
|
||||
" of branch [develop](http://example.gitlab.com/commits/develop)" \
|
||||
" by The Hacker (hacker) has passed in 02:00:10"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -292,7 +292,8 @@ describe MicrosoftTeamsService do
|
|||
|
||||
context 'when disabled' do
|
||||
let(:pipeline) do
|
||||
create(:ci_pipeline, :failed, project: project, ref: 'not-the-default-branch')
|
||||
create(:ci_pipeline, :failed, project: project,
|
||||
sha: project.commit.sha, ref: 'not-the-default-branch')
|
||||
end
|
||||
|
||||
before do
|
||||
|
|
|
@ -222,7 +222,8 @@ shared_examples_for "chat service" do |service_name|
|
|||
|
||||
context "with not default branch" do
|
||||
let(:pipeline) do
|
||||
create(:ci_pipeline, project: project, status: "failed", ref: "not-the-default-branch")
|
||||
create(:ci_pipeline, :failed, project: project,
|
||||
sha: project.commit.sha, ref: "not-the-default-branch")
|
||||
end
|
||||
|
||||
context "when notify_only_default_branch enabled" do
|
||||
|
|
|
@ -454,7 +454,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do
|
|||
context 'only notify for the default branch' do
|
||||
context 'when enabled' do
|
||||
let(:pipeline) do
|
||||
create(:ci_pipeline, :failed, project: project, ref: 'not-the-default-branch')
|
||||
create(:ci_pipeline, :failed, project: project, sha: project.commit.sha, ref: 'not-the-default-branch')
|
||||
end
|
||||
|
||||
before do
|
||||
|
@ -472,7 +472,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do
|
|||
|
||||
context 'when disabled' do
|
||||
let(:pipeline) do
|
||||
create(:ci_pipeline, :failed, project: project, ref: 'not-the-default-branch')
|
||||
create(:ci_pipeline, :failed, project: project, sha: project.commit.sha, ref: 'not-the-default-branch')
|
||||
end
|
||||
|
||||
before do
|
||||
|
|
Loading…
Reference in New Issue