Commit 7b5fb754 authored by Nathan Friend's avatar Nathan Friend

Improve pipeline status Slack notifications

This commit adds some formatting to the Slack notifications for pipeline
statuses, as well as adds information about the stage and jobs
that failed in the case of pipeline failure.
parent 6dcde68b
# 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,40 +51,147 @@ module ChatMessage ...@@ -28,40 +51,147 @@ 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
if fancy_notifications?
case status case status
when 'success' when 'success'
'passed' 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
case status
when 'success'
s_("ChatMessage|passed")
when 'failed'
s_("ChatMessage|failed")
else else
status status
end end
end end
end
def attachment_color def attachment_color
if status == 'success' if fancy_notifications?
case status
when 'success'
detailed_status == 'passed with warnings' ? 'warning' : 'good'
else
'danger'
end
else
case status
when 'success'
'good' 'good'
else else
'danger' 'danger'
end end
end end
end
def branch_url def branch_url
"#{project_url}/commits/#{ref}" "#{project_url}/commits/#{ref}"
...@@ -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
if fancy_notifications? && failed_jobs.any?
pipeline_failed_jobs_url
else
"#{project_url}/pipelines/#{pipeline_id}" "#{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
...@@ -2011,6 +2011,57 @@ msgstr "" ...@@ -2011,6 +2011,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 a message with information about succeeded build' do it 'returns an empty pretext' do
expect(subject.pretext).to be_empty expect(subject.pretext).to be_empty
expect(subject.fallback).to eq(message)
expect(subject.attachments).to eq([text: message, color: color])
end 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 end
context 'pipeline failed' do context "when the pipeline failed" do
let(:status) { 'failed' } before do
let(:color) { 'danger' } args[:object_attributes][:status] = 'failed'
let(:message) { build_message(status, combined_name) } end
it 'returns a message with information about failed build' do it "returns the summary with a 'failed' status" 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 The Hacker (hacker) failed"
)
end
end end
context 'when triggered by API therefore lacking user' do context 'when no user is provided because the pipeline was triggered by the API' do
let(:user) { nil } before do
let(:message) { build_message(status, 'API') } args[:user] = nil
end
it 'returns a message stating it is by API' 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 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 end
def build_message(status_text = status, name = user[:name]) 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>:" \ "<http://example.gitlab.com|project_name>:" \
" Pipeline <http://example.gitlab.com/pipelines/123|#123>" \ " Pipeline <http://example.gitlab.com/pipelines/123|#123>" \
" of branch <http://example.gitlab.com/commits/develop|develop>" \ " of branch <http://example.gitlab.com/commits/develop|develop>" \
" by #{name} #{status_text} in 02:00:10" " 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
context 'with markdown' do context 'when rendering markdown' do
before do before do
args[:markdown] = true args[:markdown] = true
end end
context 'pipeline succeeded' do it 'returns the pipeline summary as the attachments in markdown format' do
let(:status) { 'success' } expect(subject.attachments).to eq(
let(:color) { 'good' } "[project_name](http://example.gitlab.com):" \
let(:message) { build_markdown_message('passed', combined_name) } " 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 'when the fancy_pipeline_slack_notifications feature flag is enabled' do
before do
stub_feature_flags(fancy_pipeline_slack_notifications: true)
end
it 'returns a message with information about succeeded build' do it 'returns an empty pretext' do
expect(subject.pretext).to be_empty expect(subject.pretext).to be_empty
expect(subject.attachments).to eq(message) end
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', it "returns the pipeline summary in the activity's title" do
subtitle: 'in [project_name](http://example.gitlab.com)', expect(subject.activity[:title]).to eq(
text: 'in 02:00:10', "Pipeline [#123](http://example.gitlab.com/pipelines/123)" \
image: '' " 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 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 end
context 'pipeline failed' do context "when a job in the pipeline fails" do
let(:status) { 'failed' } before do
let(:color) { 'danger' } args[:builds] = [
let(:message) { build_markdown_message(status, combined_name) } { id: 1, name: "rspec", status: "failed", stage: "test" },
{ id: 2, name: "karma", status: "success", stage: "test" }
]
end
it 'returns a message with information about failed build' do it "returns four attachment fields" do
expect(subject.pretext).to be_empty expect(subject.attachments.first[:fields].count).to eq(4)
expect(subject.attachments).to eq(message) end
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', 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
subtitle: 'in [project_name](http://example.gitlab.com)', expect(subject.attachments.first[:fields][2]).to eq({
text: 'in 02:00:10', title: "Failed stage",
image: '' value: "<http://example.gitlab.com/pipelines/123/failures|test>",
short: true
}) })
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
it 'returns a message stating it is by API' do context "when lots of jobs across multiple stages fail" do
expect(subject.pretext).to be_empty before do
expect(subject.attachments).to eq(message) args[:builds] = (1..25).map do |i|
expect(subject.activity).to eq({ { id: i, name: "job-#{i}", status: "failed", stage: "stage-" + ((i % 3) + 1).to_s }
title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of branch [develop](http://example.gitlab.com/commits/develop) by API failed', end
subtitle: 'in [project_name](http://example.gitlab.com)', end
text: 'in 02:00:10',
image: '' 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
end end
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 end
def build_markdown_message(status_text = status, name = user[:name]) 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):" \ "[project_name](http://example.gitlab.com):" \
" Pipeline [#123](http://example.gitlab.com/pipelines/123)" \ " Pipeline [#123](http://example.gitlab.com/pipelines/123)" \
" of branch [develop](http://example.gitlab.com/commits/develop)" \ " of branch [develop](http://example.gitlab.com/commits/develop)" \
" by #{name} #{status_text} in 02:00:10" " 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
......
...@@ -220,7 +220,8 @@ shared_examples_for "chat service" do |service_name| ...@@ -220,7 +220,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
......
...@@ -452,7 +452,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do ...@@ -452,7 +452,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
...@@ -470,7 +470,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do ...@@ -470,7 +470,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