From cd9ae6bb820ae8ec98cce38d958298e081860dab Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Tue, 9 Apr 2019 14:06:36 +0100 Subject: Revert "Remove HipChat integration from GitLab" This reverts commit a5378665a1dc0b9c8dc3a4fa279a0eb78aac5aac. --- app/models/project_services/hipchat_service.rb | 311 +++++++++++++++++++++++++ 1 file changed, 311 insertions(+) create mode 100644 app/models/project_services/hipchat_service.rb (limited to 'app/models/project_services') diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb new file mode 100644 index 00000000000..a69b7b4c4b6 --- /dev/null +++ b/app/models/project_services/hipchat_service.rb @@ -0,0 +1,311 @@ +# frozen_string_literal: true + +class HipchatService < Service + include ActionView::Helpers::SanitizeHelper + + MAX_COMMITS = 3 + 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 + ].freeze + + prop_accessor :token, :room, :server, :color, :api_version + boolean_accessor :notify_only_broken_pipelines, :notify + validates :token, presence: true, if: :activated? + + def initialize_properties + if properties.nil? + self.properties = {} + self.notify_only_broken_pipelines = true + end + end + + def title + 'HipChat' + end + + def description + 'Private group chat and IM' + end + + def self.to_param + 'hipchat' + end + + def fields + [ + { type: 'text', name: 'token', placeholder: 'Room token', required: true }, + { type: 'text', name: 'room', placeholder: 'Room name or ID' }, + { type: 'checkbox', name: 'notify' }, + { type: 'select', name: 'color', choices: %w(yellow red green purple gray random) }, + { type: 'text', name: 'api_version', + placeholder: 'Leave blank for default (v2)' }, + { type: 'text', name: 'server', + placeholder: 'Leave blank for default. https://hipchat.example.com' }, + { type: 'checkbox', name: 'notify_only_broken_pipelines' } + ] + end + + def self.supported_events + %w(push issue confidential_issue merge_request note confidential_note tag_push pipeline) + end + + def execute(data) + return unless supported_events.include?(data[:object_kind]) + + message = create_message(data) + return unless message.present? + + gate[room].send('GitLab', message, message_options(data)) # rubocop:disable GitlabSecurity/PublicSend + end + + def test(data) + begin + result = execute(data) + rescue StandardError => error + return { success: false, result: error } + end + + { success: true, result: result } + end + + private + + def gate + options = { api_version: api_version.present? ? api_version : 'v2' } + options[:server_url] = server unless server.blank? + @gate ||= HipChat::Client.new(token, options) + end + + def message_options(data = nil) + { notify: notify.present? && Gitlab::Utils.to_boolean(notify), color: message_color(data) } + end + + def create_message(data) + object_kind = data[:object_kind] + + case object_kind + when "push", "tag_push" + create_push_message(data) + when "issue" + create_issue_message(data) unless update?(data) + when "merge_request" + create_merge_request_message(data) unless update?(data) + when "note" + create_note_message(data) + when "pipeline" + create_pipeline_message(data) if should_pipeline_be_notified?(data) + end + end + + def render_line(text) + markdown(text.lines.first.chomp, pipeline: :single_line) if text + end + + def create_push_message(push) + ref_type = Gitlab::Git.tag_ref?(push[:ref]) ? 'tag' : 'branch' + ref = Gitlab::Git.ref_name(push[:ref]) + + before = push[:before] + after = push[:after] + + message = [] + message << "#{push[:user_name]} " + + if Gitlab::Git.blank_ref?(before) + message << "pushed new #{ref_type} #{ref}"\ + " to #{project_link}\n" + elsif Gitlab::Git.blank_ref?(after) + message << "removed #{ref_type} #{ref} from #{project_name} \n" + else + message << "pushed to #{ref_type} #{ref} " + message << "of #{project.full_name.gsub!(/\s/, '')} " + message << "(Compare changes)" + + push[:commits].take(MAX_COMMITS).each do |commit| + message << "
- #{render_line(commit[:message])} (#{commit[:id][0..5]})" + end + + if push[:commits].count > MAX_COMMITS + message << "
... #{push[:commits].count - MAX_COMMITS} more commits" + end + end + + message.join + end + + def markdown(text, options = {}) + return "" unless text + + context = { + project: project, + pipeline: :email + } + + Banzai.render(text, context) + + context.merge!(options) + + html = Banzai.render_and_post_process(text, context) + sanitized_html = sanitize(html, tags: HIPCHAT_ALLOWED_TAGS, attributes: %w[href title alt]) + + sanitized_html.truncate(200, separator: ' ', omission: '...') + end + + def create_issue_message(data) + user_name = data[:user][:name] + + obj_attr = data[:object_attributes] + obj_attr = HashWithIndifferentAccess.new(obj_attr) + title = render_line(obj_attr[:title]) + state = obj_attr[:state] + issue_iid = obj_attr[:iid] + issue_url = obj_attr[:url] + description = obj_attr[:description] + + issue_link = "issue ##{issue_iid}" + + message = ["#{user_name} #{state} #{issue_link} in #{project_link}: #{title}"] + message << "
#{markdown(description)}
" + + message.join + end + + def create_merge_request_message(data) + user_name = data[:user][:name] + + 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 = render_line(obj_attr[:title]) + + merge_request_url = "#{project_url}/merge_requests/#{merge_request_id}" + merge_request_link = "merge request !#{merge_request_id}" + message = ["#{user_name} #{state} #{merge_request_link} in " \ + "#{project_link}: #{title}"] + + message << "
#{markdown(description)}
" + message.join + end + + def format_title(title) + "#{render_line(title)}" + end + + def create_note_message(data) + data = HashWithIndifferentAccess.new(data) + user_name = data[:user][:name] + + obj_attr = HashWithIndifferentAccess.new(data[:object_attributes]) + note = obj_attr[:note] + note_url = obj_attr[:url] + noteable_type = obj_attr[:noteable_type] + commit_id = nil + + case noteable_type + when "Commit" + commit_attr = HashWithIndifferentAccess.new(data[:commit]) + commit_id = commit_attr[:id] + subject_desc = commit_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] + subject_desc = "!#{subject_id}" + 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 = "#{subject_type} #{subject_desc}" + message = ["#{user_name} commented on #{subject_html} in #{project_link}: "] + message << title + + message << "
#{markdown(note, ref: commit_id)}
" + message.join + end + + 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] + + branch_link = "#{ref}" + pipeline_url = "##{pipeline_id}" + + "#{project_link}: Pipeline #{pipeline_url} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status(status)} in #{duration} second(s)" + end + + def message_color(data) + pipeline_status_color(data) || color || 'yellow' + end + + def pipeline_status_color(data) + return unless data && data[:object_kind] == 'pipeline' + + case data[:object_attributes][:status] + when 'success' + 'green' + else + 'red' + end + end + + def project_name + project.full_name.gsub(/\s/, '') + end + + def project_url + project.web_url + end + + def project_link + "#{project_name}" + end + + def update?(data) + data[:object_attributes][:action] == 'update' + end + + def humanized_status(status) + case status + when 'success' + 'passed' + else + status + end + end + + def should_pipeline_be_notified?(data) + case data[:object_attributes][:status] + when 'success' + !notify_only_broken_pipelines? + when 'failed' + true + else + false + end + end +end -- cgit v1.2.3