2018-11-20 20:47:30 +05:30
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
module ChatMessage
|
2016-09-29 09:46:39 +05:30
|
|
|
class PipelineMessage < BaseMessage
|
2019-10-12 21:52:04 +05:30
|
|
|
MAX_VISIBLE_JOBS = 10
|
|
|
|
|
|
|
|
attr_reader :user
|
2017-08-17 22:00:37 +05:30
|
|
|
attr_reader :ref_type
|
|
|
|
attr_reader :ref
|
|
|
|
attr_reader :status
|
2019-10-12 21:52:04 +05:30
|
|
|
attr_reader :detailed_status
|
2017-08-17 22:00:37 +05:30
|
|
|
attr_reader :duration
|
2019-10-12 21:52:04 +05:30
|
|
|
attr_reader :finished_at
|
2017-08-17 22:00:37 +05:30
|
|
|
attr_reader :pipeline_id
|
2019-10-12 21:52:04 +05:30
|
|
|
attr_reader :failed_stages
|
|
|
|
attr_reader :failed_jobs
|
|
|
|
|
|
|
|
attr_reader :project
|
|
|
|
attr_reader :commit
|
|
|
|
attr_reader :committer
|
|
|
|
attr_reader :pipeline
|
2016-09-29 09:46:39 +05:30
|
|
|
|
|
|
|
def initialize(data)
|
2017-08-17 22:00:37 +05:30
|
|
|
super
|
|
|
|
|
2019-10-12 21:52:04 +05:30
|
|
|
@user = data[:user]
|
2018-03-17 18:26:18 +05:30
|
|
|
@user_name = data.dig(:user, :username) || 'API'
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2016-09-29 09:46:39 +05:30
|
|
|
pipeline_attributes = data[:object_attributes]
|
|
|
|
@ref_type = pipeline_attributes[:tag] ? 'tag' : 'branch'
|
|
|
|
@ref = pipeline_attributes[:ref]
|
|
|
|
@status = pipeline_attributes[:status]
|
2019-10-12 21:52:04 +05:30
|
|
|
@detailed_status = pipeline_attributes[:detailed_status]
|
2017-08-17 22:00:37 +05:30
|
|
|
@duration = pipeline_attributes[:duration].to_i
|
2019-10-12 21:52:04 +05:30
|
|
|
@finished_at = pipeline_attributes[:finished_at] ? Time.parse(pipeline_attributes[:finished_at]).to_i : nil
|
2016-09-29 09:46:39 +05:30
|
|
|
@pipeline_id = pipeline_attributes[:id]
|
2020-04-22 19:07:51 +05:30
|
|
|
|
|
|
|
# Get list of jobs that have actually failed (after exhausting all retries)
|
|
|
|
@failed_jobs = actually_failed_jobs(Array(data[:builds]))
|
2019-10-12 21:52:04 +05:30
|
|
|
@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)
|
2016-09-29 09:46:39 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
def pretext
|
|
|
|
''
|
|
|
|
end
|
|
|
|
|
|
|
|
def attachments
|
2017-08-17 22:00:37 +05:30
|
|
|
return message if markdown
|
|
|
|
|
2019-10-12 21:52:04 +05:30
|
|
|
[{
|
|
|
|
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(only_path: false),
|
|
|
|
ts: finished_at
|
|
|
|
}]
|
2016-09-29 09:46:39 +05:30
|
|
|
end
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
def activity
|
|
|
|
{
|
2019-12-26 22:10:19 +05:30
|
|
|
title: s_("ChatMessage|Pipeline %{pipeline_link} of %{ref_type} %{ref_link} by %{user_combined_name} %{humanized_status}") %
|
2019-10-12 21:52:04 +05:30
|
|
|
{
|
|
|
|
pipeline_link: pipeline_link,
|
|
|
|
ref_type: ref_type,
|
2019-12-26 22:10:19 +05:30
|
|
|
ref_link: ref_link,
|
2019-10-12 21:52:04 +05:30
|
|
|
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) },
|
2017-08-17 22:00:37 +05:30
|
|
|
image: user_avatar || ''
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2016-09-29 09:46:39 +05:30
|
|
|
private
|
|
|
|
|
2020-04-22 19:07:51 +05:30
|
|
|
def actually_failed_jobs(builds)
|
|
|
|
succeeded_job_names = builds.map { |b| b[:name] if b[:status] == 'success' }.compact.uniq
|
|
|
|
|
|
|
|
failed_jobs = builds.select do |build|
|
|
|
|
# Select jobs which doesn't have a successful retry
|
|
|
|
build[:status] == 'failed' && !succeeded_job_names.include?(build[:name])
|
|
|
|
end
|
|
|
|
|
|
|
|
failed_jobs.uniq { |job| job[:name] }.reverse
|
|
|
|
end
|
|
|
|
|
2019-10-12 21:52:04 +05:30
|
|
|
def failed_stages_field
|
|
|
|
{
|
|
|
|
title: s_("ChatMessage|Failed stage").pluralize(failed_stages.length),
|
2020-04-08 14:13:33 +05:30
|
|
|
value: Slack::Messenger::Util::LinkFormatter.format(failed_stages_links),
|
2019-10-12 21:52:04 +05:30
|
|
|
short: true
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
def failed_jobs_field
|
|
|
|
{
|
|
|
|
title: s_("ChatMessage|Failed job").pluralize(failed_jobs.length),
|
2020-04-08 14:13:33 +05:30
|
|
|
value: Slack::Messenger::Util::LinkFormatter.format(failed_jobs_links),
|
2019-10-12 21:52:04 +05:30
|
|
|
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"),
|
2020-04-08 14:13:33 +05:30
|
|
|
value: Slack::Messenger::Util::LinkFormatter.format(ref_link),
|
2019-10-12 21:52:04 +05:30
|
|
|
short: true
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title: s_("ChatMessage|Commit"),
|
2020-04-08 14:13:33 +05:30
|
|
|
value: Slack::Messenger::Util::LinkFormatter.format(commit_link),
|
2019-10-12 21:52:04 +05:30
|
|
|
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
|
|
|
|
|
2016-09-29 09:46:39 +05:30
|
|
|
def message
|
2019-12-26 22:10:19 +05:30
|
|
|
s_("ChatMessage|%{project_link}: Pipeline %{pipeline_link} of %{ref_type} %{ref_link} by %{user_combined_name} %{humanized_status} in %{duration}") %
|
2019-10-12 21:52:04 +05:30
|
|
|
{
|
|
|
|
project_link: project_link,
|
|
|
|
pipeline_link: pipeline_link,
|
|
|
|
ref_type: ref_type,
|
2019-12-26 22:10:19 +05:30
|
|
|
ref_link: ref_link,
|
2019-10-12 21:52:04 +05:30
|
|
|
user_combined_name: user_combined_name,
|
|
|
|
humanized_status: humanized_status,
|
|
|
|
duration: pretty_duration(duration)
|
|
|
|
}
|
2016-09-29 09:46:39 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
def humanized_status
|
2020-05-24 23:13:21 +05:30
|
|
|
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")
|
2016-09-29 09:46:39 +05:30
|
|
|
else
|
2020-05-24 23:13:21 +05:30
|
|
|
status
|
2016-09-29 09:46:39 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def attachment_color
|
2020-05-24 23:13:21 +05:30
|
|
|
case status
|
|
|
|
when 'success'
|
|
|
|
detailed_status == 'passed with warnings' ? 'warning' : 'good'
|
2016-09-29 09:46:39 +05:30
|
|
|
else
|
2020-05-24 23:13:21 +05:30
|
|
|
'danger'
|
2016-09-29 09:46:39 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-12-26 22:10:19 +05:30
|
|
|
def ref_url
|
|
|
|
if ref_type == 'tag'
|
|
|
|
"#{project_url}/-/tags/#{ref}"
|
|
|
|
else
|
2020-06-23 00:09:42 +05:30
|
|
|
"#{project_url}/-/commits/#{ref}"
|
2019-12-26 22:10:19 +05:30
|
|
|
end
|
2016-09-29 09:46:39 +05:30
|
|
|
end
|
|
|
|
|
2019-12-26 22:10:19 +05:30
|
|
|
def ref_link
|
|
|
|
"[#{ref}](#{ref_url})"
|
2016-09-29 09:46:39 +05:30
|
|
|
end
|
|
|
|
|
2019-10-12 21:52:04 +05:30
|
|
|
def project_url
|
|
|
|
project.web_url
|
|
|
|
end
|
|
|
|
|
2016-09-29 09:46:39 +05:30
|
|
|
def project_link
|
2019-10-12 21:52:04 +05:30
|
|
|
"[#{project.name}](#{project_url})"
|
|
|
|
end
|
|
|
|
|
|
|
|
def pipeline_failed_jobs_url
|
2020-06-23 00:09:42 +05:30
|
|
|
"#{project_url}/-/pipelines/#{pipeline_id}/failures"
|
2016-09-29 09:46:39 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
def pipeline_url
|
2020-05-24 23:13:21 +05:30
|
|
|
if failed_jobs.any?
|
2019-10-12 21:52:04 +05:30
|
|
|
pipeline_failed_jobs_url
|
|
|
|
else
|
2020-06-23 00:09:42 +05:30
|
|
|
"#{project_url}/-/pipelines/#{pipeline_id}"
|
2019-10-12 21:52:04 +05:30
|
|
|
end
|
2016-09-29 09:46:39 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
def pipeline_link
|
2017-08-17 22:00:37 +05:30
|
|
|
"[##{pipeline_id}](#{pipeline_url})"
|
2016-09-29 09:46:39 +05:30
|
|
|
end
|
2019-10-12 21:52:04 +05:30
|
|
|
|
|
|
|
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 author_url
|
|
|
|
return unless user && committer
|
|
|
|
|
|
|
|
Gitlab::UrlBuilder.build(committer)
|
|
|
|
end
|
2016-09-29 09:46:39 +05:30
|
|
|
end
|
|
|
|
end
|