Commit afd747d4d7af9e4f5387927bcaa709a2d3fefbef

Authored by Dmitriy Zaporozhets
2 parents 26c8b316 6ac73f45

Merge branch 'feature/email_on_push_service' of /home/git/repositories/gitlab/gitlabhq

app/mailers/emails/projects.rb
@@ -13,5 +13,15 @@ module Emails @@ -13,5 +13,15 @@ module Emails
13 mail(to: @user.email, 13 mail(to: @user.email,
14 subject: subject("Project was moved")) 14 subject: subject("Project was moved"))
15 end 15 end
  16 +
  17 + def repository_push_email(project_id, recipient, author_id, branch, compare)
  18 + @project = Project.find(project_id)
  19 + @author = User.find(author_id)
  20 + @commits = Commit.decorate(compare.commits)
  21 + @diffs = compare.diffs
  22 + @branch = branch
  23 +
  24 + mail(to: recipient, subject: subject("New push to repository"))
  25 + end
16 end 26 end
17 end 27 end
app/models/assembla_service.rb
@@ -1,45 +0,0 @@ @@ -1,45 +0,0 @@
1 -# == Schema Information  
2 -#  
3 -# Table name: services  
4 -#  
5 -# id :integer not null, primary key  
6 -# type :string(255)  
7 -# title :string(255)  
8 -# token :string(255)  
9 -# project_id :integer not null  
10 -# created_at :datetime not null  
11 -# updated_at :datetime not null  
12 -# active :boolean default(FALSE), not null  
13 -# project_url :string(255)  
14 -# subdomain :string(255)  
15 -# room :string(255)  
16 -#  
17 -  
18 -class AssemblaService < Service  
19 - include HTTParty  
20 -  
21 - validates :token, presence: true, if: :activated?  
22 -  
23 - def title  
24 - 'Assembla'  
25 - end  
26 -  
27 - def description  
28 - 'Project Management Software (Source Commits Endpoint)'  
29 - end  
30 -  
31 - def to_param  
32 - 'assembla'  
33 - end  
34 -  
35 - def fields  
36 - [  
37 - { type: 'text', name: 'token', placeholder: '' }  
38 - ]  
39 - end  
40 -  
41 - def execute(push)  
42 - url = "https://atlas.assembla.com/spaces/ouposp/github_tool?secret_key=#{token}"  
43 - AssemblaService.post(url, body: { payload: push }.to_json, headers: { 'Content-Type' => 'application/json' })  
44 - end  
45 -end  
app/models/campfire_service.rb
@@ -1,78 +0,0 @@ @@ -1,78 +0,0 @@
1 -# == Schema Information  
2 -#  
3 -# Table name: services  
4 -#  
5 -# id :integer not null, primary key  
6 -# type :string(255)  
7 -# title :string(255)  
8 -# token :string(255)  
9 -# project_id :integer not null  
10 -# created_at :datetime not null  
11 -# updated_at :datetime not null  
12 -# active :boolean default(FALSE), not null  
13 -# project_url :string(255)  
14 -# subdomain :string(255)  
15 -# room :string(255)  
16 -#  
17 -  
18 -class CampfireService < Service  
19 - attr_accessible :subdomain, :room  
20 -  
21 - validates :token, presence: true, if: :activated?  
22 -  
23 - def title  
24 - 'Campfire'  
25 - end  
26 -  
27 - def description  
28 - 'Simple web-based real-time group chat'  
29 - end  
30 -  
31 - def to_param  
32 - 'campfire'  
33 - end  
34 -  
35 - def fields  
36 - [  
37 - { type: 'text', name: 'token', placeholder: '' },  
38 - { type: 'text', name: 'subdomain', placeholder: '' },  
39 - { type: 'text', name: 'room', placeholder: '' }  
40 - ]  
41 - end  
42 -  
43 - def execute(push_data)  
44 - room = gate.find_room_by_name(self.room)  
45 - return true unless room  
46 -  
47 - message = build_message(push_data)  
48 -  
49 - room.speak(message)  
50 - end  
51 -  
52 - private  
53 -  
54 - def gate  
55 - @gate ||= Tinder::Campfire.new(subdomain, token: token)  
56 - end  
57 -  
58 - def build_message(push)  
59 - ref = push[:ref].gsub("refs/heads/", "")  
60 - before = push[:before]  
61 - after = push[:after]  
62 -  
63 - message = ""  
64 - message << "[#{project.name_with_namespace}] "  
65 - message << "#{push[:user_name]} "  
66 -  
67 - if before =~ /000000/  
68 - message << "pushed new branch #{ref} \n"  
69 - elsif after =~ /000000/  
70 - message << "removed branch #{ref} \n"  
71 - else  
72 - message << "pushed #{push[:total_commits_count]} commits to #{ref}. "  
73 - message << "#{project.web_url}/compare/#{before}...#{after}"  
74 - end  
75 -  
76 - message  
77 - end  
78 -end  
app/models/flowdock_service.rb
@@ -1,54 +0,0 @@ @@ -1,54 +0,0 @@
1 -# == Schema Information  
2 -#  
3 -# Table name: services  
4 -#  
5 -# id :integer not null, primary key  
6 -# type :string(255)  
7 -# title :string(255)  
8 -# token :string(255)  
9 -# project_id :integer not null  
10 -# created_at :datetime not null  
11 -# updated_at :datetime not null  
12 -# active :boolean default(FALSE), not null  
13 -# project_url :string(255)  
14 -# subdomain :string(255)  
15 -# room :string(255)  
16 -#  
17 -  
18 -require "flowdock-git-hook"  
19 -  
20 -class FlowdockService < Service  
21 - validates :token, presence: true, if: :activated?  
22 -  
23 - def title  
24 - 'Flowdock'  
25 - end  
26 -  
27 - def description  
28 - 'Flowdock is a collaboration web app for technical teams.'  
29 - end  
30 -  
31 - def to_param  
32 - 'flowdock'  
33 - end  
34 -  
35 - def fields  
36 - [  
37 - { type: 'text', name: 'token', placeholder: '' }  
38 - ]  
39 - end  
40 -  
41 - def execute(push_data)  
42 - repo_path = File.join(Gitlab.config.gitlab_shell.repos_path, "#{project.path_with_namespace}.git")  
43 - Flowdock::Git.post(  
44 - push_data[:ref],  
45 - push_data[:before],  
46 - push_data[:after],  
47 - token: token,  
48 - repo: repo_path,  
49 - repo_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}",  
50 - commit_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/commit/%s",  
51 - diff_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/compare/%s...%s",  
52 - )  
53 - end  
54 -end  
app/models/gitlab_ci_service.rb
@@ -1,78 +0,0 @@ @@ -1,78 +0,0 @@
1 -# == Schema Information  
2 -#  
3 -# Table name: services  
4 -#  
5 -# id :integer not null, primary key  
6 -# type :string(255)  
7 -# title :string(255)  
8 -# token :string(255)  
9 -# project_id :integer not null  
10 -# created_at :datetime not null  
11 -# updated_at :datetime not null  
12 -# active :boolean default(FALSE), not null  
13 -# project_url :string(255)  
14 -# subdomain :string(255)  
15 -# room :string(255)  
16 -#  
17 -  
18 -class GitlabCiService < Service  
19 - attr_accessible :project_url  
20 -  
21 - validates :project_url, presence: true, if: :activated?  
22 - validates :token, presence: true, if: :activated?  
23 -  
24 - delegate :execute, to: :service_hook, prefix: nil  
25 -  
26 - after_save :compose_service_hook, if: :activated?  
27 -  
28 - def compose_service_hook  
29 - hook = service_hook || build_service_hook  
30 - hook.url = [project_url, "/build", "?token=#{token}"].join("")  
31 - hook.save  
32 - end  
33 -  
34 - def commit_status_path sha  
35 - project_url + "/builds/#{sha}/status.json?token=#{token}"  
36 - end  
37 -  
38 - def commit_status sha  
39 - response = HTTParty.get(commit_status_path(sha))  
40 -  
41 - if response.code == 200 and response["status"]  
42 - response["status"]  
43 - else  
44 - :error  
45 - end  
46 - end  
47 -  
48 - def build_page sha  
49 - project_url + "/builds/#{sha}"  
50 - end  
51 -  
52 - def builds_path  
53 - project_url + "?ref=" + project.default_branch  
54 - end  
55 -  
56 - def status_img_path  
57 - project_url + "/status.png?ref=" + project.default_branch  
58 - end  
59 -  
60 - def title  
61 - 'GitLab CI'  
62 - end  
63 -  
64 - def description  
65 - 'Continuous integration server from GitLab'  
66 - end  
67 -  
68 - def to_param  
69 - 'gitlab_ci'  
70 - end  
71 -  
72 - def fields  
73 - [  
74 - { type: 'text', name: 'token', placeholder: 'GitLab CI project specific token' },  
75 - { type: 'text', name: 'project_url', placeholder: 'http://ci.gitlabhq.com/projects/3'}  
76 - ]  
77 - end  
78 -end  
app/models/hipchat_service.rb
@@ -1,74 +0,0 @@ @@ -1,74 +0,0 @@
1 -# == Schema Information  
2 -#  
3 -# Table name: services  
4 -#  
5 -# id :integer not null, primary key  
6 -# type :string(255)  
7 -# title :string(255)  
8 -# token :string(255)  
9 -# project_id :integer not null  
10 -# created_at :datetime not null  
11 -# updated_at :datetime not null  
12 -# active :boolean default(FALSE), not null  
13 -# project_url :string(255)  
14 -# subdomain :string(255)  
15 -# room :string(255)  
16 -#  
17 -  
18 -class HipchatService < Service  
19 - attr_accessible :room  
20 -  
21 - validates :token, presence: true, if: :activated?  
22 -  
23 - def title  
24 - 'Hipchat'  
25 - end  
26 -  
27 - def description  
28 - 'Private group chat and IM'  
29 - end  
30 -  
31 - def to_param  
32 - 'hipchat'  
33 - end  
34 -  
35 - def fields  
36 - [  
37 - { type: 'text', name: 'token', placeholder: '' },  
38 - { type: 'text', name: 'room', placeholder: '' }  
39 - ]  
40 - end  
41 -  
42 - def execute(push_data)  
43 - gate[room].send('Gitlab', create_message(push_data))  
44 - end  
45 -  
46 - private  
47 -  
48 - def gate  
49 - @gate ||= HipChat::Client.new(token)  
50 - end  
51 -  
52 - def create_message(push)  
53 - ref = push[:ref].gsub("refs/heads/", "")  
54 - before = push[:before]  
55 - after = push[:after]  
56 -  
57 - message = ""  
58 - message << "#{push[:user_name]} "  
59 - if before =~ /000000/  
60 - message << "pushed new branch <a href=\"#{project.web_url}/commits/#{ref}\">#{ref}</a> to <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/,'')}</a>\n"  
61 - elsif after =~ /000000/  
62 - message << "removed branch #{ref} from <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/,'')}</a> \n"  
63 - else  
64 - message << "#pushed to branch <a href=\"#{project.web_url}/commits/#{ref}\">#{ref}</a> "  
65 - message << "of <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/,'')}</a> "  
66 - message << "(<a href=\"#{project.web_url}/compare/#{before}...#{after}\">Compare changes</a>)"  
67 - for commit in push[:commits] do  
68 - message << "<br /> - #{commit[:message]} (<a href=\"#{commit[:url]}\">#{commit[:id][0..5]}</a>)"  
69 - end  
70 - end  
71 -  
72 - message  
73 - end  
74 -end  
app/models/pivotaltracker_service.rb
@@ -1,62 +0,0 @@ @@ -1,62 +0,0 @@
1 -# == Schema Information  
2 -#  
3 -# Table name: services  
4 -#  
5 -# id :integer not null, primary key  
6 -# type :string(255)  
7 -# title :string(255)  
8 -# token :string(255)  
9 -# project_id :integer not null  
10 -# created_at :datetime not null  
11 -# updated_at :datetime not null  
12 -# active :boolean default(FALSE), not null  
13 -# project_url :string(255)  
14 -# subdomain :string(255)  
15 -# room :string(255)  
16 -#  
17 -  
18 -class PivotaltrackerService < Service  
19 - include HTTParty  
20 -  
21 - validates :token, presence: true, if: :activated?  
22 -  
23 - def title  
24 - 'PivotalTracker'  
25 - end  
26 -  
27 - def description  
28 - 'Project Management Software (Source Commits Endpoint)'  
29 - end  
30 -  
31 - def to_param  
32 - 'pivotaltracker'  
33 - end  
34 -  
35 - def fields  
36 - [  
37 - { type: 'text', name: 'token', placeholder: '' }  
38 - ]  
39 - end  
40 -  
41 - def execute(push)  
42 - url = 'https://www.pivotaltracker.com/services/v5/source_commits'  
43 - push[:commits].each do |commit|  
44 - message = {  
45 - 'source_commit' => {  
46 - 'commit_id' => commit[:id],  
47 - 'author' => commit[:author][:name],  
48 - 'url' => commit[:url],  
49 - 'message' => commit[:message]  
50 - }  
51 - }  
52 - PivotaltrackerService.post(  
53 - url,  
54 - body: message.to_json,  
55 - headers: {  
56 - 'Content-Type' => 'application/json',  
57 - 'X-TrackerToken' => token  
58 - }  
59 - )  
60 - end  
61 - end  
62 -end  
app/models/project.rb
@@ -48,6 +48,7 @@ class Project &lt; ActiveRecord::Base @@ -48,6 +48,7 @@ class Project &lt; ActiveRecord::Base
48 has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id' 48 has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id'
49 has_one :gitlab_ci_service, dependent: :destroy 49 has_one :gitlab_ci_service, dependent: :destroy
50 has_one :campfire_service, dependent: :destroy 50 has_one :campfire_service, dependent: :destroy
  51 + has_one :emails_on_push_service, dependent: :destroy
51 has_one :pivotaltracker_service, dependent: :destroy 52 has_one :pivotaltracker_service, dependent: :destroy
52 has_one :hipchat_service, dependent: :destroy 53 has_one :hipchat_service, dependent: :destroy
53 has_one :flowdock_service, dependent: :destroy 54 has_one :flowdock_service, dependent: :destroy
@@ -237,7 +238,7 @@ class Project &lt; ActiveRecord::Base @@ -237,7 +238,7 @@ class Project &lt; ActiveRecord::Base
237 end 238 end
238 239
239 def available_services_names 240 def available_services_names
240 - %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla) 241 + %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push)
241 end 242 end
242 243
243 def gitlab_ci? 244 def gitlab_ci?
app/models/project_services/assembla_service.rb 0 → 100644
@@ -0,0 +1,45 @@ @@ -0,0 +1,45 @@
  1 +# == Schema Information
  2 +#
  3 +# Table name: services
  4 +#
  5 +# id :integer not null, primary key
  6 +# type :string(255)
  7 +# title :string(255)
  8 +# token :string(255)
  9 +# project_id :integer not null
  10 +# created_at :datetime not null
  11 +# updated_at :datetime not null
  12 +# active :boolean default(FALSE), not null
  13 +# project_url :string(255)
  14 +# subdomain :string(255)
  15 +# room :string(255)
  16 +#
  17 +
  18 +class AssemblaService < Service
  19 + include HTTParty
  20 +
  21 + validates :token, presence: true, if: :activated?
  22 +
  23 + def title
  24 + 'Assembla'
  25 + end
  26 +
  27 + def description
  28 + 'Project Management Software (Source Commits Endpoint)'
  29 + end
  30 +
  31 + def to_param
  32 + 'assembla'
  33 + end
  34 +
  35 + def fields
  36 + [
  37 + { type: 'text', name: 'token', placeholder: '' }
  38 + ]
  39 + end
  40 +
  41 + def execute(push)
  42 + url = "https://atlas.assembla.com/spaces/ouposp/github_tool?secret_key=#{token}"
  43 + AssemblaService.post(url, body: { payload: push }.to_json, headers: { 'Content-Type' => 'application/json' })
  44 + end
  45 +end
app/models/project_services/campfire_service.rb 0 → 100644
@@ -0,0 +1,78 @@ @@ -0,0 +1,78 @@
  1 +# == Schema Information
  2 +#
  3 +# Table name: services
  4 +#
  5 +# id :integer not null, primary key
  6 +# type :string(255)
  7 +# title :string(255)
  8 +# token :string(255)
  9 +# project_id :integer not null
  10 +# created_at :datetime not null
  11 +# updated_at :datetime not null
  12 +# active :boolean default(FALSE), not null
  13 +# project_url :string(255)
  14 +# subdomain :string(255)
  15 +# room :string(255)
  16 +#
  17 +
  18 +class CampfireService < Service
  19 + attr_accessible :subdomain, :room
  20 +
  21 + validates :token, presence: true, if: :activated?
  22 +
  23 + def title
  24 + 'Campfire'
  25 + end
  26 +
  27 + def description
  28 + 'Simple web-based real-time group chat'
  29 + end
  30 +
  31 + def to_param
  32 + 'campfire'
  33 + end
  34 +
  35 + def fields
  36 + [
  37 + { type: 'text', name: 'token', placeholder: '' },
  38 + { type: 'text', name: 'subdomain', placeholder: '' },
  39 + { type: 'text', name: 'room', placeholder: '' }
  40 + ]
  41 + end
  42 +
  43 + def execute(push_data)
  44 + room = gate.find_room_by_name(self.room)
  45 + return true unless room
  46 +
  47 + message = build_message(push_data)
  48 +
  49 + room.speak(message)
  50 + end
  51 +
  52 + private
  53 +
  54 + def gate
  55 + @gate ||= Tinder::Campfire.new(subdomain, token: token)
  56 + end
  57 +
  58 + def build_message(push)
  59 + ref = push[:ref].gsub("refs/heads/", "")
  60 + before = push[:before]
  61 + after = push[:after]
  62 +
  63 + message = ""
  64 + message << "[#{project.name_with_namespace}] "
  65 + message << "#{push[:user_name]} "
  66 +
  67 + if before =~ /000000/
  68 + message << "pushed new branch #{ref} \n"
  69 + elsif after =~ /000000/
  70 + message << "removed branch #{ref} \n"
  71 + else
  72 + message << "pushed #{push[:total_commits_count]} commits to #{ref}. "
  73 + message << "#{project.web_url}/compare/#{before}...#{after}"
  74 + end
  75 +
  76 + message
  77 + end
  78 +end
app/models/project_services/emails_on_push_service.rb 0 → 100644
@@ -0,0 +1,44 @@ @@ -0,0 +1,44 @@
  1 +# == Schema Information
  2 +#
  3 +# Table name: services
  4 +#
  5 +# id :integer not null, primary key
  6 +# type :string(255)
  7 +# title :string(255)
  8 +# token :string(255)
  9 +# project_id :integer not null
  10 +# created_at :datetime not null
  11 +# updated_at :datetime not null
  12 +# active :boolean default(FALSE), not null
  13 +# project_url :string(255)
  14 +# subdomain :string(255)
  15 +# room :string(255)
  16 +#
  17 +
  18 +class EmailsOnPushService < Service
  19 + attr_accessible :recipients
  20 +
  21 + validates :recipients, presence: true, if: :activated?
  22 +
  23 + def title
  24 + 'Emails on push'
  25 + end
  26 +
  27 + def description
  28 + 'Email the commits and diff of each push to a list of recipients.'
  29 + end
  30 +
  31 + def to_param
  32 + 'emails_on_push'
  33 + end
  34 +
  35 + def execute(push_data)
  36 + EmailsOnPushWorker.perform_async(project_id, recipients, push_data)
  37 + end
  38 +
  39 + def fields
  40 + [
  41 + { type: 'textarea', name: 'recipients', placeholder: 'Emails separated by whitespace' },
  42 + ]
  43 + end
  44 +end
app/models/project_services/flowdock_service.rb 0 → 100644
@@ -0,0 +1,54 @@ @@ -0,0 +1,54 @@
  1 +# == Schema Information
  2 +#
  3 +# Table name: services
  4 +#
  5 +# id :integer not null, primary key
  6 +# type :string(255)
  7 +# title :string(255)
  8 +# token :string(255)
  9 +# project_id :integer not null
  10 +# created_at :datetime not null
  11 +# updated_at :datetime not null
  12 +# active :boolean default(FALSE), not null
  13 +# project_url :string(255)
  14 +# subdomain :string(255)
  15 +# room :string(255)
  16 +#
  17 +
  18 +require "flowdock-git-hook"
  19 +
  20 +class FlowdockService < Service
  21 + validates :token, presence: true, if: :activated?
  22 +
  23 + def title
  24 + 'Flowdock'
  25 + end
  26 +
  27 + def description
  28 + 'Flowdock is a collaboration web app for technical teams.'
  29 + end
  30 +
  31 + def to_param
  32 + 'flowdock'
  33 + end
  34 +
  35 + def fields
  36 + [
  37 + { type: 'text', name: 'token', placeholder: '' }
  38 + ]
  39 + end
  40 +
  41 + def execute(push_data)
  42 + repo_path = File.join(Gitlab.config.gitlab_shell.repos_path, "#{project.path_with_namespace}.git")
  43 + Flowdock::Git.post(
  44 + push_data[:ref],
  45 + push_data[:before],
  46 + push_data[:after],
  47 + token: token,
  48 + repo: repo_path,
  49 + repo_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}",
  50 + commit_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/commit/%s",
  51 + diff_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/compare/%s...%s",
  52 + )
  53 + end
  54 +end
app/models/project_services/gitlab_ci_service.rb 0 → 100644
@@ -0,0 +1,78 @@ @@ -0,0 +1,78 @@
  1 +# == Schema Information
  2 +#
  3 +# Table name: services
  4 +#
  5 +# id :integer not null, primary key
  6 +# type :string(255)
  7 +# title :string(255)
  8 +# token :string(255)
  9 +# project_id :integer not null
  10 +# created_at :datetime not null
  11 +# updated_at :datetime not null
  12 +# active :boolean default(FALSE), not null
  13 +# project_url :string(255)
  14 +# subdomain :string(255)
  15 +# room :string(255)
  16 +#
  17 +
  18 +class GitlabCiService < Service
  19 + attr_accessible :project_url
  20 +
  21 + validates :project_url, presence: true, if: :activated?
  22 + validates :token, presence: true, if: :activated?
  23 +
  24 + delegate :execute, to: :service_hook, prefix: nil
  25 +
  26 + after_save :compose_service_hook, if: :activated?
  27 +
  28 + def compose_service_hook
  29 + hook = service_hook || build_service_hook
  30 + hook.url = [project_url, "/build", "?token=#{token}"].join("")
  31 + hook.save
  32 + end
  33 +
  34 + def commit_status_path sha
  35 + project_url + "/builds/#{sha}/status.json?token=#{token}"
  36 + end
  37 +
  38 + def commit_status sha
  39 + response = HTTParty.get(commit_status_path(sha))
  40 +
  41 + if response.code == 200 and response["status"]
  42 + response["status"]
  43 + else
  44 + :error
  45 + end
  46 + end
  47 +
  48 + def build_page sha
  49 + project_url + "/builds/#{sha}"
  50 + end
  51 +
  52 + def builds_path
  53 + project_url + "?ref=" + project.default_branch
  54 + end
  55 +
  56 + def status_img_path
  57 + project_url + "/status.png?ref=" + project.default_branch
  58 + end
  59 +
  60 + def title
  61 + 'GitLab CI'
  62 + end
  63 +
  64 + def description
  65 + 'Continuous integration server from GitLab'
  66 + end
  67 +
  68 + def to_param
  69 + 'gitlab_ci'
  70 + end
  71 +
  72 + def fields
  73 + [
  74 + { type: 'text', name: 'token', placeholder: 'GitLab CI project specific token' },
  75 + { type: 'text', name: 'project_url', placeholder: 'http://ci.gitlabhq.com/projects/3'}
  76 + ]
  77 + end
  78 +end
app/models/project_services/hipchat_service.rb 0 → 100644
@@ -0,0 +1,74 @@ @@ -0,0 +1,74 @@
  1 +# == Schema Information
  2 +#
  3 +# Table name: services
  4 +#
  5 +# id :integer not null, primary key
  6 +# type :string(255)
  7 +# title :string(255)
  8 +# token :string(255)
  9 +# project_id :integer not null
  10 +# created_at :datetime not null
  11 +# updated_at :datetime not null
  12 +# active :boolean default(FALSE), not null
  13 +# project_url :string(255)
  14 +# subdomain :string(255)
  15 +# room :string(255)
  16 +#
  17 +
  18 +class HipchatService < Service
  19 + attr_accessible :room
  20 +
  21 + validates :token, presence: true, if: :activated?
  22 +
  23 + def title
  24 + 'Hipchat'
  25 + end
  26 +
  27 + def description
  28 + 'Private group chat and IM'
  29 + end
  30 +
  31 + def to_param
  32 + 'hipchat'
  33 + end
  34 +
  35 + def fields
  36 + [
  37 + { type: 'text', name: 'token', placeholder: '' },
  38 + { type: 'text', name: 'room', placeholder: '' }
  39 + ]
  40 + end
  41 +
  42 + def execute(push_data)
  43 + gate[room].send('Gitlab', create_message(push_data))
  44 + end
  45 +
  46 + private
  47 +
  48 + def gate
  49 + @gate ||= HipChat::Client.new(token)
  50 + end
  51 +
  52 + def create_message(push)
  53 + ref = push[:ref].gsub("refs/heads/", "")
  54 + before = push[:before]
  55 + after = push[:after]
  56 +
  57 + message = ""
  58 + message << "#{push[:user_name]} "
  59 + if before =~ /000000/
  60 + message << "pushed new branch <a href=\"#{project.web_url}/commits/#{ref}\">#{ref}</a> to <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/,'')}</a>\n"
  61 + elsif after =~ /000000/
  62 + message << "removed branch #{ref} from <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/,'')}</a> \n"
  63 + else
  64 + message << "#pushed to branch <a href=\"#{project.web_url}/commits/#{ref}\">#{ref}</a> "
  65 + message << "of <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/,'')}</a> "
  66 + message << "(<a href=\"#{project.web_url}/compare/#{before}...#{after}\">Compare changes</a>)"
  67 + for commit in push[:commits] do
  68 + message << "<br /> - #{commit[:message]} (<a href=\"#{commit[:url]}\">#{commit[:id][0..5]}</a>)"
  69 + end
  70 + end
  71 +
  72 + message
  73 + end
  74 +end
app/models/project_services/pivotaltracker_service.rb 0 → 100644
@@ -0,0 +1,62 @@ @@ -0,0 +1,62 @@
  1 +# == Schema Information
  2 +#
  3 +# Table name: services
  4 +#
  5 +# id :integer not null, primary key
  6 +# type :string(255)
  7 +# title :string(255)
  8 +# token :string(255)
  9 +# project_id :integer not null
  10 +# created_at :datetime not null
  11 +# updated_at :datetime not null
  12 +# active :boolean default(FALSE), not null
  13 +# project_url :string(255)
  14 +# subdomain :string(255)
  15 +# room :string(255)
  16 +#
  17 +
  18 +class PivotaltrackerService < Service
  19 + include HTTParty
  20 +
  21 + validates :token, presence: true, if: :activated?
  22 +
  23 + def title
  24 + 'PivotalTracker'
  25 + end
  26 +
  27 + def description
  28 + 'Project Management Software (Source Commits Endpoint)'
  29 + end
  30 +
  31 + def to_param
  32 + 'pivotaltracker'
  33 + end
  34 +
  35 + def fields
  36 + [
  37 + { type: 'text', name: 'token', placeholder: '' }
  38 + ]
  39 + end
  40 +
  41 + def execute(push)
  42 + url = 'https://www.pivotaltracker.com/services/v5/source_commits'
  43 + push[:commits].each do |commit|
  44 + message = {
  45 + 'source_commit' => {
  46 + 'commit_id' => commit[:id],
  47 + 'author' => commit[:author][:name],
  48 + 'url' => commit[:url],
  49 + 'message' => commit[:message]
  50 + }
  51 + }
  52 + PivotaltrackerService.post(
  53 + url,
  54 + body: message.to_json,
  55 + headers: {
  56 + 'Content-Type' => 'application/json',
  57 + 'X-TrackerToken' => token
  58 + }
  59 + )
  60 + end
  61 + end
  62 +end
app/views/notify/repository_push_email.html.haml 0 → 100644
@@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
  1 +%h3 #{@author.name} pushed to #{@branch} at #{@project.name_with_namespace}
  2 +
  3 +%h4 Commits:
  4 +
  5 +%ul
  6 + - @commits.each do |commit|
  7 + %li
  8 + #{commit.short_id} - #{commit.title}
  9 +
  10 +%h4 Diff:
  11 +- @diffs.each do |diff|
  12 + %li
  13 + %strong
  14 + - if diff.old_path == diff.new_path
  15 + = diff.new_path
  16 + - elsif diff.new_path && diff.old_path
  17 + #{diff.old_path} &rarr; #{diff.new_path}
  18 + - else
  19 + = diff.new_path || diff.old_path
  20 + %hr
  21 + %pre
  22 + = diff.diff
  23 + %br
app/views/notify/repository_push_email.text.haml 0 → 100644
@@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
  1 +#{@author.name} pushed to #{@branch} at #{@project.name_with_namespace}
  2 +
  3 +\
  4 +Commits:
  5 +- @commits.each do |commit|
  6 + #{commit.short_id} - #{truncate(commit.title, length: 40)}
  7 +\
  8 +\
  9 +Diff:
  10 +- @diffs.each do |diff|
  11 + \
  12 + \=====================================
  13 + - if diff.old_path == diff.new_path
  14 + = diff.new_path
  15 + - elsif diff.new_path && diff.old_path
  16 + #{diff.old_path} &rarr; #{diff.new_path}
  17 + - else
  18 + = diff.new_path || diff.old_path
  19 + \=====================================
  20 + = diff.diff
app/views/projects/services/_form.html.haml
@@ -33,6 +33,8 @@ @@ -33,6 +33,8 @@
33 .controls 33 .controls
34 - if type == 'text' 34 - if type == 'text'
35 = f.text_field name, class: "input-xlarge", placeholder: placeholder 35 = f.text_field name, class: "input-xlarge", placeholder: placeholder
  36 + - elsif type == 'textarea'
  37 + = f.text_area name, rows: 5, class: "input-xxlarge", placeholder: placeholder
36 - elsif type == 'checkbox' 38 - elsif type == 'checkbox'
37 = f.check_box name 39 = f.check_box name
38 40
app/views/projects/services/index.html.haml
@@ -3,7 +3,7 @@ @@ -3,7 +3,7 @@
3 %hr 3 %hr
4 4
5 %ul.bordered-list 5 %ul.bordered-list
6 - - @services.each do |service| 6 + - @services.sort_by(&:title).each do |service|
7 %li 7 %li
8 %h4 8 %h4
9 = link_to edit_project_service_path(@project, service.to_param) do 9 = link_to edit_project_service_path(@project, service.to_param) do
app/workers/emails_on_push_worker.rb 0 → 100644
@@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@
  1 +class EmailsOnPushWorker
  2 + include Sidekiq::Worker
  3 +
  4 + def perform(project_id, recipients, push_data)
  5 + project = Project.find(project_id)
  6 + before_sha = push_data["before"]
  7 + after_sha = push_data["after"]
  8 + branch = push_data["ref"]
  9 + author_id = push_data["user_id"]
  10 +
  11 + if before_sha =~ /^000000/ || after_sha =~ /^000000/
  12 + # skip if new branch was pushed or branch was removed
  13 + return true
  14 + end
  15 +
  16 + compare = Gitlab::Git::Compare.new(project.repository.raw_repository, before_sha, after_sha)
  17 +
  18 + # Do not send emails if git compare failed
  19 + return false unless compare && compare.commits.present?
  20 +
  21 + recipients.split(" ").each do |recipient|
  22 + Notify.delay.repository_push_email(project_id, recipient, author_id, branch, compare)
  23 + end
  24 + end
  25 +end
config/application.rb
@@ -12,7 +12,7 @@ module Gitlab @@ -12,7 +12,7 @@ module Gitlab
12 # -- all .rb files in that directory are automatically loaded. 12 # -- all .rb files in that directory are automatically loaded.
13 13
14 # Custom directories with classes and modules you want to be autoloadable. 14 # Custom directories with classes and modules you want to be autoloadable.
15 - config.autoload_paths += %W(#{config.root}/lib #{config.root}/app/models/concerns) 15 + config.autoload_paths += %W(#{config.root}/lib #{config.root}/app/models/concerns #{config.root}/app/models/project_services)
16 16
17 # Only load the plugins named here, in the order given (default is alphabetical). 17 # Only load the plugins named here, in the order given (default is alphabetical).
18 # :all can be used as a placeholder for all plugins not explicitly named. 18 # :all can be used as a placeholder for all plugins not explicitly named.
db/migrate/20131217102743_add_recipients_to_service.rb 0 → 100644
@@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
  1 +class AddRecipientsToService < ActiveRecord::Migration
  2 + def change
  3 + add_column :services, :recipients, :text
  4 + end
  5 +end
@@ -11,7 +11,7 @@ @@ -11,7 +11,7 @@
11 # 11 #
12 # It's strongly recommended that you check this file into your version control system. 12 # It's strongly recommended that you check this file into your version control system.
13 13
14 -ActiveRecord::Schema.define(version: 20131214224427) do 14 +ActiveRecord::Schema.define(version: 20131217102743) do
15 15
16 create_table "broadcast_messages", force: true do |t| 16 create_table "broadcast_messages", force: true do |t|
17 t.text "message", null: false 17 t.text "message", null: false
@@ -219,6 +219,7 @@ ActiveRecord::Schema.define(version: 20131214224427) do @@ -219,6 +219,7 @@ ActiveRecord::Schema.define(version: 20131214224427) do
219 t.string "project_url" 219 t.string "project_url"
220 t.string "subdomain" 220 t.string "subdomain"
221 t.string "room" 221 t.string "room"
  222 + t.text "recipients"
222 end 223 end
223 224
224 add_index "services", ["project_id"], name: "index_services_on_project_id", using: :btree 225 add_index "services", ["project_id"], name: "index_services_on_project_id", using: :btree
features/project/service.feature
@@ -35,4 +35,10 @@ Feature: Project Services @@ -35,4 +35,10 @@ Feature: Project Services
35 When I visit project "Shop" services page 35 When I visit project "Shop" services page
36 And I click Assembla service link 36 And I click Assembla service link
37 And I fill Assembla settings 37 And I fill Assembla settings
38 - Then I should see Assembla service settings saved  
39 \ No newline at end of file 38 \ No newline at end of file
  39 + Then I should see Assembla service settings saved
  40 +
  41 + Scenario: Activate email on push service
  42 + When I visit project "Shop" services page
  43 + And I click email on push service link
  44 + And I fill email on push settings
  45 + Then I should see email on push service settings saved
features/steps/project/project_services.rb
@@ -3,11 +3,11 @@ class ProjectServices &lt; Spinach::FeatureSteps @@ -3,11 +3,11 @@ class ProjectServices &lt; Spinach::FeatureSteps
3 include SharedProject 3 include SharedProject
4 include SharedPaths 4 include SharedPaths
5 5
6 - When 'I visit project "Shop" services page' do 6 + step 'I visit project "Shop" services page' do
7 visit project_services_path(@project) 7 visit project_services_path(@project)
8 end 8 end
9 9
10 - Then 'I should see list of available services' do 10 + step 'I should see list of available services' do
11 page.should have_content 'Services' 11 page.should have_content 'Services'
12 page.should have_content 'Campfire' 12 page.should have_content 'Campfire'
13 page.should have_content 'Hipchat' 13 page.should have_content 'Hipchat'
@@ -15,76 +15,89 @@ class ProjectServices &lt; Spinach::FeatureSteps @@ -15,76 +15,89 @@ class ProjectServices &lt; Spinach::FeatureSteps
15 page.should have_content 'Assembla' 15 page.should have_content 'Assembla'
16 end 16 end
17 17
18 - And 'I click gitlab-ci service link' do 18 + step 'I click gitlab-ci service link' do
19 click_link 'GitLab CI' 19 click_link 'GitLab CI'
20 end 20 end
21 21
22 - And 'I fill gitlab-ci settings' do 22 + step 'I fill gitlab-ci settings' do
23 check 'Active' 23 check 'Active'
24 fill_in 'Project url', with: 'http://ci.gitlab.org/projects/3' 24 fill_in 'Project url', with: 'http://ci.gitlab.org/projects/3'
25 fill_in 'Token', with: 'verySecret' 25 fill_in 'Token', with: 'verySecret'
26 click_button 'Save' 26 click_button 'Save'
27 end 27 end
28 28
29 - Then 'I should see service settings saved' do 29 + step 'I should see service settings saved' do
30 find_field('Project url').value.should == 'http://ci.gitlab.org/projects/3' 30 find_field('Project url').value.should == 'http://ci.gitlab.org/projects/3'
31 end 31 end
32 32
33 - And 'I click hipchat service link' do 33 + step 'I click hipchat service link' do
34 click_link 'Hipchat' 34 click_link 'Hipchat'
35 end 35 end
36 36
37 - And 'I fill hipchat settings' do 37 + step 'I fill hipchat settings' do
38 check 'Active' 38 check 'Active'
39 fill_in 'Room', with: 'gitlab' 39 fill_in 'Room', with: 'gitlab'
40 fill_in 'Token', with: 'verySecret' 40 fill_in 'Token', with: 'verySecret'
41 click_button 'Save' 41 click_button 'Save'
42 end 42 end
43 43
44 - Then 'I should see hipchat service settings saved' do 44 + step 'I should see hipchat service settings saved' do
45 find_field('Room').value.should == 'gitlab' 45 find_field('Room').value.should == 'gitlab'
46 end 46 end
47 47
48 48
49 - And 'I click pivotaltracker service link' do 49 + step 'I click pivotaltracker service link' do
50 click_link 'PivotalTracker' 50 click_link 'PivotalTracker'
51 end 51 end
52 52
53 - And 'I fill pivotaltracker settings' do 53 + step 'I fill pivotaltracker settings' do
54 check 'Active' 54 check 'Active'
55 fill_in 'Token', with: 'verySecret' 55 fill_in 'Token', with: 'verySecret'
56 click_button 'Save' 56 click_button 'Save'
57 end 57 end
58 58
59 - Then 'I should see pivotaltracker service settings saved' do 59 + step 'I should see pivotaltracker service settings saved' do
60 find_field('Token').value.should == 'verySecret' 60 find_field('Token').value.should == 'verySecret'
61 end 61 end
62 62
63 - And 'I click Flowdock service link' do 63 + step 'I click Flowdock service link' do
64 click_link 'Flowdock' 64 click_link 'Flowdock'
65 end 65 end
66 66
67 - And 'I fill Flowdock settings' do 67 + step 'I fill Flowdock settings' do
68 check 'Active' 68 check 'Active'
69 fill_in 'Token', with: 'verySecret' 69 fill_in 'Token', with: 'verySecret'
70 click_button 'Save' 70 click_button 'Save'
71 end 71 end
72 72
73 - Then 'I should see Flowdock service settings saved' do 73 + step 'I should see Flowdock service settings saved' do
74 find_field('Token').value.should == 'verySecret' 74 find_field('Token').value.should == 'verySecret'
75 end 75 end
76 76
77 - And 'I click Assembla service link' do 77 + step 'I click Assembla service link' do
78 click_link 'Assembla' 78 click_link 'Assembla'
79 end 79 end
80 80
81 - And 'I fill Assembla settings' do 81 + step 'I fill Assembla settings' do
82 check 'Active' 82 check 'Active'
83 fill_in 'Token', with: 'verySecret' 83 fill_in 'Token', with: 'verySecret'
84 click_button 'Save' 84 click_button 'Save'
85 end 85 end
86 86
87 - Then 'I should see Assembla service settings saved' do 87 + step 'I should see Assembla service settings saved' do
88 find_field('Token').value.should == 'verySecret' 88 find_field('Token').value.should == 'verySecret'
89 end 89 end
  90 +
  91 + step 'I click email on push service link' do
  92 + click_link 'Emails on push'
  93 + end
  94 +
  95 + step 'I fill email on push settings' do
  96 + fill_in 'Recipients', with: 'qa@company.name'
  97 + click_button 'Save'
  98 + end
  99 +
  100 + step 'I should see email on push service settings saved' do
  101 + find_field('Recipients').value.should == 'qa@company.name'
  102 + end
90 end 103 end
spec/mailers/notify_spec.rb
@@ -391,4 +391,28 @@ describe Notify do @@ -391,4 +391,28 @@ describe Notify do
391 should have_body_text /#{example_site_path}/ 391 should have_body_text /#{example_site_path}/
392 end 392 end
393 end 393 end
  394 +
  395 + describe 'email on push' do
  396 + let(:example_site_path) { root_path }
  397 + let(:user) { create(:user) }
  398 + let(:compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, 'cd5c4bac', 'b1e6a9db') }
  399 +
  400 + subject { Notify.repository_push_email(project.id, 'devs@company.name', user.id, 'master', compare) }
  401 +
  402 + it 'is sent to recipient' do
  403 + should deliver_to 'devs@company.name'
  404 + end
  405 +
  406 + it 'has the correct subject' do
  407 + should have_subject /New push to repository/
  408 + end
  409 +
  410 + it 'includes commits list' do
  411 + should have_body_text /tree css fixes/
  412 + end
  413 +
  414 + it 'includes diffs' do
  415 + should have_body_text /Checkout wiki pages for installation information/
  416 + end
  417 + end
394 end 418 end