hipchat_service.rb 8.73 KB
Newer Older
1 2 3 4
# == Schema Information
#
# Table name: services
#
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
5
#  id                    :integer          not null, primary key
Zeger-Jan van de Weg's avatar
Zeger-Jan van de Weg committed
6 7
#  type                  :string
#  title                 :string
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
8
#  project_id            :integer
Zeger-Jan van de Weg's avatar
Zeger-Jan van de Weg committed
9 10 11
#  created_at            :datetime         not null
#  updated_at            :datetime         not null
#  active                :boolean          not null
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
12 13 14 15 16 17
#  properties            :text
#  template              :boolean          default(FALSE)
#  push_events           :boolean          default(TRUE)
#  issues_events         :boolean          default(TRUE)
#  merge_requests_events :boolean          default(TRUE)
#  tag_push_events       :boolean          default(TRUE)
Stan Hu's avatar
Stan Hu committed
18
#  note_events           :boolean          default(TRUE), not null
Stan Hu's avatar
Stan Hu committed
19
#  build_events          :boolean          default(FALSE), not null
Zeger-Jan van de Weg's avatar
Zeger-Jan van de Weg committed
20 21 22
#  category              :string           default("common"), not null
#  default               :boolean          default(FALSE)
#  wiki_page_events      :boolean          default(TRUE)
23 24 25
#

class HipchatService < Service
26 27
  MAX_COMMITS = 3

28
  prop_accessor :token, :room, :server, :notify, :color, :api_version
29
  boolean_accessor :notify_only_broken_builds
30 31
  validates :token, presence: true, if: :activated?

Kamil Trzcinski's avatar
Kamil Trzcinski committed
32 33 34 35 36 37
  def initialize_properties
    if properties.nil?
      self.properties = {}
      self.notify_only_broken_builds = true
    end
  end
38

39
  def title
40
    'HipChat'
41 42 43
  end

  def description
44
    'Private group chat and IM'
45 46 47 48 49 50 51 52
  end

  def to_param
    'hipchat'
  end

  def fields
    [
53 54
      { type: 'text', name: 'token',     placeholder: 'Room token' },
      { type: 'text', name: 'room',      placeholder: 'Room name or ID' },
55 56
      { type: 'checkbox', name: 'notify' },
      { type: 'select', name: 'color', choices: ['yellow', 'red', 'green', 'purple', 'gray', 'random'] },
57 58
      { type: 'text', name: 'api_version',
        placeholder: 'Leave blank for default (v2)' },
59
      { type: 'text', name: 'server',
60 61
        placeholder: 'Leave blank for default. https://hipchat.example.com' },
      { type: 'checkbox', name: 'notify_only_broken_builds' },
62 63 64
    ]
  end

65
  def supported_events
66
    %w(push issue merge_request note tag_push build)
67 68
  end

69
  def execute(data)
70
    return unless supported_events.include?(data[:object_kind])
71 72
    message = create_message(data)
    return unless message.present?
73
    gate[room].send('GitLab', message, message_options)
74 75
  end

76 77 78 79 80 81 82 83 84 85
  def test(data)
    begin
      result = execute(data)
    rescue StandardError => error
      return { success: false, result: error }
    end

    { success: true, result: result }
  end

86 87 88
  private

  def gate
89
    options = { api_version: api_version.present? ? api_version : 'v2' }
Drew Blessing's avatar
Drew Blessing committed
90
    options[:server_url] = server unless server.blank?
91
    @gate ||= HipChat::Client.new(token, options)
92 93
  end

94 95 96 97
  def message_options
    { notify: notify.present? && notify == '1', color: color || 'yellow' }
  end

98 99 100
  def create_message(data)
    object_kind = data[:object_kind]

101 102 103 104 105 106 107 108 109
    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)
110 111
    when "build"
      create_build_message(data) if should_build_be_notified?(data)
112
    end
113 114 115
  end

  def create_push_message(push)
116 117
    ref_type = Gitlab::Git.tag_ref?(push[:ref]) ? 'tag' : 'branch'
    ref = Gitlab::Git.ref_name(push[:ref])
118

119 120 121 122 123
    before = push[:before]
    after = push[:after]

    message = ""
    message << "#{push[:user_name]} "
124
    if Gitlab::Git.blank_ref?(before)
125
      message << "pushed new #{ref_type} <a href=\""\
126
                 "#{project_url}/commits/#{CGI.escape(ref)}\">#{ref}</a>"\
127
                 " to #{project_link}\n"
128
    elsif Gitlab::Git.blank_ref?(after)
129
      message << "removed #{ref_type} <b>#{ref}</b> from <a href=\"#{project.web_url}\">#{project_name}</a> \n"
130
    else
131
      message << "pushed to #{ref_type} <a href=\""\
132
                  "#{project.web_url}/commits/#{CGI.escape(ref)}\">#{ref}</a> "
133 134
      message << "of <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/,'')}</a> "
      message << "(<a href=\"#{project.web_url}/compare/#{before}...#{after}\">Compare changes</a>)"
135 136 137 138 139 140 141

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

      if push[:commits].count > MAX_COMMITS
        message << "<br />... #{push[:commits].count - MAX_COMMITS} more commits"
142 143 144 145 146
      end
    end

    message
  end
147

148 149 150 151 152 153 154 155
  def format_body(body)
    if body
      body = body.truncate(200, separator: ' ', omission: '...')
    end

    "<pre>#{body}</pre>"
  end

156
  def create_issue_message(data)
157
    user_name = data[:user][:name]
158 159 160 161 162 163 164 165 166

    obj_attr = data[:object_attributes]
    obj_attr = HashWithIndifferentAccess.new(obj_attr)
    title = obj_attr[:title]
    state = obj_attr[:state]
    issue_iid = obj_attr[:iid]
    issue_url = obj_attr[:url]
    description = obj_attr[:description]

167 168
    issue_link = "<a href=\"#{issue_url}\">issue ##{issue_iid}</a>"
    message = "#{user_name} #{state} #{issue_link} in #{project_link}: <b>#{title}</b>"
169 170

    if description
171 172
      description = format_body(description)
      message << description
173 174 175 176 177 178
    end

    message
  end

  def create_merge_request_message(data)
179
    user_name = data[:user][:name]
180 181 182 183 184 185 186 187 188

    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]
    title = obj_attr[:title]

    merge_request_url = "#{project_url}/merge_requests/#{merge_request_id}"
189
    merge_request_link = "<a href=\"#{merge_request_url}\">merge request !#{merge_request_id}</a>"
190
    message = "#{user_name} #{state} #{merge_request_link} in " \
191 192 193
      "#{project_link}: <b>#{title}</b>"

    if description
194 195 196 197 198 199 200 201 202 203 204 205 206
      description = format_body(description)
      message << description
    end

    message
  end

  def format_title(title)
    "<b>" + title.lines.first.chomp + "</b>"
  end

  def create_note_message(data)
    data = HashWithIndifferentAccess.new(data)
207
    user_name = data[:user][:name]
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229

    obj_attr = HashWithIndifferentAccess.new(data[:object_attributes])
    note = obj_attr[:note]
    note_url = obj_attr[:url]
    noteable_type = obj_attr[:noteable_type]

    case noteable_type
    when "Commit"
      commit_attr = HashWithIndifferentAccess.new(data[:commit])
      subject_desc = commit_attr[:id]
      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]
230
      subject_desc = "!#{subject_id}"
231 232 233 234 235 236 237 238 239 240 241
      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>"
242
    message = "#{user_name} commented on #{subject_html} in #{project_link}: "
243 244 245 246 247
    message << title

    if note
      note = format_body(note)
      message << note
248 249 250 251 252
    end

    message
  end

253 254 255 256 257 258 259 260
  def create_build_message(data)
    ref_type = data[:tag] ? 'tag' : 'branch'
    ref = data[:ref]
    sha = data[:sha]
    user_name = data[:commit][:author_name]
    status = data[:commit][:status]
    duration = data[:commit][:duration]

261 262
    branch_link = "<a href=\"#{project_url}/commits/#{CGI.escape(ref)}\">#{ref}</a>"
    commit_link = "<a href=\"#{project_url}/commit/#{CGI.escape(sha)}/builds\">#{Commit.truncate_sha(sha)}</a>"
263 264 265 266

    "#{project_link}: Commit #{commit_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status(status)} in #{duration} second(s)"
  end

267 268 269 270 271 272 273 274 275 276 277 278 279 280 281
  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
282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301

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

  def should_build_be_notified?(data)
    case data[:commit][:status]
    when 'success'
      !notify_only_broken_builds?
    when 'failed'
      true
    else
      false
    end
  end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
302
end