Commit 4f067ae931bef908312bbf7162abb3b6fdb85f8d

Authored by Dmitriy Zaporozhets
2 parents d4f94a5b 21f4e5d3

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

CHANGELOG
... ... @@ -7,6 +7,7 @@ v 6.4.0
7 7 - Side-by-side diff view (Steven Thonus)
8 8 - Internal projects (Jason Hollingsworth)
9 9 - Allow removal of avatar (Drew Blessing)
  10 + - Project web hooks now support issues and merge request events
10 11  
11 12 v 6.3.0
12 13 - API for adding gitlab-ci service
... ...
app/assets/stylesheets/gitlab_bootstrap/forms.scss
... ... @@ -13,6 +13,13 @@ form {
13 13 margin-top: 1px !important;
14 14 }
15 15 }
  16 +
  17 + &.list-label {
  18 + float: none;
  19 + padding: 0 !important;
  20 + margin: 0;
  21 + text-align: left;
  22 + }
16 23 }
17 24 }
18 25  
... ...
app/helpers/application_helper.rb
... ... @@ -207,4 +207,12 @@ module ApplicationHelper
207 207 def broadcast_message
208 208 BroadcastMessage.current
209 209 end
  210 +
  211 + def highlight_js(&block)
  212 + string = capture(&block)
  213 +
  214 + content_tag :div, class: user_color_scheme_class do
  215 + Pygments::Lexer[:js].highlight(string).html_safe
  216 + end
  217 + end
210 218 end
... ...
app/models/concerns/issuable.rb
... ... @@ -111,4 +111,11 @@ module Issuable
111 111 end
112 112 users.concat(mentions.reduce([], :|)).uniq
113 113 end
  114 +
  115 + def to_hook_data
  116 + {
  117 + object_kind: self.class.name.underscore,
  118 + object_attributes: self.attributes
  119 + }
  120 + end
114 121 end
... ...
app/models/project.rb
... ... @@ -298,8 +298,10 @@ class Project < ActiveRecord::Base
298 298 ProjectTransferService.new.transfer(self, new_namespace)
299 299 end
300 300  
301   - def execute_hooks(data)
302   - hooks.each { |hook| hook.async_execute(data) }
  301 + def execute_hooks(data, hooks_scope = :push_hooks)
  302 + hooks.send(hooks_scope).each do |hook|
  303 + hook.async_execute(data)
  304 + end
303 305 end
304 306  
305 307 def execute_services(data)
... ...
app/models/project_hook.rb
... ... @@ -2,15 +2,24 @@
2 2 #
3 3 # Table name: web_hooks
4 4 #
5   -# id :integer not null, primary key
6   -# url :string(255)
7   -# project_id :integer
8   -# created_at :datetime not null
9   -# updated_at :datetime not null
10   -# type :string(255) default("ProjectHook")
11   -# service_id :integer
  5 +# id :integer not null, primary key
  6 +# url :string(255)
  7 +# project_id :integer
  8 +# created_at :datetime not null
  9 +# updated_at :datetime not null
  10 +# type :string(255) default("ProjectHook")
  11 +# service_id :integer
  12 +# push_events :boolean default(TRUE), not null
  13 +# issues_events :boolean default(FALSE), not null
  14 +# merge_requests_events :boolean default(FALSE), not null
12 15 #
13 16  
14 17 class ProjectHook < WebHook
15 18 belongs_to :project
  19 +
  20 + attr_accessible :push_events, :issues_events, :merge_requests_events
  21 +
  22 + scope :push_hooks, -> { where(push_events: true) }
  23 + scope :issue_hooks, -> { where(issues_events: true) }
  24 + scope :merge_request_hooks, -> { where(merge_requests_events: true) }
16 25 end
... ...
app/models/service_hook.rb
... ... @@ -2,13 +2,16 @@
2 2 #
3 3 # Table name: web_hooks
4 4 #
5   -# id :integer not null, primary key
6   -# url :string(255)
7   -# project_id :integer
8   -# created_at :datetime not null
9   -# updated_at :datetime not null
10   -# type :string(255) default("ProjectHook")
11   -# service_id :integer
  5 +# id :integer not null, primary key
  6 +# url :string(255)
  7 +# project_id :integer
  8 +# created_at :datetime not null
  9 +# updated_at :datetime not null
  10 +# type :string(255) default("ProjectHook")
  11 +# service_id :integer
  12 +# push_events :boolean default(TRUE), not null
  13 +# issues_events :boolean default(FALSE), not null
  14 +# merge_requests_events :boolean default(FALSE), not null
12 15 #
13 16  
14 17 class ServiceHook < WebHook
... ...
app/models/system_hook.rb
... ... @@ -2,13 +2,16 @@
2 2 #
3 3 # Table name: web_hooks
4 4 #
5   -# id :integer not null, primary key
6   -# url :string(255)
7   -# project_id :integer
8   -# created_at :datetime not null
9   -# updated_at :datetime not null
10   -# type :string(255) default("ProjectHook")
11   -# service_id :integer
  5 +# id :integer not null, primary key
  6 +# url :string(255)
  7 +# project_id :integer
  8 +# created_at :datetime not null
  9 +# updated_at :datetime not null
  10 +# type :string(255) default("ProjectHook")
  11 +# service_id :integer
  12 +# push_events :boolean default(TRUE), not null
  13 +# issues_events :boolean default(FALSE), not null
  14 +# merge_requests_events :boolean default(FALSE), not null
12 15 #
13 16  
14 17 class SystemHook < WebHook
... ...
app/models/web_hook.rb
... ... @@ -2,13 +2,16 @@
2 2 #
3 3 # Table name: web_hooks
4 4 #
5   -# id :integer not null, primary key
6   -# url :string(255)
7   -# project_id :integer
8   -# created_at :datetime not null
9   -# updated_at :datetime not null
10   -# type :string(255) default("ProjectHook")
11   -# service_id :integer
  5 +# id :integer not null, primary key
  6 +# url :string(255)
  7 +# project_id :integer
  8 +# created_at :datetime not null
  9 +# updated_at :datetime not null
  10 +# type :string(255) default("ProjectHook")
  11 +# service_id :integer
  12 +# push_events :boolean default(TRUE), not null
  13 +# issues_events :boolean default(FALSE), not null
  14 +# merge_requests_events :boolean default(FALSE), not null
12 15 #
13 16  
14 17 class WebHook < ActiveRecord::Base
... ...
app/observers/issue_observer.rb
1 1 class IssueObserver < BaseObserver
2 2 def after_create(issue)
3 3 notification.new_issue(issue, current_user)
4   -
5 4 issue.create_cross_references!(issue.project, current_user)
  5 + execute_hooks(issue)
6 6 end
7 7  
8 8 def after_close(issue, transition)
9 9 notification.close_issue(issue, current_user)
10   -
11 10 create_note(issue)
  11 + execute_hooks(issue)
12 12 end
13 13  
14 14 def after_reopen(issue, transition)
... ... @@ -29,4 +29,8 @@ class IssueObserver &lt; BaseObserver
29 29 def create_note(issue)
30 30 Note.create_status_change_note(issue, issue.project, current_user, issue.state, current_commit)
31 31 end
  32 +
  33 + def execute_hooks(issue)
  34 + issue.project.execute_hooks(issue.to_hook_data, :issue_hooks)
  35 + end
32 36 end
... ...
app/observers/merge_request_observer.rb
... ... @@ -7,15 +7,15 @@ class MergeRequestObserver &lt; ActivityObserver
7 7 end
8 8  
9 9 notification.new_merge_request(merge_request, current_user)
10   -
11 10 merge_request.create_cross_references!(merge_request.project, current_user)
  11 + execute_hooks(merge_request)
12 12 end
13 13  
14 14 def after_close(merge_request, transition)
15 15 create_event(merge_request, Event::CLOSED)
16   - Note.create_status_change_note(merge_request, merge_request.target_project, current_user, merge_request.state, nil)
17   -
18 16 notification.close_mr(merge_request, current_user)
  17 + create_note(merge_request)
  18 + execute_hooks(merge_request)
19 19 end
20 20  
21 21 def after_merge(merge_request, transition)
... ... @@ -31,11 +31,13 @@ class MergeRequestObserver &lt; ActivityObserver
31 31 action: Event::MERGED,
32 32 author_id: merge_request.author_id_of_changes
33 33 )
  34 +
  35 + execute_hooks(merge_request)
34 36 end
35 37  
36 38 def after_reopen(merge_request, transition)
37 39 create_event(merge_request, Event::REOPENED)
38   - Note.create_status_change_note(merge_request, merge_request.target_project, current_user, merge_request.state, nil)
  40 + create_note(merge_request)
39 41 end
40 42  
41 43 def after_update(merge_request)
... ... @@ -53,4 +55,17 @@ class MergeRequestObserver &lt; ActivityObserver
53 55 author_id: current_user.id
54 56 )
55 57 end
  58 +
  59 + private
  60 +
  61 + # Create merge request note with service comment like 'Status changed to closed'
  62 + def create_note(merge_request)
  63 + Note.create_status_change_note(merge_request, merge_request.target_project, current_user, merge_request.state, nil)
  64 + end
  65 +
  66 + def execute_hooks(merge_request)
  67 + if merge_request.project
  68 + merge_request.project.execute_hooks(merge_request.to_hook_data, :merge_request_hooks)
  69 + end
  70 + end
56 71 end
... ...
app/services/git_push_service.rb
... ... @@ -32,7 +32,7 @@ class GitPushService
32 32 end
33 33  
34 34 if push_to_branch?(ref)
35   - project.execute_hooks(@push_data.dup)
  35 + project.execute_hooks(@push_data.dup, :push_hooks)
36 36 project.execute_services(@push_data.dup)
37 37 end
38 38  
... ...
app/views/help/web_hooks.html.haml
1 1 = render layout: 'help/layout' do
2   - %h3.page-title Web hooks
  2 + %h3.page-title Project web hooks
  3 + %p.light
  4 + Project web hooks allow you to trigger url if new code is pushed or new issue is created
  5 + %hr
3 6  
4 7 %p.slead
5   - Every GitLab project can trigger a web server whenever the repo is pushed to.
  8 + You can configure web hook to listen for specific events like pushes, issues, merge requests.
  9 + %br
  10 + GitLab will send POST request with data to web hook url.
6 11 %br
7 12 Web Hooks can be used to update an external issue tracker, trigger CI builds, update a backup mirror, or even deploy to your production server.
  13 + %hr
  14 +
  15 + %h4 Push events
  16 + %p.light
  17 + Triggered when you push to the repository except pushing tags.
8 18 %br
9   - GitLab will send POST request with commits information on every push.
10   - %h5 Hooks request example:
11   - = render "projects/hooks/data_ex"
  19 + Request body:
  20 + = highlight_js do
  21 + :erb
  22 + {
  23 + "before": "95790bf891e76fee5e1747ab589903a6a1f80f22",
  24 + "after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
  25 + "ref": "refs/heads/master",
  26 + "user_id": 4,
  27 + "user_name": "John Smith",
  28 + "project_id": 15,
  29 + "repository": {
  30 + "name": "Diaspora",
  31 + "url": "git@localhost:diaspora.git",
  32 + "description": "",
  33 + "homepage": "http://localhost/diaspora",
  34 + },
  35 + "commits": [
  36 + {
  37 + "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
  38 + "message": "Update Catalan translation to e38cb41.",
  39 + "timestamp": "2011-12-12T14:27:31+02:00",
  40 + "url": "http://localhost/diaspora/commits/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
  41 + "author": {
  42 + "name": "Jordi Mallach",
  43 + "email": "jordi@softcatala.org",
  44 + }
  45 + },
  46 + // ...
  47 + {
  48 + "id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
  49 + "message": "fixed readme",
  50 + "timestamp": "2012-01-03T23:36:29+02:00",
  51 + "url": "http://localhost/diaspora/commits/da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
  52 + "author": {
  53 + "name": "GitLab dev user",
  54 + "email": "gitlabdev@dv6700.(none)",
  55 + },
  56 + },
  57 + ],
  58 + "total_commits_count": 4,
  59 + };
  60 +
12 61  
  62 + %h4.prepend-top-20 Issues events
  63 + %p.light
  64 + Triggered when new issue created or existing issue was closed.
  65 + %br
  66 + Request body:
  67 + = highlight_js do
  68 + :erb
  69 + {
  70 + "object_kind":"issue",
  71 + "object_attributes":{
  72 + "id":301,
  73 + "title":"New API: create/update/delete file",
  74 + "assignee_id":51,
  75 + "author_id":51,
  76 + "project_id":14,
  77 + "created_at":"2013-12-03T17:15:43Z",
  78 + "updated_at":"2013-12-03T17:15:43Z",
  79 + "position":0,
  80 + "branch_name":null,
  81 + "description":"Create new API for manipulations with repository",
  82 + "milestone_id":null,
  83 + "state":"opened",
  84 + "iid":23
  85 + }
  86 + }
  87 + %h4.prepend-top-20 Merge request events
  88 + %p.light
  89 + Triggered when new merge request created or existing merge request was merged/closed.
  90 + %br
  91 + Request body:
  92 + = highlight_js do
  93 + :erb
  94 + {
  95 + "object_kind":"merge_request",
  96 + "object_attributes":{
  97 + "id":99,
  98 + "target_branch":"master",
  99 + "source_branch":"ms-viewport",
  100 + "source_project_id":14,
  101 + "author_id":51,
  102 + "assignee_id":6,
  103 + "title":"MS-Viewport",
  104 + "created_at":"2013-12-03T17:23:34Z",
  105 + "updated_at":"2013-12-03T17:23:34Z",
  106 + "st_commits":null,
  107 + "st_diffs":null,
  108 + "milestone_id":null,
  109 + "state":"opened",
  110 + "merge_status":"unchecked",
  111 + "target_project_id":14,
  112 + "iid":1,
  113 + "description":""
  114 + }
  115 + }
... ...
app/views/projects/hooks/_data_ex.html.erb
... ... @@ -1,44 +0,0 @@
1   -<% data_ex_str = <<eos
2   -{
3   - "before": "95790bf891e76fee5e1747ab589903a6a1f80f22",
4   - "after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
5   - "ref": "refs/heads/master",
6   - "user_id": 4,
7   - "user_name": "John Smith",
8   - "project_id": 15,
9   - "repository": {
10   - "name": "Diaspora",
11   - "url": "git@localhost:diaspora.git",
12   - "description": "",
13   - "homepage": "http://localhost/diaspora",
14   - },
15   - "commits": [
16   - {
17   - "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
18   - "message": "Update Catalan translation to e38cb41.",
19   - "timestamp": "2011-12-12T14:27:31+02:00",
20   - "url": "http://localhost/diaspora/commits/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
21   - "author": {
22   - "name": "Jordi Mallach",
23   - "email": "jordi@softcatala.org",
24   - }
25   - },
26   - // ...
27   - {
28   - "id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
29   - "message": "fixed readme",
30   - "timestamp": "2012-01-03T23:36:29+02:00",
31   - "url": "http://localhost/diaspora/commits/da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
32   - "author": {
33   - "name": "GitLab dev user",
34   - "email": "gitlabdev@dv6700.(none)",
35   - },
36   - },
37   - ],
38   - "total_commits_count": 4,
39   -};
40   -eos
41   -%>
42   -<div class="<%= user_color_scheme_class%>">
43   - <%= raw Pygments::Lexer[:js].highlight(data_ex_str) %>
44   -</div>
app/views/projects/hooks/index.html.haml
1 1 %h3.page-title
2   - Post-receive hooks
  2 + Web hooks
3 3  
4 4 %p.light
5   - #{link_to "Post-receive hooks ", help_web_hooks_path, class: "vlink"} can be
6   - used for binding events when someone pushes to the repository.
  5 + #{link_to "Web hooks ", help_web_hooks_path, class: "vlink"} can be
  6 + used for binding events when something happends to the the project.
7 7  
8 8 %hr.clearfix
9 9  
... ... @@ -13,23 +13,50 @@
13 13 - @hook.errors.full_messages.each do |msg|
14 14 %p= msg
15 15 .control-group
16   - = f.label :url, "URL:"
  16 + = f.label :url, "URL"
17 17 .controls
18 18 = f.text_field :url, class: "text_field input-xxlarge input-xpadding", placeholder: 'http://example.com/trigger-ci.json'
19 19 &nbsp;
20 20 = f.submit "Add Web Hook", class: "btn btn-create"
  21 + .control-group
  22 + = f.label :url, "Trigger"
  23 + .controls
  24 + %div
  25 + = f.check_box :push_events, class: 'pull-left'
  26 + .prepend-left-20
  27 + = f.label :push_events, class: 'list-label' do
  28 + %strong Push events
  29 + %p.light
  30 + This url will be triggered in case of push to repository
  31 + %div
  32 + = f.check_box :issues_events, class: 'pull-left'
  33 + .prepend-left-20
  34 + = f.label :issues_events, class: 'list-label' do
  35 + %strong Issues events
  36 + %p.light
  37 + This url will be triggered for created issues
  38 + %div
  39 + = f.check_box :merge_requests_events, class: 'pull-left'
  40 + .prepend-left-20
  41 + = f.label :merge_requests_events, class: 'list-label' do
  42 + %strong Merge Request events
  43 + %p.light
  44 + This url will be triggered for created merge requests
21 45 %hr
22 46  
23 47 -if @hooks.any?
24 48 .ui-box
25 49 .title
26   - Hooks (#{@hooks.count})
  50 + Web Hooks (#{@hooks.count})
27 51 %ul.well-list
28 52 - @hooks.each do |hook|
29 53 %li
30   - %span.badge.badge-info POST
31   - &rarr;
32   - %span.monospace= hook.url
33 54 .pull-right
34 55 = link_to 'Test Hook', test_project_hook_path(@project, hook), class: "btn btn-small grouped"
35 56 = link_to 'Remove', project_hook_path(@project, hook), confirm: 'Are you sure?', method: :delete, class: "btn btn-remove btn-small grouped"
  57 + .clearfix
  58 + %span.monospace= hook.url
  59 + %p
  60 + - %w(push_events issues_events merge_requests_events).each do |trigger|
  61 + - if hook.send(trigger)
  62 + %span.label.label-gray= trigger.titleize
... ...
db/migrate/20131202192556_add_event_fields_for_web_hook.rb 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +class AddEventFieldsForWebHook < ActiveRecord::Migration
  2 + def change
  3 + add_column :web_hooks, :push_events, :boolean, default: true, null: false
  4 + add_column :web_hooks, :issues_events, :boolean, default: false, null: false
  5 + add_column :web_hooks, :merge_requests_events, :boolean, default: false, null: false
  6 + end
  7 +end
... ...
db/schema.rb
... ... @@ -11,7 +11,7 @@
11 11 #
12 12 # It's strongly recommended to check this file into your version control system.
13 13  
14   -ActiveRecord::Schema.define(:version => 20131112220935) do
  14 +ActiveRecord::Schema.define(:version => 20131202192556) do
15 15  
16 16 create_table "broadcast_messages", :force => true do |t|
17 17 t.text "message", :null => false
... ... @@ -334,10 +334,13 @@ ActiveRecord::Schema.define(:version =&gt; 20131112220935) do
334 334 create_table "web_hooks", :force => true do |t|
335 335 t.string "url"
336 336 t.integer "project_id"
337   - t.datetime "created_at", :null => false
338   - t.datetime "updated_at", :null => false
339   - t.string "type", :default => "ProjectHook"
  337 + t.datetime "created_at", :null => false
  338 + t.datetime "updated_at", :null => false
  339 + t.string "type", :default => "ProjectHook"
340 340 t.integer "service_id"
  341 + t.boolean "push_events", :default => true, :null => false
  342 + t.boolean "issues_events", :default => false, :null => false
  343 + t.boolean "merge_requests_events", :default => false, :null => false
341 344 end
342 345  
343 346 add_index "web_hooks", ["project_id"], :name => "index_web_hooks_on_project_id"
... ...
doc/api/projects.md
... ... @@ -402,6 +402,10 @@ Parameters:
402 402 {
403 403 "id": 1,
404 404 "url": "http://example.com/hook",
  405 + "project_id": 3,
  406 + "push_events": "true",
  407 + "issues_events": "true",
  408 + "merge_requests_events": "true",
405 409 "created_at": "2012-10-12T17:04:47Z"
406 410 }
407 411 ```
... ... @@ -419,6 +423,9 @@ Parameters:
419 423  
420 424 + `id` (required) - The ID or NAME of a project
421 425 + `url` (required) - The hook URL
  426 ++ `push_events` - Trigger hook on push events
  427 ++ `issues_events` - Trigger hook on issues events
  428 ++ `merge_requests_events` - Trigger hook on merge_requests events
422 429  
423 430  
424 431 ### Edit project hook
... ... @@ -434,6 +441,9 @@ Parameters:
434 441 + `id` (required) - The ID or NAME of a project
435 442 + `hook_id` (required) - The ID of a project hook
436 443 + `url` (required) - The hook URL
  444 ++ `push_events` - Trigger hook on push events
  445 ++ `issues_events` - Trigger hook on issues events
  446 ++ `merge_requests_events` - Trigger hook on merge_requests events
437 447  
438 448  
439 449 ### Delete project hook
... ...
lib/api/entities.rb
... ... @@ -24,6 +24,10 @@ module API
24 24 expose :id, :url, :created_at
25 25 end
26 26  
  27 + class ProjectHook < Hook
  28 + expose :project_id, :push_events, :issues_events, :merge_requests_events
  29 + end
  30 +
27 31 class ForkedFromProject < Grape::Entity
28 32 expose :id
29 33 expose :name, :name_with_namespace
... ...
lib/api/project_hooks.rb
... ... @@ -22,7 +22,7 @@ module API
22 22 # GET /projects/:id/hooks
23 23 get ":id/hooks" do
24 24 @hooks = paginate user_project.hooks
25   - present @hooks, with: Entities::Hook
  25 + present @hooks, with: Entities::ProjectHook
26 26 end
27 27  
28 28 # Get a project hook
... ... @@ -34,7 +34,7 @@ module API
34 34 # GET /projects/:id/hooks/:hook_id
35 35 get ":id/hooks/:hook_id" do
36 36 @hook = user_project.hooks.find(params[:hook_id])
37   - present @hook, with: Entities::Hook
  37 + present @hook, with: Entities::ProjectHook
38 38 end
39 39  
40 40  
... ... @@ -47,10 +47,11 @@ module API
47 47 # POST /projects/:id/hooks
48 48 post ":id/hooks" do
49 49 required_attributes! [:url]
  50 + attrs = attributes_for_keys [:url, :push_events, :issues_events, :merge_requests_events]
  51 + @hook = user_project.hooks.new(attrs)
50 52  
51   - @hook = user_project.hooks.new({"url" => params[:url]})
52 53 if @hook.save
53   - present @hook, with: Entities::Hook
  54 + present @hook, with: Entities::ProjectHook
54 55 else
55 56 if @hook.errors[:url].present?
56 57 error!("Invalid url given", 422)
... ... @@ -70,10 +71,10 @@ module API
70 71 put ":id/hooks/:hook_id" do
71 72 @hook = user_project.hooks.find(params[:hook_id])
72 73 required_attributes! [:url]
  74 + attrs = attributes_for_keys [:url, :push_events, :issues_events, :merge_requests_events]
73 75  
74   - attrs = attributes_for_keys [:url]
75 76 if @hook.update_attributes attrs
76   - present @hook, with: Entities::Hook
  77 + present @hook, with: Entities::ProjectHook
77 78 else
78 79 if @hook.errors[:url].present?
79 80 error!("Invalid url given", 422)
... ...
spec/models/service_hook_spec.rb
... ... @@ -2,13 +2,16 @@
2 2 #
3 3 # Table name: web_hooks
4 4 #
5   -# id :integer not null, primary key
6   -# url :string(255)
7   -# project_id :integer
8   -# created_at :datetime not null
9   -# updated_at :datetime not null
10   -# type :string(255) default("ProjectHook")
11   -# service_id :integer
  5 +# id :integer not null, primary key
  6 +# url :string(255)
  7 +# project_id :integer
  8 +# created_at :datetime not null
  9 +# updated_at :datetime not null
  10 +# type :string(255) default("ProjectHook")
  11 +# service_id :integer
  12 +# push_events :boolean default(TRUE), not null
  13 +# issues_events :boolean default(FALSE), not null
  14 +# merge_requests_events :boolean default(FALSE), not null
12 15 #
13 16  
14 17 require "spec_helper"
... ...
spec/models/system_hook_spec.rb
... ... @@ -2,13 +2,16 @@
2 2 #
3 3 # Table name: web_hooks
4 4 #
5   -# id :integer not null, primary key
6   -# url :string(255)
7   -# project_id :integer
8   -# created_at :datetime not null
9   -# updated_at :datetime not null
10   -# type :string(255) default("ProjectHook")
11   -# service_id :integer
  5 +# id :integer not null, primary key
  6 +# url :string(255)
  7 +# project_id :integer
  8 +# created_at :datetime not null
  9 +# updated_at :datetime not null
  10 +# type :string(255) default("ProjectHook")
  11 +# service_id :integer
  12 +# push_events :boolean default(TRUE), not null
  13 +# issues_events :boolean default(FALSE), not null
  14 +# merge_requests_events :boolean default(FALSE), not null
12 15 #
13 16  
14 17 require "spec_helper"
... ...
spec/models/web_hook_spec.rb
... ... @@ -2,13 +2,16 @@
2 2 #
3 3 # Table name: web_hooks
4 4 #
5   -# id :integer not null, primary key
6   -# url :string(255)
7   -# project_id :integer
8   -# created_at :datetime not null
9   -# updated_at :datetime not null
10   -# type :string(255) default("ProjectHook")
11   -# service_id :integer
  5 +# id :integer not null, primary key
  6 +# url :string(255)
  7 +# project_id :integer
  8 +# created_at :datetime not null
  9 +# updated_at :datetime not null
  10 +# type :string(255) default("ProjectHook")
  11 +# service_id :integer
  12 +# push_events :boolean default(TRUE), not null
  13 +# issues_events :boolean default(FALSE), not null
  14 +# merge_requests_events :boolean default(FALSE), not null
12 15 #
13 16  
14 17 require 'spec_helper'
... ...
spec/observers/merge_request_observer_spec.rb
... ... @@ -4,7 +4,7 @@ describe MergeRequestObserver do
4 4 let(:some_user) { create :user }
5 5 let(:assignee) { create :user }
6 6 let(:author) { create :user }
7   - let(:mr_mock) { double(:merge_request, id: 42, assignee: assignee, author: author) }
  7 + let(:mr_mock) { double(:merge_request, id: 42, assignee: assignee, author: author).as_null_object }
8 8 let(:assigned_mr) { create(:merge_request, assignee: assignee, author: author, target_project: create(:project)) }
9 9 let(:unassigned_mr) { create(:merge_request, author: author, target_project: create(:project)) }
10 10 let(:closed_assigned_mr) { create(:closed_merge_request, assignee: assignee, author: author, target_project: create(:project)) }
... ...
spec/requests/api/project_hooks_spec.rb 0 → 100644
... ... @@ -0,0 +1,132 @@
  1 +require 'spec_helper'
  2 +
  3 +describe API::API, 'ProjectHooks' do
  4 + include ApiHelpers
  5 + before(:each) { enable_observers }
  6 + after(:each) { disable_observers }
  7 +
  8 + let(:user) { create(:user) }
  9 + let(:user3) { create(:user) }
  10 + let!(:project) { create(:project_with_code, creator_id: user.id, namespace: user.namespace) }
  11 + let!(:hook) { create(:project_hook, project: project, url: "http://example.com") }
  12 +
  13 + before do
  14 + project.team << [user, :master]
  15 + project.team << [user3, :developer]
  16 + end
  17 +
  18 + describe "GET /projects/:id/hooks" do
  19 + context "authorized user" do
  20 + it "should return project hooks" do
  21 + get api("/projects/#{project.id}/hooks", user)
  22 + response.status.should == 200
  23 +
  24 + json_response.should be_an Array
  25 + json_response.count.should == 1
  26 + json_response.first['url'].should == "http://example.com"
  27 + end
  28 + end
  29 +
  30 + context "unauthorized user" do
  31 + it "should not access project hooks" do
  32 + get api("/projects/#{project.id}/hooks", user3)
  33 + response.status.should == 403
  34 + end
  35 + end
  36 + end
  37 +
  38 + describe "GET /projects/:id/hooks/:hook_id" do
  39 + context "authorized user" do
  40 + it "should return a project hook" do
  41 + get api("/projects/#{project.id}/hooks/#{hook.id}", user)
  42 + response.status.should == 200
  43 + json_response['url'].should == hook.url
  44 + end
  45 +
  46 + it "should return a 404 error if hook id is not available" do
  47 + get api("/projects/#{project.id}/hooks/1234", user)
  48 + response.status.should == 404
  49 + end
  50 + end
  51 +
  52 + context "unauthorized user" do
  53 + it "should not access an existing hook" do
  54 + get api("/projects/#{project.id}/hooks/#{hook.id}", user3)
  55 + response.status.should == 403
  56 + end
  57 + end
  58 +
  59 + it "should return a 404 error if hook id is not available" do
  60 + get api("/projects/#{project.id}/hooks/1234", user)
  61 + response.status.should == 404
  62 + end
  63 + end
  64 +
  65 + describe "POST /projects/:id/hooks" do
  66 + it "should add hook to project" do
  67 + expect {
  68 + post api("/projects/#{project.id}/hooks", user),
  69 + url: "http://example.com", issues_events: true
  70 + }.to change {project.hooks.count}.by(1)
  71 + response.status.should == 201
  72 + end
  73 +
  74 + it "should return a 400 error if url not given" do
  75 + post api("/projects/#{project.id}/hooks", user)
  76 + response.status.should == 400
  77 + end
  78 +
  79 + it "should return a 422 error if url not valid" do
  80 + post api("/projects/#{project.id}/hooks", user), "url" => "ftp://example.com"
  81 + response.status.should == 422
  82 + end
  83 + end
  84 +
  85 + describe "PUT /projects/:id/hooks/:hook_id" do
  86 + it "should update an existing project hook" do
  87 + put api("/projects/#{project.id}/hooks/#{hook.id}", user),
  88 + url: 'http://example.org', push_events: false
  89 + response.status.should == 200
  90 + json_response['url'].should == 'http://example.org'
  91 + end
  92 +
  93 + it "should return 404 error if hook id not found" do
  94 + put api("/projects/#{project.id}/hooks/1234", user), url: 'http://example.org'
  95 + response.status.should == 404
  96 + end
  97 +
  98 + it "should return 400 error if url is not given" do
  99 + put api("/projects/#{project.id}/hooks/#{hook.id}", user)
  100 + response.status.should == 400
  101 + end
  102 +
  103 + it "should return a 422 error if url is not valid" do
  104 + put api("/projects/#{project.id}/hooks/#{hook.id}", user), url: 'ftp://example.com'
  105 + response.status.should == 422
  106 + end
  107 + end
  108 +
  109 + describe "DELETE /projects/:id/hooks/:hook_id" do
  110 + it "should delete hook from project" do
  111 + expect {
  112 + delete api("/projects/#{project.id}/hooks/#{hook.id}", user)
  113 + }.to change {project.hooks.count}.by(-1)
  114 + response.status.should == 200
  115 + end
  116 +
  117 + it "should return success when deleting hook" do
  118 + delete api("/projects/#{project.id}/hooks/#{hook.id}", user)
  119 + response.status.should == 200
  120 + end
  121 +
  122 + it "should return success when deleting non existent hook" do
  123 + delete api("/projects/#{project.id}/hooks/42", user)
  124 + response.status.should == 200
  125 + end
  126 +
  127 + it "should return a 405 error if hook id not given" do
  128 + delete api("/projects/#{project.id}/hooks", user)
  129 + response.status.should == 405
  130 + end
  131 + end
  132 +end
... ...
spec/requests/api/projects_spec.rb
... ... @@ -10,7 +10,6 @@ describe API::API do
10 10 let(:user3) { create(:user) }
11 11 let(:admin) { create(:admin) }
12 12 let!(:project) { create(:project_with_code, creator_id: user.id, namespace: user.namespace) }
13   - let!(:hook) { create(:project_hook, project: project, url: "http://example.com") }
14 13 let!(:snippet) { create(:project_snippet, author: user, project: project, title: 'example') }
15 14 let!(:users_project) { create(:users_project, user: user, project: project, project_access: UsersProject::MASTER) }
16 15 let!(:users_project2) { create(:users_project, user: user3, project: project, project_access: UsersProject::DEVELOPER) }
... ... @@ -439,121 +438,6 @@ describe API::API do
439 438 end
440 439 end
441 440  
442   - describe "GET /projects/:id/hooks" do
443   - context "authorized user" do
444   - it "should return project hooks" do
445   - get api("/projects/#{project.id}/hooks", user)
446   - response.status.should == 200
447   -
448   - json_response.should be_an Array
449   - json_response.count.should == 1
450   - json_response.first['url'].should == "http://example.com"
451   - end
452   - end
453   -
454   - context "unauthorized user" do
455   - it "should not access project hooks" do
456   - get api("/projects/#{project.id}/hooks", user3)
457   - response.status.should == 403
458   - end
459   - end
460   - end
461   -
462   - describe "GET /projects/:id/hooks/:hook_id" do
463   - context "authorized user" do
464   - it "should return a project hook" do
465   - get api("/projects/#{project.id}/hooks/#{hook.id}", user)
466   - response.status.should == 200
467   - json_response['url'].should == hook.url
468   - end
469   -
470   - it "should return a 404 error if hook id is not available" do
471   - get api("/projects/#{project.id}/hooks/1234", user)
472   - response.status.should == 404
473   - end
474   - end
475   -
476   - context "unauthorized user" do
477   - it "should not access an existing hook" do
478   - get api("/projects/#{project.id}/hooks/#{hook.id}", user3)
479   - response.status.should == 403
480   - end
481   - end
482   -
483   - it "should return a 404 error if hook id is not available" do
484   - get api("/projects/#{project.id}/hooks/1234", user)
485   - response.status.should == 404
486   - end
487   - end
488   -
489   - describe "POST /projects/:id/hooks" do
490   - it "should add hook to project" do
491   - expect {
492   - post api("/projects/#{project.id}/hooks", user),
493   - url: "http://example.com"
494   - }.to change {project.hooks.count}.by(1)
495   - response.status.should == 201
496   - end
497   -
498   - it "should return a 400 error if url not given" do
499   - post api("/projects/#{project.id}/hooks", user)
500   - response.status.should == 400
501   - end
502   -
503   - it "should return a 422 error if url not valid" do
504   - post api("/projects/#{project.id}/hooks", user), "url" => "ftp://example.com"
505   - response.status.should == 422
506   - end
507   - end
508   -
509   - describe "PUT /projects/:id/hooks/:hook_id" do
510   - it "should update an existing project hook" do
511   - put api("/projects/#{project.id}/hooks/#{hook.id}", user),
512   - url: 'http://example.org'
513   - response.status.should == 200
514   - json_response['url'].should == 'http://example.org'
515   - end
516   -
517   - it "should return 404 error if hook id not found" do
518   - put api("/projects/#{project.id}/hooks/1234", user), url: 'http://example.org'
519   - response.status.should == 404
520   - end
521   -
522   - it "should return 400 error if url is not given" do
523   - put api("/projects/#{project.id}/hooks/#{hook.id}", user)
524   - response.status.should == 400
525   - end
526   -
527   - it "should return a 422 error if url is not valid" do
528   - put api("/projects/#{project.id}/hooks/#{hook.id}", user), url: 'ftp://example.com'
529   - response.status.should == 422
530   - end
531   - end
532   -
533   - describe "DELETE /projects/:id/hooks/:hook_id" do
534   - it "should delete hook from project" do
535   - expect {
536   - delete api("/projects/#{project.id}/hooks/#{hook.id}", user)
537   - }.to change {project.hooks.count}.by(-1)
538   - response.status.should == 200
539   - end
540   -
541   - it "should return success when deleting hook" do
542   - delete api("/projects/#{project.id}/hooks/#{hook.id}", user)
543   - response.status.should == 200
544   - end
545   -
546   - it "should return success when deleting non existent hook" do
547   - delete api("/projects/#{project.id}/hooks/42", user)
548   - response.status.should == 200
549   - end
550   -
551   - it "should return a 405 error if hook id not given" do
552   - delete api("/projects/#{project.id}/hooks", user)
553   - response.status.should == 405
554   - end
555   - end
556   -
557 441 describe "GET /projects/:id/snippets" do
558 442 it "should return an array of project snippets" do
559 443 get api("/projects/#{project.id}/snippets", user)
... ...
spec/services/git_push_service_spec.rb
... ... @@ -74,38 +74,19 @@ describe GitPushService do
74 74 end
75 75  
76 76 describe "Web Hooks" do
77   - context "with web hooks" do
78   - before do
79   - @project_hook = create(:project_hook)
80   - @project_hook_2 = create(:project_hook)
81   - project.hooks << [@project_hook, @project_hook_2]
82   -
83   - stub_request(:post, @project_hook.url)
84   - stub_request(:post, @project_hook_2.url)
85   - end
86   -
87   - it "executes multiple web hook" do
88   - @project_hook.should_receive(:async_execute).once
89   - @project_hook_2.should_receive(:async_execute).once
90   -
91   - service.execute(project, user, @oldrev, @newrev, @ref)
92   - end
93   - end
94   -
95 77 context "execute web hooks" do
96   - before do
97   - @project_hook = create(:project_hook)
98   - project.hooks << [@project_hook]
99   - stub_request(:post, @project_hook.url)
100   - end
101   -
102 78 it "when pushing a branch for the first time" do
103   - @project_hook.should_receive(:async_execute)
  79 + project.should_receive(:execute_hooks)
104 80 service.execute(project, user, @blankrev, 'newrev', 'refs/heads/master')
105 81 end
106 82  
  83 + it "when pushing new commits to existing branch" do
  84 + project.should_receive(:execute_hooks)
  85 + service.execute(project, user, 'oldrev', 'newrev', 'refs/heads/master')
  86 + end
  87 +
107 88 it "when pushing tags" do
108   - @project_hook.should_not_receive(:async_execute)
  89 + project.should_not_receive(:execute_hooks)
109 90 service.execute(project, user, 'newrev', 'newrev', 'refs/tags/v1.0.0')
110 91 end
111 92 end
... ...