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.
This commit is contained in:
parent
6dcde68b6d
commit
7b5fb754f8
|
@ -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
|
|
@ -2011,6 +2011,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
|
||||
|
|
|
@ -220,7 +220,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
|
||||
|
|
|
@ -452,7 +452,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
|
||||
|
@ -470,7 +470,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