summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>2014-03-19 09:36:51 +0200
committerDmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>2014-03-19 09:36:51 +0200
commit11a56ccd2189592758335c5346828d3c61b0e6f9 (patch)
treef0d01f3ed7f2eff37d0ab81d268592101d1a2a52
parentacaf297846907bff096dcbd71d6665ac6d4b8abc (diff)
parente3fc01b3eaa13a51aef380dc21f1e7d259706227 (diff)
downloadgitlab-ce-11a56ccd2189592758335c5346828d3c61b0e6f9.tar.gz
Merge pull request #6550 from razielgn/slack-integration
Added Slack service integration.
-rw-r--r--CHANGELOG1
-rw-r--r--Gemfile3
-rw-r--r--Gemfile.lock2
-rw-r--r--app/models/project.rb3
-rw-r--r--app/models/project_services/slack_message.rb95
-rw-r--r--app/models/project_services/slack_service.rb67
-rw-r--r--features/project/service.feature6
-rw-r--r--features/steps/project/services.rb18
-rw-r--r--spec/models/project_spec.rb1
-rw-r--r--spec/models/slack_message_spec.rb56
-rw-r--r--spec/models/slack_service_spec.rb69
11 files changed, 320 insertions, 1 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 031536816a5..7d61fef39a4 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -24,6 +24,7 @@ v 6.7.0
- Faster authorized_keys rebuilding in `rake gitlab:shell:setup` (requires gitlab-shell 1.8.5)
- Create and Update MR calls now support the description parameter (Greg Messner)
- Markdown relative links in the wiki link to wiki pages, markdown relative links in repositories link to files in the repository
+ - Added Slack service integration (Federico Ravasio)
v 6.6.5
- Added option to remove issue assignee on project issue page and issue edit page (Jason Blanchard)
diff --git a/Gemfile b/Gemfile
index e3acf4a153d..6a587d1d279 100644
--- a/Gemfile
+++ b/Gemfile
@@ -132,6 +132,9 @@ gem "gitlab-flowdock-git-hook", "~> 0.4.2"
# Gemnasium integration
gem "gemnasium-gitlab-service", "~> 0.2"
+# Slack integration
+gem "slack-notifier", "~> 0.2.0"
+
# d3
gem "d3_rails", "~> 3.1.4"
diff --git a/Gemfile.lock b/Gemfile.lock
index 52d6ac31463..de1ef59712e 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -468,6 +468,7 @@ GEM
rack-protection (~> 1.4)
tilt (~> 1.3, >= 1.3.4)
six (0.2.0)
+ slack-notifier (0.2.0)
slim (2.0.2)
temple (~> 0.6.6)
tilt (>= 1.3.3, < 2.1)
@@ -652,6 +653,7 @@ DEPENDENCIES
simplecov
sinatra
six
+ slack-notifier (~> 0.2.0)
slim
spinach-rails
spork (~> 1.0rc)
diff --git a/app/models/project.rb b/app/models/project.rb
index 7d7edc45739..769ab217625 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -56,6 +56,7 @@ class Project < ActiveRecord::Base
has_one :flowdock_service, dependent: :destroy
has_one :assembla_service, dependent: :destroy
has_one :gemnasium_service, dependent: :destroy
+ has_one :slack_service, dependent: :destroy
has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id"
has_one :forked_from_project, through: :forked_project_link
# Merge Requests for target project should be removed with it
@@ -304,7 +305,7 @@ class Project < ActiveRecord::Base
end
def available_services_names
- %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push gemnasium)
+ %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push gemnasium slack)
end
def gitlab_ci?
diff --git a/app/models/project_services/slack_message.rb b/app/models/project_services/slack_message.rb
new file mode 100644
index 00000000000..b2b8d6fed7a
--- /dev/null
+++ b/app/models/project_services/slack_message.rb
@@ -0,0 +1,95 @@
+require 'slack-notifier'
+
+class SlackMessage
+ def initialize(params)
+ @after = params.fetch(:after)
+ @before = params.fetch(:before)
+ @commits = params.fetch(:commits, [])
+ @project_name = params.fetch(:project_name)
+ @project_url = params.fetch(:project_url)
+ @ref = params.fetch(:ref).gsub('refs/heads/', '')
+ @username = params.fetch(:user_name)
+ end
+
+ def compose
+ format(message)
+ end
+
+ private
+
+ attr_reader :after
+ attr_reader :before
+ attr_reader :commits
+ attr_reader :project_name
+ attr_reader :project_url
+ attr_reader :ref
+ attr_reader :username
+
+ def message
+ if new_branch?
+ new_branch_message
+ elsif removed_branch?
+ removed_branch_message
+ else
+ push_message << commit_messages
+ end
+ end
+
+ def format(string)
+ Slack::Notifier::LinkFormatter.format(string)
+ end
+
+ def new_branch_message
+ "#{username} pushed new branch #{branch_link} to #{project_link}"
+ end
+
+ def removed_branch_message
+ "#{username} removed branch #{ref} from #{project_link}"
+ end
+
+ def push_message
+ "#{username} pushed to branch #{branch_link} of #{project_link} (#{compare_link})"
+ end
+
+ def commit_messages
+ commits.each_with_object('') do |commit, str|
+ str << compose_commit_message(commit)
+ end
+ end
+
+ def compose_commit_message(commit)
+ id = commit.fetch(:id)[0..5]
+ message = commit.fetch(:message)
+ url = commit.fetch(:url)
+
+ "\n - #{message} ([#{id}](#{url}))"
+ end
+
+ def new_branch?
+ before =~ /000000/
+ end
+
+ def removed_branch?
+ after =~ /000000/
+ end
+
+ def branch_url
+ "#{project_url}/commits/#{ref}"
+ end
+
+ def compare_url
+ "#{project_url}/compare/#{before}...#{after}"
+ end
+
+ def branch_link
+ "[#{ref}](#{branch_url})"
+ end
+
+ def project_link
+ "[#{project_name}](#{project_url})"
+ end
+
+ def compare_link
+ "[Compare changes](#{compare_url})"
+ end
+end
diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb
new file mode 100644
index 00000000000..27648acf6d0
--- /dev/null
+++ b/app/models/project_services/slack_service.rb
@@ -0,0 +1,67 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# token :string(255)
+# project_id :integer not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# active :boolean default(FALSE), not null
+# project_url :string(255)
+# subdomain :string(255)
+# room :string(255)
+# api_key :string(255)
+#
+
+class SlackService < Service
+ attr_accessible :room
+ attr_accessible :subdomain
+
+ validates :room, presence: true, if: :activated?
+ validates :subdomain, presence: true, if: :activated?
+ validates :token, presence: true, if: :activated?
+
+ def title
+ 'Slack'
+ end
+
+ def description
+ 'A team communication tool for the 21st century'
+ end
+
+ def to_param
+ 'slack'
+ end
+
+ def fields
+ [
+ { type: 'text', name: 'subdomain', placeholder: '' },
+ { type: 'text', name: 'token', placeholder: '' },
+ { type: 'text', name: 'room', placeholder: '' },
+ ]
+ end
+
+ def execute(push_data)
+ message = SlackMessage.new(push_data.merge(
+ project_url: project_url,
+ project_name: project_name
+ ))
+
+ notifier = Slack::Notifier.new(subdomain, token)
+ notifier.channel = room
+ notifier.ping(message.compose)
+ end
+
+ private
+
+ def project_name
+ project.name_with_namespace.gsub(/\s/, '')
+ end
+
+ def project_url
+ project.web_url
+ end
+end
diff --git a/features/project/service.feature b/features/project/service.feature
index 46b983e8f9a..a5af065c9e7 100644
--- a/features/project/service.feature
+++ b/features/project/service.feature
@@ -37,6 +37,12 @@ Feature: Project Services
And I fill Assembla settings
Then I should see Assembla service settings saved
+ Scenario: Activate Slack service
+ When I visit project "Shop" services page
+ And I click Slack service link
+ And I fill Slack settings
+ Then I should see Slack service settings saved
+
Scenario: Activate email on push service
When I visit project "Shop" services page
And I click email on push service link
diff --git a/features/steps/project/services.rb b/features/steps/project/services.rb
index 54b3f18e084..0594a08a5e7 100644
--- a/features/steps/project/services.rb
+++ b/features/steps/project/services.rb
@@ -100,4 +100,22 @@ class ProjectServices < Spinach::FeatureSteps
step 'I should see email on push service settings saved' do
find_field('Recipients').value.should == 'qa@company.name'
end
+
+ step 'I click Slack service link' do
+ click_link 'Slack'
+ end
+
+ step 'I fill Slack settings' do
+ check 'Active'
+ fill_in 'Subdomain', with: 'gitlab'
+ fill_in 'Room', with: '#gitlab'
+ fill_in 'Token', with: 'verySecret'
+ click_button 'Save'
+ end
+
+ step 'I should see Slack service settings saved' do
+ find_field('Subdomain').value.should == 'gitlab'
+ find_field('Room').value.should == '#gitlab'
+ find_field('Token').value.should == 'verySecret'
+ end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 6bae5951b7b..839350bafbf 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -47,6 +47,7 @@ describe Project do
it { should have_many(:hooks).dependent(:destroy) }
it { should have_many(:protected_branches).dependent(:destroy) }
it { should have_one(:forked_project_link).dependent(:destroy) }
+ it { should have_one(:slack_service).dependent(:destroy) }
end
describe "Mass assignment" do
diff --git a/spec/models/slack_message_spec.rb b/spec/models/slack_message_spec.rb
new file mode 100644
index 00000000000..b39cd4edf82
--- /dev/null
+++ b/spec/models/slack_message_spec.rb
@@ -0,0 +1,56 @@
+require_relative '../../app/models/project_services/slack_message'
+
+describe SlackMessage do
+ subject { SlackMessage.new(args) }
+
+ let(:args) {
+ {
+ after: 'after',
+ before: 'before',
+ project_name: 'project_name',
+ ref: 'refs/heads/master',
+ user_name: 'user_name',
+ project_url: 'url'
+ }
+ }
+
+ context 'push' do
+ before do
+ args[:commits] = [
+ { message: 'message1', url: 'url1', id: 'abcdefghi' },
+ { message: 'message2', url: 'url2', id: '123456789' },
+ ]
+ end
+
+ it 'returns a message regarding pushes' do
+ subject.compose.should ==
+ 'user_name pushed to branch <url/commits/master|master> of ' <<
+ '<url|project_name> (<url/compare/before...after|Compare changes>)' <<
+ "\n - message1 (<url1|abcdef>)" <<
+ "\n - message2 (<url2|123456>)"
+ end
+ end
+
+ context 'new branch' do
+ before do
+ args[:before] = '000000'
+ end
+
+ it 'returns a message regarding a new branch' do
+ subject.compose.should ==
+ 'user_name pushed new branch <url/commits/master|master> to ' <<
+ '<url|project_name>'
+ end
+ end
+
+ context 'removed branch' do
+ before do
+ args[:after] = '000000'
+ end
+
+ it 'returns a message regarding a removed branch' do
+ subject.compose.should ==
+ 'user_name removed branch master from <url|project_name>'
+ end
+ end
+end
diff --git a/spec/models/slack_service_spec.rb b/spec/models/slack_service_spec.rb
new file mode 100644
index 00000000000..387455cb25e
--- /dev/null
+++ b/spec/models/slack_service_spec.rb
@@ -0,0 +1,69 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# token :string(255)
+# project_id :integer not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# active :boolean default(FALSE), not null
+# project_url :string(255)
+# subdomain :string(255)
+# room :string(255)
+# api_key :string(255)
+#
+
+require 'spec_helper'
+
+describe SlackService do
+ describe "Associations" do
+ it { should belong_to :project }
+ it { should have_one :service_hook }
+ end
+
+ describe "Validations" do
+ context "active" do
+ before do
+ subject.active = true
+ end
+
+ it { should validate_presence_of :room }
+ it { should validate_presence_of :subdomain }
+ it { should validate_presence_of :token }
+ end
+ end
+
+ describe "Execute" do
+ let(:slack) { SlackService.new }
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:sample_data) { GitPushService.new.sample_data(project, user) }
+ let(:subdomain) { 'gitlab' }
+ let(:token) { 'verySecret' }
+ let(:api_url) {
+ "https://#{subdomain}.slack.com/services/hooks/incoming-webhook?token=#{token}"
+ }
+
+ before do
+ slack.stub(
+ project: project,
+ project_id: project.id,
+ room: '#gitlab',
+ service_hook: true,
+ subdomain: subdomain,
+ token: token
+ )
+
+ WebMock.stub_request(:post, api_url)
+ end
+
+ it "should call Slack API" do
+ slack.execute(sample_data)
+
+ WebMock.should have_requested(:post, api_url).once
+ end
+ end
+end