BigW Consortium Gitlab

hipchat_service.rb 8.79 KB
Newer Older
1
class HipchatService < Service
2 3
  include ActionView::Helpers::SanitizeHelper

4
  MAX_COMMITS = 3
5 6 7 8
  HIPCHAT_ALLOWED_TAGS = %w[
    a b i strong em br img pre code
    table th tr td caption colgroup col thead tbody tfoot
    ul ol li dl dt dd
9
  ].freeze
10

11
  prop_accessor :token, :room, :server, :color, :api_version
12
  boolean_accessor :notify_only_broken_pipelines, :notify
13 14
  validates :token, presence: true, if: :activated?

15 16 17
  def initialize_properties
    if properties.nil?
      self.properties = {}
18
      self.notify_only_broken_pipelines = true
19 20
    end
  end
21

22
  def title
23
    'HipChat'
24 25 26
  end

  def description
27
    'Private group chat and IM'
28 29
  end

30
  def self.to_param
31 32 33 34 35
    'hipchat'
  end

  def fields
    [
36
      { type: 'text', name: 'token',     placeholder: 'Room token', required: true },
37
      { type: 'text', name: 'room',      placeholder: 'Room name or ID' },
38
      { type: 'checkbox', name: 'notify' },
Douwe Maan committed
39
      { type: 'select', name: 'color', choices: %w(yellow red green purple gray random) },
40 41
      { type: 'text', name: 'api_version',
        placeholder: 'Leave blank for default (v2)' },
42
      { type: 'text', name: 'server',
43
        placeholder: 'Leave blank for default. https://hipchat.example.com' },
44
      { type: 'checkbox', name: 'notify_only_broken_pipelines' }
45 46 47
    ]
  end

48
  def self.supported_events
49
    %w(push issue confidential_issue merge_request note tag_push pipeline)
50 51
  end

52
  def execute(data)
53
    return unless supported_events.include?(data[:object_kind])
54 55
    message = create_message(data)
    return unless message.present?
56
    gate[room].send('GitLab', message, message_options(data))
57 58
  end

59 60 61 62 63 64 65 66 67 68
  def test(data)
    begin
      result = execute(data)
    rescue StandardError => error
      return { success: false, result: error }
    end

    { success: true, result: result }
  end

69 70 71
  private

  def gate
72
    options = { api_version: api_version.present? ? api_version : 'v2' }
Drew Blessing committed
73
    options[:server_url] = server unless server.blank?
74
    @gate ||= HipChat::Client.new(token, options)
75 76
  end

77
  def message_options(data = nil)
78
    { notify: notify.present? && Gitlab::Utils.to_boolean(notify), color: message_color(data) }
79 80
  end

81 82 83
  def create_message(data)
    object_kind = data[:object_kind]

84 85 86 87 88 89 90 91 92
    case object_kind
    when "push", "tag_push"
      create_push_message(data)
    when "issue"
      create_issue_message(data) unless is_update?(data)
    when "merge_request"
      create_merge_request_message(data) unless is_update?(data)
    when "note"
      create_note_message(data)
93 94
    when "pipeline"
      create_pipeline_message(data) if should_pipeline_be_notified?(data)
95
    end
96 97
  end

98
  def render_line(text)
99
    markdown(text.lines.first.chomp, pipeline: :single_line) if text
100 101
  end

102
  def create_push_message(push)
103 104
    ref_type = Gitlab::Git.tag_ref?(push[:ref]) ? 'tag' : 'branch'
    ref = Gitlab::Git.ref_name(push[:ref])
105

106 107 108 109 110
    before = push[:before]
    after = push[:after]

    message = ""
    message << "#{push[:user_name]} "
111
    if Gitlab::Git.blank_ref?(before)
112
      message << "pushed new #{ref_type} <a href=\""\
113
                 "#{project_url}/commits/#{CGI.escape(ref)}\">#{ref}</a>"\
114
                 " to #{project_link}\n"
115
    elsif Gitlab::Git.blank_ref?(after)
116
      message << "removed #{ref_type} <b>#{ref}</b> from <a href=\"#{project.web_url}\">#{project_name}</a> \n"
117
    else
118
      message << "pushed to #{ref_type} <a href=\""\
119
                  "#{project.web_url}/commits/#{CGI.escape(ref)}\">#{ref}</a> "
120
      message << "of <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/, '')}</a> "
121
      message << "(<a href=\"#{project.web_url}/compare/#{before}...#{after}\">Compare changes</a>)"
122 123

      push[:commits].take(MAX_COMMITS).each do |commit|
124
        message << "<br /> - #{render_line(commit[:message])} (<a href=\"#{commit[:url]}\">#{commit[:id][0..5]}</a>)"
125 126 127 128
      end

      if push[:commits].count > MAX_COMMITS
        message << "<br />... #{push[:commits].count - MAX_COMMITS} more commits"
129 130 131 132 133
      end
    end

    message
  end
134

135
  def markdown(text, options = {})
136
    return "" unless text
137

138 139 140 141
    context = {
      project: project,
      pipeline: :email
    }
142

143
    Banzai.render(text, context)
144

145 146
    context.merge!(options)

147 148
    html = Banzai.post_process(Banzai.render(text, context), context)
    sanitized_html = sanitize(html, tags: HIPCHAT_ALLOWED_TAGS, attributes: %w[href title alt])
149

150
    sanitized_html.truncate(200, separator: ' ', omission: '...')
151 152
  end

153
  def create_issue_message(data)
154
    user_name = data[:user][:name]
155 156 157

    obj_attr = data[:object_attributes]
    obj_attr = HashWithIndifferentAccess.new(obj_attr)
158
    title = render_line(obj_attr[:title])
159 160 161 162 163
    state = obj_attr[:state]
    issue_iid = obj_attr[:iid]
    issue_url = obj_attr[:url]
    description = obj_attr[:description]

164 165
    issue_link = "<a href=\"#{issue_url}\">issue ##{issue_iid}</a>"
    message = "#{user_name} #{state} #{issue_link} in #{project_link}: <b>#{title}</b>"
166

167
    message << "<pre>#{markdown(description)}</pre>"
168 169 170 171 172

    message
  end

  def create_merge_request_message(data)
173
    user_name = data[:user][:name]
174 175 176 177 178 179

    obj_attr = data[:object_attributes]
    obj_attr = HashWithIndifferentAccess.new(obj_attr)
    merge_request_id = obj_attr[:iid]
    state = obj_attr[:state]
    description = obj_attr[:description]
180
    title = render_line(obj_attr[:title])
181 182

    merge_request_url = "#{project_url}/merge_requests/#{merge_request_id}"
183
    merge_request_link = "<a href=\"#{merge_request_url}\">merge request !#{merge_request_id}</a>"
184
    message = "#{user_name} #{state} #{merge_request_link} in " \
185 186
      "#{project_link}: <b>#{title}</b>"

187
    message << "<pre>#{markdown(description)}</pre>"
188 189 190 191 192

    message
  end

  def format_title(title)
193
    "<b>#{render_line(title)}</b>"
194 195 196 197
  end

  def create_note_message(data)
    data = HashWithIndifferentAccess.new(data)
198
    user_name = data[:user][:name]
199 200 201 202 203

    obj_attr = HashWithIndifferentAccess.new(data[:object_attributes])
    note = obj_attr[:note]
    note_url = obj_attr[:url]
    noteable_type = obj_attr[:noteable_type]
204
    commit_id = nil
205 206 207 208

    case noteable_type
    when "Commit"
      commit_attr = HashWithIndifferentAccess.new(data[:commit])
209 210
      commit_id = commit_attr[:id]
      subject_desc = commit_id
211 212 213 214 215 216 217 218 219 220 221 222
      subject_desc = Commit.truncate_sha(subject_desc)
      subject_type = "commit"
      title = format_title(commit_attr[:message])
    when "Issue"
      subj_attr = HashWithIndifferentAccess.new(data[:issue])
      subject_id = subj_attr[:iid]
      subject_desc = "##{subject_id}"
      subject_type = "issue"
      title = format_title(subj_attr[:title])
    when "MergeRequest"
      subj_attr = HashWithIndifferentAccess.new(data[:merge_request])
      subject_id = subj_attr[:iid]
223
      subject_desc = "!#{subject_id}"
224 225 226 227 228 229 230 231 232 233 234
      subject_type = "merge request"
      title = format_title(subj_attr[:title])
    when "Snippet"
      subj_attr = HashWithIndifferentAccess.new(data[:snippet])
      subject_id = subj_attr[:id]
      subject_desc = "##{subject_id}"
      subject_type = "snippet"
      title = format_title(subj_attr[:title])
    end

    subject_html = "<a href=\"#{note_url}\">#{subject_type} #{subject_desc}</a>"
235
    message = "#{user_name} commented on #{subject_html} in #{project_link}: "
236 237
    message << title

238
    message << "<pre>#{markdown(note, ref: commit_id)}</pre>"
239 240 241 242

    message
  end

243 244 245 246 247 248 249 250
  def create_pipeline_message(data)
    pipeline_attributes = data[:object_attributes]
    pipeline_id = pipeline_attributes[:id]
    ref_type = pipeline_attributes[:tag] ? 'tag' : 'branch'
    ref = pipeline_attributes[:ref]
    user_name = (data[:user] && data[:user][:name]) || 'API'
    status = pipeline_attributes[:status]
    duration = pipeline_attributes[:duration]
251

252
    branch_link = "<a href=\"#{project_url}/commits/#{CGI.escape(ref)}\">#{ref}</a>"
253
    pipeline_url = "<a href=\"#{project_url}/pipelines/#{pipeline_id}\">##{pipeline_id}</a>"
254

255
    "#{project_link}: Pipeline #{pipeline_url} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status(status)} in #{duration} second(s)"
256 257
  end

258
  def message_color(data)
259
    pipeline_status_color(data) || color || 'yellow'
260 261
  end

262 263
  def pipeline_status_color(data)
    return unless data && data[:object_kind] == 'pipeline'
264

265
    case data[:object_attributes][:status]
266
    when 'success'
267 268 269
      'green'
    else
      'red'
270
    end
271 272
  end

273 274 275 276 277 278 279 280 281 282 283 284 285 286 287
  def project_name
    project.name_with_namespace.gsub(/\s/, '')
  end

  def project_url
    project.web_url
  end

  def project_link
    "<a href=\"#{project_url}\">#{project_name}</a>"
  end

  def is_update?(data)
    data[:object_attributes][:action] == 'update'
  end
288 289 290 291 292 293 294 295 296 297

  def humanized_status(status)
    case status
    when 'success'
      'passed'
    else
      status
    end
  end

298 299
  def should_pipeline_be_notified?(data)
    case data[:object_attributes][:status]
300
    when 'success'
301
      !notify_only_broken_pipelines?
302 303 304 305 306 307
    when 'failed'
      true
    else
      false
    end
  end
Dmitriy Zaporozhets committed
308
end