slack_service.rb 4.36 KB
Newer Older
1
class SlackService < Service
2
  prop_accessor :webhook, :username, :channel
3
  boolean_accessor :notify_only_broken_builds, :notify_only_broken_pipelines
4
  validates :webhook, presence: true, url: true, if: :activated?
5

Kamil Trzcinski's avatar
Kamil Trzcinski committed
6
  def initialize_properties
7
    # Custom serialized properties initialization
8
    self.supported_events.each { |event| self.class.prop_accessor(event_channel_name(event)) }
9

Kamil Trzcinski's avatar
Kamil Trzcinski committed
10 11 12
    if properties.nil?
      self.properties = {}
      self.notify_only_broken_builds = true
13
      self.notify_only_broken_pipelines = true
Kamil Trzcinski's avatar
Kamil Trzcinski committed
14 15
    end
  end
16

17 18 19 20 21 22 23 24 25 26 27 28
  def title
    'Slack'
  end

  def description
    'A team communication tool for the 21st century'
  end

  def to_param
    'slack'
  end

29 30 31 32 33 34
  def help
    'This service sends notifications to your Slack channel.<br/>
    To setup this Service you need to create a new <b>"Incoming webhook"</b> in your Slack integration panel,
    and enter the Webhook URL below.'
  end

35
  def fields
36 37 38 39
    default_fields =
      [
        { type: 'text', name: 'webhook',   placeholder: 'https://hooks.slack.com/services/...' },
        { type: 'text', name: 'username', placeholder: 'username' },
40
        { type: 'text', name: 'channel', placeholder: "#general" },
41
        { type: 'checkbox', name: 'notify_only_broken_builds' },
42
        { type: 'checkbox', name: 'notify_only_broken_pipelines' },
43 44 45
      ]

    default_fields + build_event_channels
46 47
  end

48
  def supported_events
49 50
    %w[push issue confidential_issue merge_request note tag_push
       build pipeline wiki_page]
51 52
  end

53
  def execute(data)
54
    return unless supported_events.include?(data[:object_kind])
55 56
    return unless webhook.present?

57 58 59
    object_kind = data[:object_kind]

    data = data.merge(
60 61
      project_url: project_url,
      project_name: project_name
62 63 64 65 66 67
    )

    # WebHook events often have an 'update' event that follows a 'open' or
    # 'close' action. Ignore update events for now to prevent duplicate
    # messages from arriving.

68
    message = get_message(object_kind, data)
69

70 71
    if message
      opt = {}
72

73
      event_channel = get_channel_field(object_kind) || channel
74

75 76
      opt[:channel] = event_channel if event_channel
      opt[:username] = username if username
77

78
      notifier = Slack::Notifier.new(webhook, opt)
79
      notifier.ping(message.pretext, attachments: message.attachments, fallback: message.fallback)
80 81 82 83

      true
    else
      false
84
    end
85 86
  end

87 88 89 90
  def event_channel_names
    supported_events.map { |event| event_channel_name(event) }
  end

91 92 93 94 95 96 97 98
  def event_field(event)
    fields.find { |field| field[:name] == event_channel_name(event) }
  end

  def global_fields
    fields.reject { |field| field[:name].end_with?('channel') }
  end

99 100
  private

101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
  def get_message(object_kind, data)
    case object_kind
    when "push", "tag_push"
      PushMessage.new(data)
    when "issue"
      IssueMessage.new(data) unless is_update?(data)
    when "merge_request"
      MergeMessage.new(data) unless is_update?(data)
    when "note"
      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
  end

120 121
  def get_channel_field(event)
    field_name = event_channel_name(event)
122
    self.public_send(field_name)
123 124 125
  end

  def build_event_channels
126 127
    supported_events.reduce([]) do |channels, event|
      channels << { type: 'text', name: event_channel_name(event), placeholder: "#general" }
128 129 130 131 132 133 134
    end
  end

  def event_channel_name(event)
    "#{event}_channel"
  end

135 136 137 138 139 140 141
  def project_name
    project.name_with_namespace.gsub(/\s/, '')
  end

  def project_url
    project.web_url
  end
142 143 144 145

  def is_update?(data)
    data[:object_attributes][:action] == 'update'
  end
146 147 148 149 150 151 152 153 154 155 156

  def should_build_be_notified?(data)
    case data[:commit][:status]
    when 'success'
      !notify_only_broken_builds?
    when 'failed'
      true
    else
      false
    end
  end
157 158 159 160

  def should_pipeline_be_notified?(data)
    case data[:object_attributes][:status]
    when 'success'
161
      !notify_only_broken_pipelines?
162 163 164 165 166 167
    when 'failed'
      true
    else
      false
    end
  end
168
end
169 170 171

require "slack_service/issue_message"
require "slack_service/push_message"
Douwe Maan's avatar
Douwe Maan committed
172
require "slack_service/merge_message"
173
require "slack_service/note_message"
174
require "slack_service/build_message"
175
require "slack_service/pipeline_message"
176
require "slack_service/wiki_page_message"