From d27095a36a5d8c83182771d7548a13fcc9188e3c Mon Sep 17 00:00:00 2001
From: Lin Jen-Shin <godfat@godfat.org>
Date: Sat, 30 Jul 2016 00:59:54 +0800
Subject: [PATCH] Add pipeline_events to Slack service:

Also add Service#event_names so that we don't have to hard code all
event names in ServiceParams. Note that I don't know why we want to
call plural on issue_event and merge_request_event so that we still
need to hard code them for issues_event and merge_requests_event.
See app/helpers/services_helper.rb for those special rules.
---
 app/controllers/concerns/service_params.rb    | 15 ++--
 app/models/project_services/slack_service.rb  | 20 ++++-
 .../slack_service/pipeline_message.rb         | 77 +++++++++++++++++++
 app/models/service.rb                         |  4 +
 .../data_builder/pipeline_data_builder.rb     |  3 +-
 .../slack_service/pipeline_message_spec.rb    | 58 ++++++++++++++
 6 files changed, 166 insertions(+), 11 deletions(-)
 create mode 100644 app/models/project_services/slack_service/pipeline_message.rb
 create mode 100644 spec/models/project_services/slack_service/pipeline_message_spec.rb

diff --git a/app/controllers/concerns/service_params.rb b/app/controllers/concerns/service_params.rb
index 471d15af913..58877c5ad5d 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 abbc780dc1a..6584646e998 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 00000000000..3d12af3fa7e
--- /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 4a63abb2724..e4cd44f542a 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 fed9bd92ba4..46f8a1857b4 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 00000000000..2960a200e9d
--- /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
-- 
GitLab