summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG1
-rw-r--r--Gemfile3
-rw-r--r--Gemfile.lock5
-rw-r--r--app/controllers/services_controller.rb3
-rw-r--r--app/models/project.rb3
-rw-r--r--app/models/project_services/hip_chat_message.rb76
-rw-r--r--app/models/project_services/hip_chat_service.rb80
-rw-r--r--app/workers/hip_chat_notifier_worker.rb18
-rw-r--r--spec/models/project_services/hip_chat_message_spec.rb65
-rw-r--r--spec/models/project_services/hip_chat_service_spec.rb61
-rw-r--r--spec/models/project_services/slack_message_spec.rb (renamed from spec/models/slack_message_spec.rb)0
-rw-r--r--spec/models/project_services/slack_service_spec.rb (renamed from spec/models/slack_service_spec.rb)0
12 files changed, 313 insertions, 2 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 84499ee..5f157e0 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -4,6 +4,7 @@ v7.11.0
- Improved runners page
- Running and Pending tabs on admin builds page
- Fix [ci skip] tag, so you can skip CI triggering now
+ - Add HipChat notifications
v7.10.1
- Fix failing migration when update to 7.10 from 7.8 and older versions
diff --git a/Gemfile b/Gemfile
index b0b803e..9950761 100644
--- a/Gemfile
+++ b/Gemfile
@@ -63,6 +63,9 @@ gem "default_value_for", "~> 3.0.0"
# Slack integration
gem "slack-notifier", "~> 1.0.0"
+# HipChat integration
+gem 'hipchat', '~> 1.5.0'
+
# Other
gem 'rake'
gem 'foreman'
diff --git a/Gemfile.lock b/Gemfile.lock
index 41bc118..d2c9158 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -156,6 +156,9 @@ GEM
hashie (2.0.5)
highline (1.6.21)
hike (1.2.3)
+ hipchat (1.5.0)
+ httparty
+ mimemagic
httparty (0.11.0)
multi_json (~> 1.0)
multi_xml (>= 0.5.2)
@@ -186,6 +189,7 @@ GEM
mime-types (>= 1.16, < 3)
method_source (0.8.2)
mime-types (2.4.3)
+ mimemagic (0.3.0)
mini_portile (0.5.2)
minitest (5.5.1)
multi_json (1.11.0)
@@ -390,6 +394,7 @@ DEPENDENCIES
growl
guard-rspec
haml-rails (~> 0.5.3)
+ hipchat (~> 1.5.0)
httparty (= 0.11.0)
jquery-rails
jquery-turbolinks
diff --git a/app/controllers/services_controller.rb b/app/controllers/services_controller.rb
index 39cf306..64bc698 100644
--- a/app/controllers/services_controller.rb
+++ b/app/controllers/services_controller.rb
@@ -50,7 +50,8 @@ class ServicesController < ApplicationController
def service_params
params.require(:service).permit(
:type, :active, :webhook, :notify_only_broken_builds,
- :email_recipients, :email_only_broken_builds, :email_add_pusher
+ :email_recipients, :email_only_broken_builds, :email_add_pusher,
+ :hipchat_token, :hipchat_room, :hipchat_server
)
end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 31ce6ab..c48b079 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -37,6 +37,7 @@ class Project < ActiveRecord::Base
# Project services
has_many :services, dependent: :destroy
+ has_one :hip_chat_service, dependent: :destroy
has_one :slack_service, dependent: :destroy
has_one :mail_service, dependent: :destroy
@@ -210,7 +211,7 @@ ls -la
end
def available_services_names
- %w(slack mail)
+ %w(slack mail hip_chat)
end
def build_missing_services
diff --git a/app/models/project_services/hip_chat_message.rb b/app/models/project_services/hip_chat_message.rb
new file mode 100644
index 0000000..8350bb6
--- /dev/null
+++ b/app/models/project_services/hip_chat_message.rb
@@ -0,0 +1,76 @@
+class HipChatMessage
+ attr_reader :build
+
+ def initialize(build)
+ @build = build
+ end
+
+ def to_s
+ lines = Array.new
+ lines.push("<a href=\"#{RoutesHelper.project_url(project)}\">#{project.name}</a> - ")
+ if commit.matrix?
+ lines.push("<a href=\"#{RoutesHelper.project_ref_commit_url(project, commit.ref, commit.sha)}\">Commit ##{commit.id}</a></br>")
+ else
+ first_build = commit.builds_without_retry.first
+ lines.push("<a href=\"#{RoutesHelper.project_build_url(project, first_build)}\">Build '#{first_build.job_name}' ##{first_build.id}</a></br>")
+ end
+ lines.push("#{commit.short_sha} #{commit.git_author_name} - #{commit.git_commit_message}</br>")
+ lines.push("#{humanized_status(commit_status)} in #{commit.duration} second(s).")
+ lines.join('')
+ end
+
+ def status_color(build_or_commit=nil)
+ build_or_commit ||= commit_status
+ case build_or_commit
+ when :success
+ 'green'
+ when :failed, :canceled
+ 'red'
+ else # :pending, :running or unknown
+ 'yellow'
+ end
+ end
+
+ def notify?
+ [:failed, :canceled].include?(commit_status)
+ end
+
+
+ private
+
+ def commit
+ build.commit
+ end
+
+ def project
+ commit.project
+ end
+
+ def build_status
+ build.status.to_sym
+ end
+
+ def commit_status
+ commit.status.to_sym
+ end
+
+ def humanized_status(build_or_commit=nil)
+ build_or_commit ||= commit_status
+ case build_or_commit
+ when :pending
+ "Pending"
+ when :running
+ "Running"
+ when :failed
+ "Failed"
+ when :success
+ "Successful"
+ when :canceled
+ "Canceled"
+ else
+ "Unknown"
+ end
+ end
+
+end
+
diff --git a/app/models/project_services/hip_chat_service.rb b/app/models/project_services/hip_chat_service.rb
new file mode 100644
index 0000000..8e5f024
--- /dev/null
+++ b/app/models/project_services/hip_chat_service.rb
@@ -0,0 +1,80 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer not null
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+#
+
+class HipChatService < Service
+ prop_accessor :hipchat_token, :hipchat_room, :hipchat_server
+ boolean_accessor :notify_only_broken_builds
+ validates :hipchat_token, presence: true, if: :activated?
+ validates :hipchat_room, presence: true, if: :activated?
+ default_value_for :notify_only_broken_builds, true
+
+ def title
+ "HipChat"
+ end
+
+ def description
+ "Private group chat, video chat, instant messaging for teams"
+ end
+
+ def help
+ end
+
+ def to_param
+ 'hip_chat'
+ end
+
+ def fields
+ [
+ { type: 'text', name: 'hipchat_token', label: 'Token', placeholder: '' },
+ { type: 'text', name: 'hipchat_room', label: 'Room', placeholder: '' },
+ { type: 'text', name: 'hipchat_server', label: 'Server', placeholder: 'https://hipchat.example.com', help: 'Leave blank for default' },
+ { type: 'checkbox', name: 'notify_only_broken_builds', label: 'Notify only broken builds' }
+ ]
+ end
+
+ def execute build
+ commit = build.commit
+ return unless commit
+ return unless commit.builds_without_retry.include? build
+ return if commit.success? and notify_only_broken_builds?
+ return if commit.running?
+
+ msg = HipChatMessage.new(build)
+ opts = default_options.merge(
+ token: hipchat_token,
+ room: hipchat_room,
+ server: server_url,
+ color: msg.status_color,
+ notify: msg.notify?
+ )
+ HipChatNotifierWorker.perform_async(msg.to_s, opts)
+ end
+
+ private
+
+ def default_options
+ {
+ service_name: 'GitLab CI',
+ message_format: 'html'
+ }
+ end
+
+ def server_url
+ if hipchat_server.blank?
+ 'https://api.hipchat.com'
+ else
+ hipchat_server
+ end
+ end
+end
diff --git a/app/workers/hip_chat_notifier_worker.rb b/app/workers/hip_chat_notifier_worker.rb
new file mode 100644
index 0000000..0403578
--- /dev/null
+++ b/app/workers/hip_chat_notifier_worker.rb
@@ -0,0 +1,18 @@
+
+class HipChatNotifierWorker
+ include Sidekiq::Worker
+
+ def perform(message, options={})
+ room = options.delete('room')
+ token = options.delete('token')
+ server = options.delete('server')
+ name = options.delete('service_name')
+ client_opts = {
+ api_version: 'v2',
+ server_url: server
+ }
+
+ client = HipChat::Client.new(token, client_opts)
+ client[room].send(name, message, options.symbolize_keys)
+ end
+end
diff --git a/spec/models/project_services/hip_chat_message_spec.rb b/spec/models/project_services/hip_chat_message_spec.rb
new file mode 100644
index 0000000..6096b5c
--- /dev/null
+++ b/spec/models/project_services/hip_chat_message_spec.rb
@@ -0,0 +1,65 @@
+require 'spec_helper'
+
+describe HipChatMessage do
+ subject { HipChatMessage.new(build) }
+
+ let(:project) { FactoryGirl.create(:project) }
+ let(:commit) { FactoryGirl.create(:commit, project: project) }
+ let(:job) { FactoryGirl.create(:job, project: project) }
+ let(:build) { FactoryGirl.create(:build, commit: commit, job: job, status: 'success') }
+
+ context 'when build succeeds' do
+
+ before { build.save }
+
+ it 'returns a successful message' do
+ expect( subject.status_color ).to eq 'green'
+ expect( subject.notify? ).to be_false
+ expect( subject.to_s ).to match(/Build '[^']+' #\d+/)
+ expect( subject.to_s ).to match(/Successful in \d+ second\(s\)\./)
+ end
+ end
+
+ context 'when build fails' do
+
+ before do
+ build.status = 'failed'
+ build.save
+ end
+
+ it 'returns a failure message' do
+ expect( subject.status_color ).to eq 'red'
+ expect( subject.notify? ).to be_true
+ expect( subject.to_s ).to match(/Build '[^']+' #\d+/)
+ expect( subject.to_s ).to match(/Failed in \d+ second\(s\)\./)
+ end
+ end
+
+ context 'when all matrix builds succeed' do
+ let(:job2) { FactoryGirl.create(:job, project: project, name: 'Another Job') }
+ let(:build2) { FactoryGirl.create(:build, id: 10, commit: commit, job: job2, status: 'success') }
+
+ before { build.save; build2.save }
+
+ it 'returns a successful message' do
+ expect( subject.status_color ).to eq 'green'
+ expect( subject.notify? ).to be_false
+ expect( subject.to_s ).to match(/Commit #\d+/)
+ expect( subject.to_s ).to match(/Successful in \d+ second\(s\)\./)
+ end
+ end
+
+ context 'when at least one matrix build fails' do
+ let(:job2) { FactoryGirl.create(:job, project: project, name: 'Another Job') }
+ let(:build2) { FactoryGirl.create(:build, id: 10, commit: commit, job: job2, status: 'failed') }
+
+ before { build.save; build2.save }
+
+ it 'returns a failure message' do
+ expect( subject.status_color ).to eq 'red'
+ expect( subject.notify? ).to be_true
+ expect( subject.to_s ).to match(/Commit #\d+/)
+ expect( subject.to_s ).to match(/Failed in \d+ second\(s\)\./)
+ end
+ end
+end
diff --git a/spec/models/project_services/hip_chat_service_spec.rb b/spec/models/project_services/hip_chat_service_spec.rb
new file mode 100644
index 0000000..5642221
--- /dev/null
+++ b/spec/models/project_services/hip_chat_service_spec.rb
@@ -0,0 +1,61 @@
+
+require 'spec_helper'
+
+describe HipChatService do
+
+ describe "Validations" do
+
+ context "active" do
+ before do
+ subject.active = true
+ end
+
+ it { should validate_presence_of :hipchat_room }
+ it { should validate_presence_of :hipchat_token }
+
+ end
+ end
+
+ describe "Execute" do
+
+ let(:service) { HipChatService.new }
+ let(:project) { FactoryGirl.create :project }
+ let(:commit) { FactoryGirl.create :commit, project: project }
+ let(:build) { FactoryGirl.create :build, commit: commit, status: 'failed' }
+ let(:api_url) { 'https://api.hipchat.com/v2/room/123/notification?auth_token=a1b2c3d4e5f6' }
+
+ before do
+ service.stub(
+ project: project,
+ project_id: project.id,
+ notify_only_broken_builds: false,
+ hipchat_room: 123,
+ hipchat_token: 'a1b2c3d4e5f6'
+ )
+
+ WebMock.stub_request(:post, api_url)
+ end
+
+
+ it "should call the HipChat API" do
+ service.execute(build)
+ HipChatNotifierWorker.drain
+
+ expect( WebMock ).to have_requested(:post, api_url).once
+ end
+
+ it "calls the worker with expected arguments" do
+ expect( HipChatNotifierWorker ).to receive(:perform_async) \
+ .with(an_instance_of(String), hash_including(
+ token: 'a1b2c3d4e5f6',
+ room: 123,
+ server: 'https://api.hipchat.com',
+ color: 'red',
+ notify: true
+ ))
+
+ service.execute(build)
+ end
+ end
+end
+
diff --git a/spec/models/slack_message_spec.rb b/spec/models/project_services/slack_message_spec.rb
index 1fa2e31..1fa2e31 100644
--- a/spec/models/slack_message_spec.rb
+++ b/spec/models/project_services/slack_message_spec.rb
diff --git a/spec/models/slack_service_spec.rb b/spec/models/project_services/slack_service_spec.rb
index e1c1428..e1c1428 100644
--- a/spec/models/slack_service_spec.rb
+++ b/spec/models/project_services/slack_service_spec.rb