Commit c712340f authored by Robert Speicher's avatar Robert Speicher

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
parents 88c8f0ba 7b5fb754
# frozen_string_literal: true # frozen_string_literal: true
require 'slack-notifier'
module ChatMessage module ChatMessage
class PipelineMessage < BaseMessage class PipelineMessage < BaseMessage
MAX_VISIBLE_JOBS = 10
attr_reader :user
attr_reader :ref_type attr_reader :ref_type
attr_reader :ref attr_reader :ref
attr_reader :status attr_reader :status
attr_reader :detailed_status
attr_reader :duration attr_reader :duration
attr_reader :finished_at
attr_reader :pipeline_id 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) def initialize(data)
super super
@user = data[:user]
@user_name = data.dig(:user, :username) || 'API' @user_name = data.dig(:user, :username) || 'API'
pipeline_attributes = data[:object_attributes] pipeline_attributes = data[:object_attributes]
@ref_type = pipeline_attributes[:tag] ? 'tag' : 'branch' @ref_type = pipeline_attributes[:tag] ? 'tag' : 'branch'
@ref = pipeline_attributes[:ref] @ref = pipeline_attributes[:ref]
@status = pipeline_attributes[:status] @status = pipeline_attributes[:status]
@detailed_status = pipeline_attributes[:detailed_status]
@duration = pipeline_attributes[:duration].to_i @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] @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 end
def pretext def pretext
...@@ -28,38 +51,145 @@ module ChatMessage ...@@ -28,38 +51,145 @@ module ChatMessage
def attachments def attachments
return message if markdown 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 end
def activity def activity
{ {
title: "Pipeline #{pipeline_link} of #{ref_type} #{branch_link} by #{user_combined_name} #{humanized_status}", title: s_("ChatMessage|Pipeline %{pipeline_link} of %{ref_type} %{branch_link} by %{user_combined_name} %{humanized_status}") %
subtitle: "in #{project_link}", {
text: "in #{pretty_duration(duration)}", 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 || '' image: user_avatar || ''
} }
end end
private 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 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 end
def humanized_status def humanized_status
case status if fancy_notifications?
when 'success' case status
'passed' 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 else
status case status
when 'success'
s_("ChatMessage|passed")
when 'failed'
s_("ChatMessage|failed")
else
status
end
end end
end end
def attachment_color def attachment_color
if status == 'success' if fancy_notifications?
'good' case status
when 'success'
detailed_status == 'passed with warnings' ? 'warning' : 'good'
else
'danger'
end
else else
'danger' case status
when 'success'
'good'
else
'danger'
end
end end
end end
...@@ -71,16 +201,83 @@ module ChatMessage ...@@ -71,16 +201,83 @@ module ChatMessage
"[#{ref}](#{branch_url})" "[#{ref}](#{branch_url})"
end end
def project_url
project.web_url
end
def project_link def project_link
"[#{project_name}](#{project_url})" "[#{project.name}](#{project_url})"
end
def pipeline_failed_jobs_url
"#{project_url}/pipelines/#{pipeline_id}/failures"
end end
def pipeline_url 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 end
def pipeline_link def pipeline_link
"[##{pipeline_id}](#{pipeline_url})" "[##{pipeline_id}](#{pipeline_url})"
end 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
end end
---
title: Improve pipeline status Slack notifications
merge_request: 27683
author:
type: added
...@@ -2020,6 +2020,57 @@ msgstr "" ...@@ -2020,6 +2020,57 @@ msgstr ""
msgid "Chat" msgid "Chat"
msgstr "" 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" msgid "Check again"
msgstr "" msgstr ""
......
# frozen_string_literal: true # frozen_string_literal: true
require 'spec_helper' require 'spec_helper'
describe ChatMessage::PipelineMessage do describe ChatMessage::PipelineMessage do
subject { described_class.new(args) } subject { described_class.new(args) }
let(:user) { { name: "The Hacker", username: 'hacker' } }
let(:duration) { 7210 }
let(:args) do let(:args) do
{ {
object_attributes: { object_attributes: {
...@@ -14,122 +11,437 @@ describe ChatMessage::PipelineMessage do ...@@ -14,122 +11,437 @@ describe ChatMessage::PipelineMessage do
sha: '97de212e80737a608d939f648d959671fb0a0142', sha: '97de212e80737a608d939f648d959671fb0a0142',
tag: false, tag: false,
ref: 'develop', ref: 'develop',
status: status, status: 'success',
duration: duration detailed_status: nil,
duration: 7210,
finished_at: "2019-05-27 11:56:36 -0300"
}, },
project: { project: {
path_with_namespace: 'project_name', id: 234,
web_url: 'http://example.gitlab.com' name: "project_name",
path_with_namespace: 'group/project_name',
web_url: 'http://example.gitlab.com',
avatar_url: 'http://example.com/project_avatar'
},
user: {
id: 345,
name: "The Hacker",
username: "hacker",
email: "hacker@example.gitlab.com",
avatar_url: "http://example.com/avatar"
},
commit: {
id: "abcdef"
}, },
user: user builds: nil,
markdown: false
} }
end end
let(:combined_name) { "The Hacker (hacker)" }
context 'without markdown' do let(:has_yaml_errors) { false }
context 'pipeline succeeded' do
let(:status) { 'success' } before do
let(:color) { 'good' } test_commit = double("A test commit", committer: args[:user], title: "A test commit message")
let(:message) { build_message('passed', combined_name) } 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
it 'returns a message with information about succeeded build' do context "when the pipeline failed" do
expect(subject.pretext).to be_empty before do
expect(subject.fallback).to eq(message) args[:object_attributes][:status] = 'failed'
expect(subject.attachments).to eq([text: message, color: color]) 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
end end
context 'pipeline failed' do context 'when no user is provided because the pipeline was triggered by the API' do
let(:status) { 'failed' } before do
let(:color) { 'danger' } args[:user] = nil
let(:message) { build_message(status, combined_name) } end
it 'returns a message with information about failed build' do it "returns the summary with 'API' as the username" do
expect(subject.pretext).to be_empty expect(subject.activity[:title]).to eq(
expect(subject.fallback).to eq(message) "Pipeline [#123](http://example.gitlab.com/pipelines/123)" \
expect(subject.attachments).to eq([text: message, color: color]) " of branch [develop](http://example.gitlab.com/commits/develop)" \
" by API passed"
)
end end
end
context 'when triggered by API therefore lacking user' do it "returns a link to the project in the activity's subtitle" do
let(:user) { nil } expect(subject.activity[:subtitle]).to eq("in [project_name](http://example.gitlab.com)")
let(:message) { build_message(status, 'API') } end
it 'returns a message stating it is by API' do it "returns the build duration in the activity's text property" do
expect(subject.pretext).to be_empty expect(subject.activity[:text]).to eq("in 02:00:10")
expect(subject.fallback).to eq(message) end
expect(subject.attachments).to eq([text: message, color: color])
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
end end
def build_message(status_text = status, name = user[:name]) context 'when rendering markdown' do
"<http://example.gitlab.com|project_name>:" \ before do
" Pipeline <http://example.gitlab.com/pipelines/123|#123>" \ args[:markdown] = true
" of branch <http://example.gitlab.com/commits/develop|develop>" \ end
" by #{name} #{status_text} in 02:00:10"
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
end end
context 'with markdown' do context 'when the fancy_pipeline_slack_notifications feature flag is enabled' do
before do before do
args[:markdown] = true stub_feature_flags(fancy_pipeline_slack_notifications: true)
end end
context 'pipeline succeeded' do it 'returns an empty pretext' do
let(:status) { 'success' } expect(subject.pretext).to be_empty
let(:color) { 'good' } end
let(:message) { build_markdown_message('passed', combined_name) }
it "returns the pipeline summary in the activity's title" do
it 'returns a message with information about succeeded build' do expect(subject.activity[:title]).to eq(
expect(subject.pretext).to be_empty "Pipeline [#123](http://example.gitlab.com/pipelines/123)" \
expect(subject.attachments).to eq(message) " of branch [develop](http://example.gitlab.com/commits/develop)" \
expect(subject.activity).to eq({ " by The Hacker (hacker) has passed"
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)', end
text: 'in 02:00:10',
image: '' 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
end end
context 'pipeline failed' do it "returns the committer's name and username as the attachment's author_name property" do
let(:status) { 'failed' } expect(subject.attachments.first[:author_name]).to eq('The Hacker (hacker)')
let(:color) { 'danger' } end
let(:message) { build_markdown_message(status, combined_name) }
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 a message with information about failed build' do 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.pretext).to be_empty expect(subject.attachments.first[:fields][2]).to eq({
expect(subject.attachments).to eq(message) title: "Failed stage",
expect(subject.activity).to eq({ value: "<http://example.gitlab.com/pipelines/123/failures|test>",
title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of branch [develop](http://example.gitlab.com/commits/develop) by The Hacker (hacker) failed', short: true
subtitle: 'in [project_name](http://example.gitlab.com)',
text: 'in 02:00:10',
image: ''
}) })
end end
context 'when triggered by API therefore lacking user' do it "returns the job name and link as the attachment's fourth field property" do
let(:user) { nil } expect(subject.attachments.first[:fields][3]).to eq({
let(:message) { build_markdown_message(status, 'API') } title: "Failed job",
value: "<http://example.gitlab.com/-/jobs/1|rspec>",
short: true
})
end
end
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 a message stating it is by API' do it "returns the job names and links as the attachment's fourth field property" do
expect(subject.pretext).to be_empty expected_jobs = 25.downto(16).map do |i|
expect(subject.attachments).to eq(message) "<http://example.gitlab.com/-/jobs/#{i}|job-#{i}>"
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: ''
})
end 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
end end
def build_markdown_message(status_text = status, name = user[:name]) context "when the CI config file contains a YAML error" do
"[project_name](http://example.gitlab.com):" \ let(:has_yaml_errors) { true }
" Pipeline [#123](http://example.gitlab.com/pipelines/123)" \
" of branch [develop](http://example.gitlab.com/commits/develop)" \ it "returns three attachment fields" do
" by #{name} #{status_text} in 02:00:10" 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 end
end end
...@@ -292,7 +292,8 @@ describe MicrosoftTeamsService do ...@@ -292,7 +292,8 @@ describe MicrosoftTeamsService do
context 'when disabled' do context 'when disabled' do
let(:pipeline) 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 end
before do before do
......
...@@ -222,7 +222,8 @@ shared_examples_for "chat service" do |service_name| ...@@ -222,7 +222,8 @@ shared_examples_for "chat service" do |service_name|
context "with not default branch" do context "with not default branch" do
let(:pipeline) 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 end
context "when notify_only_default_branch enabled" do context "when notify_only_default_branch enabled" do
......
...@@ -454,7 +454,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do ...@@ -454,7 +454,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do
context 'only notify for the default branch' do context 'only notify for the default branch' do
context 'when enabled' do context 'when enabled' do
let(:pipeline) 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 end
before do before do
...@@ -472,7 +472,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do ...@@ -472,7 +472,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do
context 'when disabled' do context 'when disabled' do
let(:pipeline) 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 end
before do before do
......
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