diff --git a/app/controllers/concerns/service_params.rb b/app/controllers/concerns/service_params.rb index 471d15af9130aa1e2962730053b7113cbaf021a3..58877c5ad5d5cfccc701e39139e4ca356d6e4f72 100644 --- a/app/controllers/concerns/service_params.rb +++ b/app/controllers/concerns/service_params.rb @@ -7,11 +7,12 @@ module ServiceParams :build_key, :server, :teamcity_url, :drone_url, :build_type, :description, :issues_url, :new_issue_url, :restrict_to_branch, :channel, :colorize_messages, :channels, - :push_events, :issues_events, :merge_requests_events, :tag_push_events, - :note_events, :build_events, :wiki_page_events, - :notify_only_broken_builds, :add_pusher, - :send_from_committer_email, :disable_diffs, :external_wiki_url, - :notify, :color, + # See app/helpers/services_helper.rb + # for why we need issues_events and merge_requests_events. + :issues_events, :merge_requests_events, + :notify_only_broken_builds, :notify_only_broken_pipelines, + :add_pusher, :send_from_committer_email, :disable_diffs, + :external_wiki_url, :notify, :color, :server_host, :server_port, :default_irc_uri, :enable_ssl_verification, :jira_issue_transition_id] @@ -19,9 +20,7 @@ module ServiceParams FILTER_BLANK_PARAMS = [:password] def service_params - dynamic_params = [] - dynamic_params.concat(@service.event_channel_names) - + dynamic_params = @service.event_channel_names + @service.event_names service_params = params.permit(:id, service: ALLOWED_PARAMS + dynamic_params) if service_params[:service].is_a?(Hash) diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb index abbc780dc1a09fe6e45ea29102b2ea93b5049539..6584646e998aac5d0e65efbb155e9ee1344ee05f 100644 --- a/app/models/project_services/slack_service.rb +++ b/app/models/project_services/slack_service.rb @@ -1,6 +1,6 @@ class SlackService < Service prop_accessor :webhook, :username, :channel - boolean_accessor :notify_only_broken_builds + boolean_accessor :notify_only_broken_builds, :notify_only_broken_pipelines validates :webhook, presence: true, url: true, if: :activated? def initialize_properties @@ -10,6 +10,7 @@ class SlackService < Service if properties.nil? self.properties = {} self.notify_only_broken_builds = true + self.notify_only_broken_pipelines = true end end @@ -38,13 +39,14 @@ class SlackService < Service { type: 'text', name: 'username', placeholder: 'username' }, { type: 'text', name: 'channel', placeholder: "#general" }, { type: 'checkbox', name: 'notify_only_broken_builds' }, + { type: 'checkbox', name: 'notify_only_broken_pipelines' }, ] default_fields + build_event_channels end def supported_events - %w(push issue merge_request note tag_push build wiki_page) + %w(push issue merge_request note tag_push build pipeline wiki_page) end def execute(data) @@ -74,6 +76,8 @@ class SlackService < Service NoteMessage.new(data) when "build" BuildMessage.new(data) if should_build_be_notified?(data) + when "pipeline" + PipelineMessage.new(data) if should_pipeline_be_notified?(data) when "wiki_page" WikiPageMessage.new(data) end @@ -142,6 +146,17 @@ class SlackService < Service false end end + + def should_pipeline_be_notified?(data) + case data[:object_attributes][:status] + when 'success' + !notify_only_broken_builds? + when 'failed' + true + else + false + end + end end require "slack_service/issue_message" @@ -149,4 +164,5 @@ require "slack_service/push_message" require "slack_service/merge_message" require "slack_service/note_message" require "slack_service/build_message" +require "slack_service/pipeline_message" require "slack_service/wiki_page_message" diff --git a/app/models/project_services/slack_service/pipeline_message.rb b/app/models/project_services/slack_service/pipeline_message.rb new file mode 100644 index 0000000000000000000000000000000000000000..3d12af3fa7e838e2eb7689762ddaff0d3ae415ff --- /dev/null +++ b/app/models/project_services/slack_service/pipeline_message.rb @@ -0,0 +1,77 @@ +class SlackService + class PipelineMessage < BaseMessage + attr_reader :sha, :ref_type, :ref, :status, :project_name, :project_url, + :user_name, :duration, :pipeline_id + + def initialize(data) + @sha = data[:sha] + @ref_type = data[:tag] ? 'tag' : 'branch' + @ref = data[:ref] + @status = data[:status] + @project_name = data[:project][:path_with_namespace] + @project_url = data[:project][:web_url] + @user_name = data[:commit] && data[:commit][:author_name] + @duration = data[:object_attributes][:duration] + @pipeline_id = data[:object_attributes][:id] + end + + def pretext + '' + end + + def fallback + format(message) + end + + def attachments + [{ text: format(message), color: attachment_color }] + end + + private + + def message + "#{project_link}: Pipeline #{pipeline_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status} in #{duration} #{'second'.pluralize(duration)}" + end + + def format(string) + Slack::Notifier::LinkFormatter.format(string) + end + + def humanized_status + case status + when 'success' + 'passed' + else + status + end + end + + def attachment_color + if status == 'success' + 'good' + else + 'danger' + end + end + + def branch_url + "#{project_url}/commits/#{ref}" + end + + def branch_link + "[#{ref}](#{branch_url})" + end + + def project_link + "[#{project_name}](#{project_url})" + end + + def pipeline_url + "#{project_url}/pipelines/#{pipeline_id}" + end + + def pipeline_link + "[#{Commit.truncate_sha(sha)}](#{pipeline_url})" + end + end +end diff --git a/app/models/service.rb b/app/models/service.rb index 4a63abb2724968608feef86641ec87830cc95c92..e4cd44f542abe84df42ead069ffbfe112297e73b 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -87,6 +87,10 @@ class Service < ActiveRecord::Base [] end + def event_names + supported_events.map { |event| "#{event}_events" } + end + def event_field(event) nil end diff --git a/lib/gitlab/data_builder/pipeline_data_builder.rb b/lib/gitlab/data_builder/pipeline_data_builder.rb index fed9bd92ba4a6526700f954ff45e9aa7e29fa050..46f8a1857b40c0b58949b7e2ebe71ceb79e12226 100644 --- a/lib/gitlab/data_builder/pipeline_data_builder.rb +++ b/lib/gitlab/data_builder/pipeline_data_builder.rb @@ -28,7 +28,8 @@ module Gitlab stage: first_pending_build.try(:stage), stages: config_processor.try(:stages), created_at: pipeline.created_at, - finished_at: pipeline.finished_at + finished_at: pipeline.finished_at, + duration: pipeline.duration } end diff --git a/spec/models/project_services/slack_service/pipeline_message_spec.rb b/spec/models/project_services/slack_service/pipeline_message_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..2960a200e9de950ec8e3d9fb20b35779a091141a --- /dev/null +++ b/spec/models/project_services/slack_service/pipeline_message_spec.rb @@ -0,0 +1,58 @@ +require 'spec_helper' + +describe SlackService::PipelineMessage do + subject { SlackService::PipelineMessage.new(args) } + + let(:args) do + { + sha: '97de212e80737a608d939f648d959671fb0a0142', + tag: false, + ref: 'develop', + status: status, + project: { path_with_namespace: 'project_name', + web_url: 'somewhere.com' }, + commit: { author_name: 'hacker' }, + object_attributes: { duration: duration, + id: 123 } + } + end + + context 'succeeded' do + let(:status) { 'success' } + let(:color) { 'good' } + let(:duration) { 10 } + + it 'returns a message with information about succeeded build' do + message = '<somewhere.com|project_name>: Pipeline <somewhere.com/pipelines/123|97de212e> of <somewhere.com/commits/develop|develop> branch by hacker passed in 10 seconds' + expect(subject.pretext).to be_empty + expect(subject.fallback).to eq(message) + expect(subject.attachments).to eq([text: message, color: color]) + end + end + + context 'failed' do + let(:status) { 'failed' } + let(:color) { 'danger' } + let(:duration) { 10 } + + it 'returns a message with information about failed build' do + message = '<somewhere.com|project_name>: Pipeline <somewhere.com/pipelines/123|97de212e> of <somewhere.com/commits/develop|develop> branch by hacker failed in 10 seconds' + expect(subject.pretext).to be_empty + expect(subject.fallback).to eq(message) + expect(subject.attachments).to eq([text: message, color: color]) + end + end + + describe '#seconds_name' do + let(:status) { 'failed' } + let(:color) { 'danger' } + let(:duration) { 1 } + + it 'returns seconds as singular when there is only one' do + message = '<somewhere.com|project_name>: Pipeline <somewhere.com/pipelines/123|97de212e> of <somewhere.com/commits/develop|develop> branch by hacker failed in 1 second' + expect(subject.pretext).to be_empty + expect(subject.fallback).to eq(message) + expect(subject.attachments).to eq([text: message, color: color]) + end + end +end