summaryrefslogtreecommitdiff
path: root/app/models/project_services/slack_service.rb
blob: abbc780dc1a09fe6e45ea29102b2ea93b5049539 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
class SlackService < Service
  prop_accessor :webhook, :username, :channel
  boolean_accessor :notify_only_broken_builds
  validates :webhook, presence: true, url: true, if: :activated?

  def initialize_properties
    # Custom serialized properties initialization
    self.supported_events.each { |event| self.class.prop_accessor(event_channel_name(event)) }

    if properties.nil?
      self.properties = {}
      self.notify_only_broken_builds = true
    end
  end

  def title
    'Slack'
  end

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

  def to_param
    'slack'
  end

  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

  def fields
    default_fields =
      [
        { type: 'text', name: 'webhook',   placeholder: 'https://hooks.slack.com/services/...' },
        { type: 'text', name: 'username', placeholder: 'username' },
        { type: 'text', name: 'channel', placeholder: "#general" },
        { type: 'checkbox', name: 'notify_only_broken_builds' },
      ]

    default_fields + build_event_channels
  end

  def supported_events
    %w(push issue merge_request note tag_push build wiki_page)
  end

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

    object_kind = data[:object_kind]

    data = data.merge(
      project_url: project_url,
      project_name: project_name
    )

    # 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.

    message = \
      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 "wiki_page"
        WikiPageMessage.new(data)
      end

    opt = {}

    event_channel = get_channel_field(object_kind) || channel

    opt[:channel] = event_channel if event_channel
    opt[:username] = username if username

    if message
      notifier = Slack::Notifier.new(webhook, opt)
      notifier.ping(message.pretext, attachments: message.attachments, fallback: message.fallback)
    end
  end

  def event_channel_names
    supported_events.map { |event| event_channel_name(event) }
  end

  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

  private

  def get_channel_field(event)
    field_name = event_channel_name(event)
    self.public_send(field_name)
  end

  def build_event_channels
    supported_events.reduce([]) do |channels, event|
      channels << { type: 'text', name: event_channel_name(event), placeholder: "#general" }
    end
  end

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

  def project_name
    project.name_with_namespace.gsub(/\s/, '')
  end

  def project_url
    project.web_url
  end

  def is_update?(data)
    data[:object_attributes][:action] == 'update'
  end

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

require "slack_service/issue_message"
require "slack_service/push_message"
require "slack_service/merge_message"
require "slack_service/note_message"
require "slack_service/build_message"
require "slack_service/wiki_page_message"