Commit 155703c6132a86c13a18dba65da19129f49ea9c9

Authored by Dmitriy Zaporozhets
2 parents 52028dcd 6e333d50

Merge branch 'state-machine' of https://github.com/Undev/gitlabhq into Undev-state-machine

Conflicts:
	app/models/issue.rb
	app/models/merge_request.rb
Showing 55 changed files with 511 additions and 418 deletions   Show diff stats
Gemfile
... ... @@ -70,6 +70,9 @@ gem "github-markup", "~> 0.7.4", require: 'github/markup'
70 70 # Servers
71 71 gem "unicorn", "~> 4.4.0"
72 72  
  73 +# State machine
  74 +gem "state_machine"
  75 +
73 76 # Issue tags
74 77 gem "acts-as-taggable-on", "2.3.3"
75 78  
... ...
Gemfile.lock
... ... @@ -425,6 +425,7 @@ GEM
425 425 rack (~> 1.0)
426 426 tilt (~> 1.1, != 1.3.0)
427 427 stamp (0.3.0)
  428 + state_machine (1.1.2)
428 429 temple (0.5.5)
429 430 test_after_commit (0.0.1)
430 431 therubyracer (0.10.2)
... ... @@ -536,6 +537,7 @@ DEPENDENCIES
536 537 slim
537 538 spinach-rails
538 539 stamp
  540 + state_machine
539 541 test_after_commit
540 542 therubyracer
541 543 thin
... ...
app/assets/javascripts/merge_requests.js.coffee
... ... @@ -27,7 +27,7 @@ class MergeRequest
27 27 this.$el.find(selector)
28 28  
29 29 initMergeWidget: ->
30   - this.showState( @opts.current_state )
  30 + this.showState( @opts.current_status )
31 31  
32 32 if this.$('.automerge_widget').length and @opts.check_enable
33 33 $.get @opts.url_to_automerge_check, (data) =>
... ...
app/contexts/merge_requests_load_context.rb
... ... @@ -14,7 +14,7 @@ class MergeRequestsLoadContext < BaseContext
14 14 end
15 15  
16 16 merge_requests = merge_requests.page(params[:page]).per(20)
17   - merge_requests = merge_requests.includes(:author, :project).order("closed, created_at desc")
  17 + merge_requests = merge_requests.includes(:author, :project).order("state, created_at desc")
18 18  
19 19 # Filter by specific assignee_id (or lack thereof)?
20 20 if params[:assignee_id].present?
... ...
app/controllers/merge_requests_controller.rb
... ... @@ -73,14 +73,14 @@ class MergeRequestsController < ProjectResourceController
73 73 if @merge_request.unchecked?
74 74 @merge_request.check_if_can_be_merged
75 75 end
76   - render json: {state: @merge_request.human_state}
  76 + render json: {merge_status: @merge_request.human_merge_status}
77 77 rescue Gitlab::SatelliteNotExistError
78   - render json: {state: :no_satellite}
  78 + render json: {merge_status: :no_satellite}
79 79 end
80 80  
81 81 def automerge
82 82 return access_denied! unless can?(current_user, :accept_mr, @project)
83   - if @merge_request.open? && @merge_request.can_be_merged?
  83 + if @merge_request.opened? && @merge_request.can_be_merged?
84 84 @merge_request.should_remove_source_branch = params[:should_remove_source_branch]
85 85 @merge_request.automerge!(current_user)
86 86 @status = true
... ...
app/controllers/milestones_controller.rb
... ... @@ -12,7 +12,7 @@ class MilestonesController < ProjectResourceController
12 12  
13 13 def index
14 14 @milestones = case params[:f]
15   - when 'all'; @project.milestones.order("closed, due_date DESC")
  15 + when 'all'; @project.milestones.order("state, due_date DESC")
16 16 when 'closed'; @project.milestones.closed.order("due_date DESC")
17 17 else @project.milestones.active.order("due_date ASC")
18 18 end
... ...
app/helpers/issues_helper.rb
... ... @@ -6,7 +6,7 @@ module IssuesHelper
6 6  
7 7 def issue_css_classes issue
8 8 classes = "issue"
9   - classes << " closed" if issue.closed
  9 + classes << " closed" if issue.closed?
10 10 classes << " today" if issue.today?
11 11 classes
12 12 end
... ...
app/helpers/merge_requests_helper.rb
... ... @@ -12,7 +12,7 @@ module MergeRequestsHelper
12 12  
13 13 def mr_css_classes mr
14 14 classes = "merge_request"
15   - classes << " closed" if mr.closed
  15 + classes << " closed" if mr.closed?
16 16 classes << " merged" if mr.merged?
17 17 classes
18 18 end
... ...
app/models/concerns/issuable.rb
... ... @@ -17,10 +17,9 @@ module Issuable
17 17 validates :project, presence: true
18 18 validates :author, presence: true
19 19 validates :title, presence: true, length: { within: 0..255 }
20   - validates :closed, inclusion: { in: [true, false] }
21 20  
22   - scope :opened, -> { where(closed: false) }
23   - scope :closed, -> { where(closed: true) }
  21 + scope :opened, -> { with_state(:opened) }
  22 + scope :closed, -> { with_state(:closed) }
24 23 scope :of_group, ->(group) { where(project_id: group.project_ids) }
25 24 scope :of_user_team, ->(team) { where(project_id: team.project_ids, assignee_id: team.member_ids) }
26 25 scope :assigned, ->(u) { where(assignee_id: u.id)}
... ... @@ -62,14 +61,6 @@ module Issuable
62 61 assignee_id_changed?
63 62 end
64 63  
65   - def is_being_closed?
66   - closed_changed? && closed
67   - end
68   -
69   - def is_being_reopened?
70   - closed_changed? && !closed
71   - end
72   -
73 64 #
74 65 # Votes
75 66 #
... ...
app/models/issue.rb
... ... @@ -9,7 +9,7 @@
9 9 # project_id :integer
10 10 # created_at :datetime not null
11 11 # updated_at :datetime not null
12   -# closed :boolean default(FALSE), not null
  12 +# state :string default(FALSE), not null
13 13 # position :integer default(0)
14 14 # branch_name :string(255)
15 15 # description :text
... ... @@ -19,8 +19,9 @@
19 19 class Issue < ActiveRecord::Base
20 20 include Issuable
21 21  
22   - attr_accessible :title, :assignee_id, :closed, :position, :description,
23   - :milestone_id, :label_list, :author_id_of_changes
  22 + attr_accessible :title, :assignee_id, :position, :description,
  23 + :milestone_id, :label_list, :author_id_of_changes,
  24 + :state_event
24 25  
25 26 acts_as_taggable_on :labels
26 27  
... ... @@ -33,4 +34,20 @@ class Issue &lt; ActiveRecord::Base
33 34 opened.assigned(user)
34 35 end
35 36 end
  37 +
  38 + state_machine :state, initial: :opened do
  39 + event :close do
  40 + transition [:reopened, :opened] => :closed
  41 + end
  42 +
  43 + event :reopen do
  44 + transition closed: :reopened
  45 + end
  46 +
  47 + state :opened
  48 +
  49 + state :reopened
  50 +
  51 + state :closed
  52 + end
36 53 end
... ...
app/models/merge_request.rb
... ... @@ -9,15 +9,14 @@
9 9 # author_id :integer
10 10 # assignee_id :integer
11 11 # title :string(255)
12   -# closed :boolean default(FALSE), not null
  12 +# state :string(255) not null
13 13 # created_at :datetime not null
14 14 # updated_at :datetime not null
15 15 # st_commits :text(2147483647)
16 16 # st_diffs :text(2147483647)
17   -# merged :boolean default(FALSE), not null
18   -# state :integer default(1), not null
19   -# milestone_id :integer
  17 +# merge_status :integer default(1), not null
20 18 #
  19 +# milestone_id :integer
21 20  
22 21 require Rails.root.join("app/models/commit")
23 22 require Rails.root.join("lib/static_model")
... ... @@ -25,11 +24,33 @@ require Rails.root.join(&quot;lib/static_model&quot;)
25 24 class MergeRequest < ActiveRecord::Base
26 25 include Issuable
27 26  
28   - attr_accessible :title, :assignee_id, :closed, :target_branch, :source_branch, :milestone_id,
29   - :author_id_of_changes
  27 + attr_accessible :title, :assignee_id, :target_branch, :source_branch, :milestone_id,
  28 + :author_id_of_changes, :state_event
30 29  
31 30 attr_accessor :should_remove_source_branch
32 31  
  32 + state_machine :state, initial: :opened do
  33 + event :close do
  34 + transition [:reopened, :opened] => :closed
  35 + end
  36 +
  37 + event :merge do
  38 + transition [:reopened, :opened] => :merged
  39 + end
  40 +
  41 + event :reopen do
  42 + transition closed: :reopened
  43 + end
  44 +
  45 + state :opened
  46 +
  47 + state :reopened
  48 +
  49 + state :closed
  50 +
  51 + state :merged
  52 + end
  53 +
33 54 BROKEN_DIFF = "--broken-diff"
34 55  
35 56 UNCHECKED = 1
... ... @@ -43,8 +64,13 @@ class MergeRequest &lt; ActiveRecord::Base
43 64 validates :target_branch, presence: true
44 65 validate :validate_branches
45 66  
  67 + scope :merged, -> { with_state(:merged) }
46 68  
47 69 class << self
  70 + def find_all_by_branch(branch_name)
  71 + where("source_branch LIKE :branch OR target_branch LIKE :branch", branch: branch_name)
  72 + end
  73 +
48 74 def cared(user)
49 75 where('assignee_id = :user OR author_id = :user', user: user.id)
50 76 end
... ... @@ -58,13 +84,13 @@ class MergeRequest &lt; ActiveRecord::Base
58 84 end
59 85 end
60 86  
61   - def human_state
62   - states = {
  87 + def human_merge_status
  88 + merge_statuses = {
63 89 CAN_BE_MERGED => "can_be_merged",
64 90 CANNOT_BE_MERGED => "cannot_be_merged",
65 91 UNCHECKED => "unchecked"
66 92 }
67   - states[self.state]
  93 + merge_statuses[self.merge_status]
68 94 end
69 95  
70 96 def validate_branches
... ... @@ -79,20 +105,20 @@ class MergeRequest &lt; ActiveRecord::Base
79 105 end
80 106  
81 107 def unchecked?
82   - state == UNCHECKED
  108 + merge_status == UNCHECKED
83 109 end
84 110  
85 111 def mark_as_unchecked
86   - self.state = UNCHECKED
  112 + self.merge_status = UNCHECKED
87 113 self.save
88 114 end
89 115  
90 116 def can_be_merged?
91   - state == CAN_BE_MERGED
  117 + merge_status == CAN_BE_MERGED
92 118 end
93 119  
94 120 def check_if_can_be_merged
95   - self.state = if Gitlab::Satellite::MergeAction.new(self.author, self).can_be_merged?
  121 + self.merge_status = if Gitlab::Satellite::MergeAction.new(self.author, self).can_be_merged?
96 122 CAN_BE_MERGED
97 123 else
98 124 CANNOT_BE_MERGED
... ... @@ -105,7 +131,7 @@ class MergeRequest &lt; ActiveRecord::Base
105 131 end
106 132  
107 133 def reloaded_diffs
108   - if open? && unmerged_diffs.any?
  134 + if opened? && unmerged_diffs.any?
109 135 self.st_diffs = unmerged_diffs
110 136 self.save
111 137 end
... ... @@ -135,10 +161,6 @@ class MergeRequest &lt; ActiveRecord::Base
135 161 commits.first
136 162 end
137 163  
138   - def merged?
139   - merged && merge_event
140   - end
141   -
142 164 def merge_event
143 165 self.project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::MERGED).last
144 166 end
... ... @@ -153,26 +175,16 @@ class MergeRequest &lt; ActiveRecord::Base
153 175  
154 176 def probably_merged?
155 177 unmerged_commits.empty? &&
156   - commits.any? && open?
157   - end
158   -
159   - def open?
160   - !closed
161   - end
162   -
163   - def mark_as_merged!
164   - self.merged = true
165   - self.closed = true
166   - save
  178 + commits.any? && opened?
167 179 end
168 180  
169 181 def mark_as_unmergable
170   - self.state = CANNOT_BE_MERGED
  182 + self.merge_status = CANNOT_BE_MERGED
171 183 self.save
172 184 end
173 185  
174 186 def reloaded_commits
175   - if open? && unmerged_commits.any?
  187 + if opened? && unmerged_commits.any?
176 188 self.st_commits = unmerged_commits
177 189 save
178 190 end
... ... @@ -188,7 +200,8 @@ class MergeRequest &lt; ActiveRecord::Base
188 200 end
189 201  
190 202 def merge!(user_id)
191   - self.mark_as_merged!
  203 + self.merge
  204 +
192 205 Event.create(
193 206 project: self.project,
194 207 action: Event::MERGED,
... ...
app/models/milestone.rb
... ... @@ -13,19 +13,32 @@
13 13 #
14 14  
15 15 class Milestone < ActiveRecord::Base
16   - attr_accessible :title, :description, :due_date, :closed, :author_id_of_changes
  16 + attr_accessible :title, :description, :due_date, :state_event, :author_id_of_changes
17 17 attr_accessor :author_id_of_changes
18 18  
19 19 belongs_to :project
20 20 has_many :issues
21 21 has_many :merge_requests
22 22  
23   - scope :active, -> { where(closed: false) }
24   - scope :closed, -> { where(closed: true) }
  23 + scope :active, -> { with_state(:active) }
  24 + scope :closed, -> { with_state(:closed) }
25 25  
26 26 validates :title, presence: true
27 27 validates :project, presence: true
28   - validates :closed, inclusion: { in: [true, false] }
  28 +
  29 + state_machine :state, initial: :active do
  30 + event :close do
  31 + transition active: :closed
  32 + end
  33 +
  34 + event :activate do
  35 + transition closed: :active
  36 + end
  37 +
  38 + state :closed
  39 +
  40 + state :active
  41 + end
29 42  
30 43 def expired?
31 44 if due_date
... ... @@ -68,17 +81,13 @@ class Milestone &lt; ActiveRecord::Base
68 81 end
69 82  
70 83 def can_be_closed?
71   - open? && issues.opened.count.zero?
  84 + active? && issues.opened.count.zero?
72 85 end
73 86  
74 87 def is_empty?
75 88 total_items_count.zero?
76 89 end
77 90  
78   - def open?
79   - !closed
80   - end
81   -
82 91 def author_id
83 92 author_id_of_changes
84 93 end
... ...
app/models/project.rb
... ... @@ -43,7 +43,7 @@ class Project &lt; ActiveRecord::Base
43 43  
44 44 has_many :events, dependent: :destroy
45 45 has_many :merge_requests, dependent: :destroy
46   - has_many :issues, dependent: :destroy, order: "closed, created_at DESC"
  46 + has_many :issues, dependent: :destroy, order: "state, created_at DESC"
47 47 has_many :milestones, dependent: :destroy
48 48 has_many :users_projects, dependent: :destroy
49 49 has_many :notes, dependent: :destroy
... ...
app/observers/activity_observer.rb
... ... @@ -20,15 +20,23 @@ class ActivityObserver &lt; ActiveRecord::Observer
20 20 end
21 21 end
22 22  
23   - def after_save(record)
24   - if record.changed.include?("closed") && record.author_id_of_changes
  23 + def after_close(record, transition)
25 24 Event.create(
26 25 project: record.project,
27 26 target_id: record.id,
28 27 target_type: record.class.name,
29   - action: (record.closed ? Event::CLOSED : Event::REOPENED),
  28 + action: Event::CLOSED,
  29 + author_id: record.author_id_of_changes
  30 + )
  31 + end
  32 +
  33 + def after_reopen(record, transition)
  34 + Event.create(
  35 + project: record.project,
  36 + target_id: record.id,
  37 + target_type: record.class.name,
  38 + action: Event::REOPENED,
30 39 author_id: record.author_id_of_changes
31 40 )
32   - end
33 41 end
34 42 end
... ...
app/observers/issue_observer.rb
... ... @@ -7,22 +7,31 @@ class IssueObserver &lt; ActiveRecord::Observer
7 7 end
8 8 end
9 9  
10   - def after_update(issue)
  10 + def after_close(issue, transition)
11 11 send_reassigned_email(issue) if issue.is_being_reassigned?
12 12  
13   - status = nil
14   - status = 'closed' if issue.is_being_closed?
15   - status = 'reopened' if issue.is_being_reopened?
16   - if status
17   - Note.create_status_change_note(issue, current_user, status)
18   - [issue.author, issue.assignee].compact.each do |recipient|
19   - Notify.delay.issue_status_changed_email(recipient.id, issue.id, status, current_user.id)
20   - end
21   - end
  13 + create_note(issue)
  14 + end
  15 +
  16 + def after_reopen(issue, transition)
  17 + send_reassigned_email(issue) if issue.is_being_reassigned?
  18 +
  19 + create_note(issue)
  20 + end
  21 +
  22 + def after_update(issue)
  23 + send_reassigned_email(issue) if issue.is_being_reassigned?
22 24 end
23 25  
24 26 protected
25 27  
  28 + def create_note(issue)
  29 + Note.create_status_change_note(issue, current_user, issue.state)
  30 + [issue.author, issue.assignee].compact.each do |recipient|
  31 + Notify.delay.issue_status_changed_email(recipient.id, issue.id, issue.state, current_user.id)
  32 + end
  33 + end
  34 +
26 35 def send_reassigned_email(issue)
27 36 recipient_ids = [issue.assignee_id, issue.assignee_id_was].keep_if {|id| id && id != current_user.id }
28 37  
... ...
app/observers/merge_request_observer.rb
... ... @@ -7,15 +7,20 @@ class MergeRequestObserver &lt; ActiveRecord::Observer
7 7 end
8 8 end
9 9  
10   - def after_update(merge_request)
  10 + def after_close(merge_request, transition)
11 11 send_reassigned_email(merge_request) if merge_request.is_being_reassigned?
12 12  
13   - status = nil
14   - status = 'closed' if merge_request.is_being_closed?
15   - status = 'reopened' if merge_request.is_being_reopened?
16   - if status
17   - Note.create_status_change_note(merge_request, current_user, status)
18   - end
  13 + Note.create_status_change_note(merge_request, current_user, merge_request.state)
  14 + end
  15 +
  16 + def after_reopen(merge_request, transition)
  17 + send_reassigned_email(merge_request) if merge_request.is_being_reassigned?
  18 +
  19 + Note.create_status_change_note(merge_request, current_user, merge_request.state)
  20 + end
  21 +
  22 + def after_update(merge_request)
  23 + send_reassigned_email(merge_request) if merge_request.is_being_reassigned?
19 24 end
20 25  
21 26 protected
... ...
app/views/issues/_show.html.haml
... ... @@ -8,10 +8,10 @@
8 8 %i.icon-comment
9 9 = issue.notes.count
10 10 - if can? current_user, :modify_issue, issue
11   - - if issue.closed
12   - = link_to 'Reopen', project_issue_path(issue.project, issue, issue: {closed: false }, status_only: true), method: :put, class: "btn btn-small grouped reopen_issue", remote: true
  11 + - if issue.closed?
  12 + = link_to 'Reopen', project_issue_path(issue.project, issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-small grouped reopen_issue", remote: true
13 13 - else
14   - = link_to 'Close', project_issue_path(issue.project, issue, issue: {closed: true }, status_only: true), method: :put, class: "btn btn-small grouped close_issue", remote: true
  14 + = link_to 'Close', project_issue_path(issue.project, issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-small grouped close_issue", remote: true
15 15 = link_to edit_project_issue_path(issue.project, issue), class: "btn btn-small edit-issue-link grouped" do
16 16 %i.icon-edit
17 17 Edit
... ...
app/views/issues/show.html.haml
... ... @@ -7,10 +7,10 @@
7 7  
8 8 %span.pull-right
9 9 - if can?(current_user, :admin_project, @project) || @issue.author == current_user
10   - - if @issue.closed
11   - = link_to 'Reopen', project_issue_path(@project, @issue, issue: {closed: false }, status_only: true), method: :put, class: "btn grouped reopen_issue"
  10 + - if @issue.closed?
  11 + = link_to 'Reopen', project_issue_path(@project, @issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn grouped reopen_issue"
12 12 - else
13   - = link_to 'Close', project_issue_path(@project, @issue, issue: {closed: true }, status_only: true), method: :put, class: "btn grouped close_issue", title: "Close Issue"
  13 + = link_to 'Close', project_issue_path(@project, @issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn grouped close_issue", title: "Close Issue"
14 14 - if can?(current_user, :admin_project, @project) || @issue.author == current_user
15 15 = link_to edit_project_issue_path(@project, @issue), class: "btn grouped" do
16 16 %i.icon-edit
... ... @@ -27,7 +27,7 @@
27 27 .ui-box.ui-box-show
28 28 .ui-box-head
29 29 %h4.box-title
30   - - if @issue.closed
  30 + - if @issue.closed?
31 31 .error.status_info Closed
32 32 = gfm escape_once(@issue.title)
33 33  
... ...
app/views/merge_requests/_show.html.haml
... ... @@ -29,10 +29,10 @@
29 29 $(function(){
30 30 merge_request = new MergeRequest({
31 31 url_to_automerge_check: "#{automerge_check_project_merge_request_path(@project, @merge_request)}",
32   - check_enable: #{@merge_request.state == MergeRequest::UNCHECKED ? "true" : "false"},
  32 + check_enable: #{@merge_request.merge_status == MergeRequest::UNCHECKED ? "true" : "false"},
33 33 url_to_ci_check: "#{ci_status_project_merge_request_path(@project, @merge_request)}",
34 34 ci_enable: #{@project.gitlab_ci? ? "true" : "false"},
35   - current_state: "#{@merge_request.human_state}",
  35 + current_status: "#{@merge_request.human_merge_status}",
36 36 action: "#{controller.action_name}"
37 37 });
38 38 });
... ...
app/views/merge_requests/show/_mr_accept.html.haml
... ... @@ -3,7 +3,7 @@
3 3 %strong Only masters can accept MR
4 4  
5 5  
6   -- if @merge_request.open? && @commits.any? && can?(current_user, :accept_mr, @project)
  6 +- if @merge_request.opened? && @commits.any? && can?(current_user, :accept_mr, @project)
7 7 .automerge_widget.can_be_merged{style: "display:none"}
8 8 .alert.alert-success
9 9 %span
... ...
app/views/merge_requests/show/_mr_box.html.haml
1 1 .ui-box.ui-box-show
2 2 .ui-box-head
3 3 %h4.box-title
4   - - if @merge_request.merged
  4 + - if @merge_request.merged?
5 5 .error.status_info
6 6 %i.icon-ok
7 7 Merged
8   - - elsif @merge_request.closed
  8 + - elsif @merge_request.closed?
9 9 .error.status_info Closed
10 10 = gfm escape_once(@merge_request.title)
11 11  
... ... @@ -21,14 +21,14 @@
21 21 %strong= link_to_gfm truncate(milestone.title, length: 20), project_milestone_path(milestone.project, milestone)
22 22  
23 23  
24   - - if @merge_request.closed
  24 + - if @merge_request.closed?
25 25 .ui-box-bottom
26   - - if @merge_request.merged?
27   - %span
28   - Merged by #{link_to_member(@project, @merge_request.merge_event.author)}
29   - %small #{time_ago_in_words(@merge_request.merge_event.created_at)} ago.
30   - - elsif @merge_request.closed_event
31   - %span
32   - Closed by #{link_to_member(@project, @merge_request.closed_event.author)}
33   - %small #{time_ago_in_words(@merge_request.closed_event.created_at)} ago.
  26 + %span
  27 + Closed by #{link_to_member(@project, @merge_request.closed_event.author)}
  28 + %small #{time_ago_in_words(@merge_request.closed_event.created_at)} ago.
  29 + - if @merge_request.merged?
  30 + .ui-box-bottom
  31 + %span
  32 + Merged by #{link_to_member(@project, @merge_request.merge_event.author)}
  33 + %small #{time_ago_in_words(@merge_request.merge_event.created_at)} ago.
34 34  
... ...
app/views/merge_requests/show/_mr_ci.html.haml
1   -- if @merge_request.open? && @commits.any?
  1 +- if @merge_request.opened? && @commits.any?
2 2 .ci_widget.ci-success{style: "display:none"}
3 3 .alert.alert-success
4 4 %i.icon-ok
... ...
app/views/merge_requests/show/_mr_title.html.haml
... ... @@ -7,7 +7,7 @@
7 7  
8 8 %span.pull-right
9 9 - if can?(current_user, :modify_merge_request, @merge_request)
10   - - if @merge_request.open?
  10 + - if @merge_request.opened?
11 11 .left.btn-group
12 12 %a.btn.grouped.dropdown-toggle{ data: {toggle: :dropdown} }
13 13 %i.icon-download-alt
... ... @@ -17,7 +17,7 @@
17 17 %li= link_to "Email Patches", project_merge_request_path(@project, @merge_request, format: :patch)
18 18 %li= link_to "Plain Diff", project_merge_request_path(@project, @merge_request, format: :diff)
19 19  
20   - = link_to 'Close', project_merge_request_path(@project, @merge_request, merge_request: {closed: true }, status_only: true), method: :put, class: "btn grouped btn-close", title: "Close merge request"
  20 + = link_to 'Close', project_merge_request_path(@project, @merge_request, merge_request: {state_event: :close }), method: :put, class: "btn grouped btn-close", title: "Close merge request"
21 21  
22 22 = link_to edit_project_merge_request_path(@project, @merge_request), class: "btn grouped" do
23 23 %i.icon-edit
... ...
app/views/milestones/_milestone.html.haml
1   -%li{class: "milestone milestone-#{milestone.closed ? 'closed' : 'open'}", id: dom_id(milestone) }
  1 +%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone) }
2 2 .pull-right
3   - - if can?(current_user, :admin_milestone, milestone.project) and milestone.open?
  3 + - if can?(current_user, :admin_milestone, milestone.project) and milestone.active?
4 4 = link_to edit_project_milestone_path(milestone.project, milestone), class: "btn btn-small edit-milestone-link grouped" do
5 5 %i.icon-edit
6 6 Edit
7 7 %h4
8 8 = link_to_gfm truncate(milestone.title, length: 100), project_milestone_path(milestone.project, milestone)
9   - - if milestone.expired? and not milestone.closed
  9 + - if milestone.expired? and not milestone.closed?
10 10 %span.cred (Expired)
11 11 %small
12 12 = milestone.expires_at
... ...
app/views/milestones/show.html.haml
... ... @@ -9,7 +9,7 @@
9 9 &larr; To milestones list
10 10 .span6
11 11 .pull-right
12   - - unless @milestone.closed
  12 + - unless @milestone.closed?
13 13 = link_to new_project_issue_path(@project, issue: { milestone_id: @milestone.id }), class: "btn btn-small grouped", title: "New Issue" do
14 14 %i.icon-plus
15 15 New Issue
... ... @@ -25,12 +25,12 @@
25 25 %hr
26 26 %p
27 27 %span All issues for this milestone are closed. You may close milestone now.
28   - = link_to 'Close Milestone', project_milestone_path(@project, @milestone, milestone: {closed: true }), method: :put, class: "btn btn-small btn-remove"
  28 + = link_to 'Close Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-small btn-remove"
29 29  
30 30 .ui-box.ui-box-show
31 31 .ui-box-head
32 32 %h4.box-title
33   - - if @milestone.closed
  33 + - if @milestone.closed?
34 34 .error.status_info Closed
35 35 - elsif @milestone.expired?
36 36 .error.status_info Expired
... ... @@ -63,7 +63,7 @@
63 63 %li=link_to('All Issues', '#')
64 64 %ul.well-list
65 65 - @issues.each do |issue|
66   - %li{data: {closed: issue.closed}}
  66 + %li{data: {closed: issue.closed?}}
67 67 = link_to [@project, issue] do
68 68 %span.badge.badge-info ##{issue.id}
69 69 &ndash;
... ... @@ -77,7 +77,7 @@
77 77 %li=link_to('All Merge Requests', '#')
78 78 %ul.well-list
79 79 - @merge_requests.each do |merge_request|
80   - %li{data: {closed: merge_request.closed}}
  80 + %li{data: {closed: merge_request.closed?}}
81 81 = link_to [@project, merge_request] do
82 82 %span.badge.badge-info ##{merge_request.id}
83 83 &ndash;
... ...
db/migrate/20130214154045_rename_state_to_merge_status_in_milestone.rb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +class RenameStateToMergeStatusInMilestone < ActiveRecord::Migration
  2 + def change
  3 + rename_column :merge_requests, :state, :merge_status
  4 + end
  5 +end
... ...
db/migrate/20130218140952_add_state_to_issue.rb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +class AddStateToIssue < ActiveRecord::Migration
  2 + def change
  3 + add_column :issues, :state, :string
  4 + end
  5 +end
... ...
db/migrate/20130218141038_add_state_to_merge_request.rb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +class AddStateToMergeRequest < ActiveRecord::Migration
  2 + def change
  3 + add_column :merge_requests, :state, :string
  4 + end
  5 +end
... ...
db/migrate/20130218141117_add_state_to_milestone.rb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +class AddStateToMilestone < ActiveRecord::Migration
  2 + def change
  3 + add_column :milestones, :state, :string
  4 + end
  5 +end
... ...
db/migrate/20130218141258_convert_closed_to_state_in_issue.rb 0 → 100644
... ... @@ -0,0 +1,14 @@
  1 +class ConvertClosedToStateInIssue < ActiveRecord::Migration
  2 + def up
  3 + Issue.transaction do
  4 + Issue.where(closed: true).update_all("state = 'closed'")
  5 + Issue.where(closed: false).update_all("state = 'opened'")
  6 + end
  7 + end
  8 +
  9 + def down
  10 + Issue.transaction do
  11 + Issue.where(state: :closed).update_all("closed = 1")
  12 + end
  13 + end
  14 +end
... ...
db/migrate/20130218141327_convert_closed_to_state_in_merge_request.rb 0 → 100644
... ... @@ -0,0 +1,16 @@
  1 +class ConvertClosedToStateInMergeRequest < ActiveRecord::Migration
  2 + def up
  3 + MergeRequest.transaction do
  4 + MergeRequest.where("closed = 1 AND merged = 1").update_all("state = 'merged'")
  5 + MergeRequest.where("closed = 1 AND merged = 0").update_all("state = 'closed'")
  6 + MergeRequest.where("closed = 0").update_all("state = 'opened'")
  7 + end
  8 + end
  9 +
  10 + def down
  11 + MergeRequest.transaction do
  12 + MergeRequest.where(state: :closed).update_all("closed = 1")
  13 + MergeRequest.where(state: :merged).update_all("closed = 1, merged = 1")
  14 + end
  15 + end
  16 +end
... ...
db/migrate/20130218141344_convert_closed_to_state_in_milestone.rb 0 → 100644
... ... @@ -0,0 +1,14 @@
  1 +class ConvertClosedToStateInMilestone < ActiveRecord::Migration
  2 + def up
  3 + Milestone.transaction do
  4 + Milestone.where(closed: false).update_all("state = 'opened'")
  5 + Milestone.where(closed: false).update_all("state = 'active'")
  6 + end
  7 + end
  8 +
  9 + def down
  10 + Milestone.transaction do
  11 + Milestone.where(state: :closed).update_all("closed = 1")
  12 + end
  13 + end
  14 +end
... ...
db/migrate/20130218141444_remove_merged_from_merge_request.rb 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +class RemoveMergedFromMergeRequest < ActiveRecord::Migration
  2 + def up
  3 + remove_column :merge_requests, :merged
  4 + end
  5 +
  6 + def down
  7 + add_column :merge_requests, :merged, :boolean, default: true, null: false
  8 + end
  9 +end
... ...
db/migrate/20130218141507_remove_closed_from_issue.rb 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +class RemoveClosedFromIssue < ActiveRecord::Migration
  2 + def up
  3 + remove_column :issues, :closed
  4 + end
  5 +
  6 + def down
  7 + add_column :issues, :closed, :boolean
  8 + end
  9 +end
... ...
db/migrate/20130218141536_remove_closed_from_merge_request.rb 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +class RemoveClosedFromMergeRequest < ActiveRecord::Migration
  2 + def up
  3 + remove_column :merge_requests, :closed
  4 + end
  5 +
  6 + def down
  7 + add_column :merge_requests, :closed, :boolean
  8 + end
  9 +end
... ...
db/migrate/20130218141554_remove_closed_from_milestone.rb 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +class RemoveClosedFromMilestone < ActiveRecord::Migration
  2 + def up
  3 + remove_column :milestones, :closed
  4 + end
  5 +
  6 + def down
  7 + add_column :milestones, :closed, :boolean
  8 + end
  9 +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 => 20130131070232) do
  14 +ActiveRecord::Schema.define(:version => 20130218141554) do
15 15  
16 16 create_table "events", :force => true do |t|
17 17 t.string "target_type"
... ... @@ -37,18 +37,17 @@ ActiveRecord::Schema.define(:version =&gt; 20130131070232) do
37 37 t.integer "assignee_id"
38 38 t.integer "author_id"
39 39 t.integer "project_id"
40   - t.datetime "created_at", :null => false
41   - t.datetime "updated_at", :null => false
42   - t.boolean "closed", :default => false, :null => false
  40 + t.datetime "created_at", :null => false
  41 + t.datetime "updated_at", :null => false
43 42 t.integer "position", :default => 0
44 43 t.string "branch_name"
45 44 t.text "description"
46 45 t.integer "milestone_id"
  46 + t.string "state"
47 47 end
48 48  
49 49 add_index "issues", ["assignee_id"], :name => "index_issues_on_assignee_id"
50 50 add_index "issues", ["author_id"], :name => "index_issues_on_author_id"
51   - add_index "issues", ["closed"], :name => "index_issues_on_closed"
52 51 add_index "issues", ["created_at"], :name => "index_issues_on_created_at"
53 52 add_index "issues", ["milestone_id"], :name => "index_issues_on_milestone_id"
54 53 add_index "issues", ["project_id"], :name => "index_issues_on_project_id"
... ... @@ -69,25 +68,23 @@ ActiveRecord::Schema.define(:version =&gt; 20130131070232) do
69 68 add_index "keys", ["user_id"], :name => "index_keys_on_user_id"
70 69  
71 70 create_table "merge_requests", :force => true do |t|
72   - t.string "target_branch", :null => false
73   - t.string "source_branch", :null => false
74   - t.integer "project_id", :null => false
  71 + t.string "target_branch", :null => false
  72 + t.string "source_branch", :null => false
  73 + t.integer "project_id", :null => false
75 74 t.integer "author_id"
76 75 t.integer "assignee_id"
77 76 t.string "title"
78   - t.boolean "closed", :default => false, :null => false
79   - t.datetime "created_at", :null => false
80   - t.datetime "updated_at", :null => false
  77 + t.datetime "created_at", :null => false
  78 + t.datetime "updated_at", :null => false
81 79 t.text "st_commits", :limit => 2147483647
82 80 t.text "st_diffs", :limit => 2147483647
83   - t.boolean "merged", :default => false, :null => false
84   - t.integer "state", :default => 1, :null => false
  81 + t.integer "merge_status", :default => 1, :null => false
85 82 t.integer "milestone_id"
  83 + t.string "state"
86 84 end
87 85  
88 86 add_index "merge_requests", ["assignee_id"], :name => "index_merge_requests_on_assignee_id"
89 87 add_index "merge_requests", ["author_id"], :name => "index_merge_requests_on_author_id"
90   - add_index "merge_requests", ["closed"], :name => "index_merge_requests_on_closed"
91 88 add_index "merge_requests", ["created_at"], :name => "index_merge_requests_on_created_at"
92 89 add_index "merge_requests", ["milestone_id"], :name => "index_merge_requests_on_milestone_id"
93 90 add_index "merge_requests", ["project_id"], :name => "index_merge_requests_on_project_id"
... ... @@ -96,13 +93,13 @@ ActiveRecord::Schema.define(:version =&gt; 20130131070232) do
96 93 add_index "merge_requests", ["title"], :name => "index_merge_requests_on_title"
97 94  
98 95 create_table "milestones", :force => true do |t|
99   - t.string "title", :null => false
100   - t.integer "project_id", :null => false
  96 + t.string "title", :null => false
  97 + t.integer "project_id", :null => false
101 98 t.text "description"
102 99 t.date "due_date"
103   - t.boolean "closed", :default => false, :null => false
104   - t.datetime "created_at", :null => false
105   - t.datetime "updated_at", :null => false
  100 + t.datetime "created_at", :null => false
  101 + t.datetime "updated_at", :null => false
  102 + t.string "state"
106 103 end
107 104  
108 105 add_index "milestones", ["due_date"], :name => "index_milestones_on_due_date"
... ...
features/steps/project/project_issues.rb
... ... @@ -122,10 +122,9 @@ class ProjectIssues &lt; Spinach::FeatureSteps
122 122  
123 123 And 'project "Shop" have "Release 0.3" closed issue' do
124 124 project = Project.find_by_name("Shop")
125   - create(:issue,
  125 + create(:closed_issue,
126 126 :title => "Release 0.3",
127 127 :project => project,
128   - :author => project.users.first,
129   - :closed => true)
  128 + :author => project.users.first)
130 129 end
131 130 end
... ...
features/steps/project/project_merge_requests.rb
... ... @@ -26,7 +26,7 @@ class ProjectMergeRequests &lt; Spinach::FeatureSteps
26 26  
27 27 Then 'I should see closed merge request "Bug NS-04"' do
28 28 mr = MergeRequest.find_by_title("Bug NS-04")
29   - mr.closed.should be_true
  29 + mr.closed?.should be_true
30 30 page.should have_content "Closed by"
31 31 end
32 32  
... ... @@ -80,11 +80,10 @@ class ProjectMergeRequests &lt; Spinach::FeatureSteps
80 80  
81 81 And 'project "Shop" have "Feature NS-03" closed merge request' do
82 82 project = Project.find_by_name("Shop")
83   - create(:merge_request,
  83 + create(:closed_merge_request,
84 84 title: "Feature NS-03",
85 85 project: project,
86   - author: project.users.first,
87   - closed: true)
  86 + author: project.users.first)
88 87 end
89 88  
90 89 And 'I switch to the diff tab' do
... ...
lib/api/entities.rb
... ... @@ -35,12 +35,11 @@ module Gitlab
35 35 class Group < Grape::Entity
36 36 expose :id, :name, :path, :owner_id
37 37 end
38   -
  38 +
39 39 class GroupDetail < Group
40 40 expose :projects, using: Entities::Project
41 41 end
42 42  
43   -
44 43 class RepoObject < Grape::Entity
45 44 expose :name, :commit
46 45 expose :protected do |repo, options|
... ... @@ -63,7 +62,7 @@ module Gitlab
63 62 class Milestone < Grape::Entity
64 63 expose :id
65 64 expose (:project_id) {|milestone| milestone.project.id}
66   - expose :title, :description, :due_date, :closed, :updated_at, :created_at
  65 + expose :title, :description, :due_date, :state, :updated_at, :created_at
67 66 end
68 67  
69 68 class Issue < Grape::Entity
... ... @@ -73,7 +72,7 @@ module Gitlab
73 72 expose :label_list, as: :labels
74 73 expose :milestone, using: Entities::Milestone
75 74 expose :assignee, :author, using: Entities::UserBasic
76   - expose :closed, :updated_at, :created_at
  75 + expose :state, :updated_at, :created_at
77 76 end
78 77  
79 78 class SSHKey < Grape::Entity
... ... @@ -81,7 +80,7 @@ module Gitlab
81 80 end
82 81  
83 82 class MergeRequest < Grape::Entity
84   - expose :id, :target_branch, :source_branch, :project_id, :title, :closed, :merged
  83 + expose :id, :target_branch, :source_branch, :project_id, :title, :state
85 84 expose :author, :assignee, using: Entities::UserBasic
86 85 end
87 86  
... ...
lib/api/issues.rb
... ... @@ -69,14 +69,14 @@ module Gitlab
69 69 # assignee_id (optional) - The ID of a user to assign issue
70 70 # milestone_id (optional) - The ID of a milestone to assign issue
71 71 # labels (optional) - The labels of an issue
72   - # closed (optional) - The state of an issue (0 = false, 1 = true)
  72 + # state (optional) - The state of an issue (close|reopen)
73 73 # Example Request:
74 74 # PUT /projects/:id/issues/:issue_id
75 75 put ":id/issues/:issue_id" do
76 76 @issue = user_project.issues.find(params[:issue_id])
77 77 authorize! :modify_issue, @issue
78 78  
79   - attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id, :closed]
  79 + attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id, :state_event]
80 80 attrs[:label_list] = params[:labels] if params[:labels].present?
81 81 IssueObserver.current_user = current_user
82 82 if @issue.update_attributes attrs
... ...
lib/api/merge_requests.rb
... ... @@ -73,12 +73,12 @@ module Gitlab
73 73 # target_branch - The target branch
74 74 # assignee_id - Assignee user ID
75 75 # title - Title of MR
76   - # closed - Status of MR. true - closed
  76 + # state_event - Status of MR. (close|reopen|merge)
77 77 # Example:
78 78 # PUT /projects/:id/merge_request/:merge_request_id
79 79 #
80 80 put ":id/merge_request/:merge_request_id" do
81   - attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :closed]
  81 + attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :state_event]
82 82 merge_request = user_project.merge_requests.find(params[:merge_request_id])
83 83  
84 84 authorize! :modify_merge_request, merge_request
... ...
lib/api/milestones.rb
... ... @@ -59,14 +59,14 @@ module Gitlab
59 59 # title (optional) - The title of a milestone
60 60 # description (optional) - The description of a milestone
61 61 # due_date (optional) - The due date of a milestone
62   - # closed (optional) - The status of the milestone
  62 + # state (optional) - The status of the milestone (close|activate)
63 63 # Example Request:
64 64 # PUT /projects/:id/milestones/:milestone_id
65 65 put ":id/milestones/:milestone_id" do
66 66 authorize! :admin_milestone, user_project
67 67  
68 68 @milestone = user_project.milestones.find(params[:milestone_id])
69   - attrs = attributes_for_keys [:title, :description, :due_date, :closed]
  69 + attrs = attributes_for_keys [:title, :description, :due_date, :state_event]
70 70 if @milestone.update_attributes attrs
71 71 present @milestone, with: Entities::Milestone
72 72 else
... ...
spec/factories.rb
... ... @@ -54,10 +54,15 @@ FactoryGirl.define do
54 54 project
55 55  
56 56 trait :closed do
57   - closed true
  57 + state :closed
  58 + end
  59 +
  60 + trait :reopened do
  61 + state :reopened
58 62 end
59 63  
60 64 factory :closed_issue, traits: [:closed]
  65 + factory :reopened_issue, traits: [:reopened]
61 66 end
62 67  
63 68 factory :merge_request do
... ... @@ -67,10 +72,6 @@ FactoryGirl.define do
67 72 source_branch "master"
68 73 target_branch "stable"
69 74  
70   - trait :closed do
71   - closed true
72   - end
73   -
74 75 # pick 3 commits "at random" (from bcf03b5d~3 to bcf03b5d)
75 76 trait :with_diffs do
76 77 target_branch "master" # pretend bcf03b5d~3
... ... @@ -85,7 +86,16 @@ FactoryGirl.define do
85 86 end
86 87 end
87 88  
  89 + trait :closed do
  90 + state :closed
  91 + end
  92 +
  93 + trait :reopened do
  94 + state :reopened
  95 + end
  96 +
88 97 factory :closed_merge_request, traits: [:closed]
  98 + factory :reopened_merge_request, traits: [:reopened]
89 99 factory :merge_request_with_diffs, traits: [:with_diffs]
90 100 end
91 101  
... ... @@ -159,6 +169,12 @@ FactoryGirl.define do
159 169 factory :milestone do
160 170 title
161 171 project
  172 +
  173 + trait :closed do
  174 + state :closed
  175 + end
  176 +
  177 + factory :closed_milestone, traits: [:closed]
162 178 end
163 179  
164 180 factory :system_hook do
... ...
spec/models/concerns/issuable_spec.rb
... ... @@ -15,7 +15,6 @@ describe Issue, &quot;Issuable&quot; do
15 15 it { should validate_presence_of(:author) }
16 16 it { should validate_presence_of(:title) }
17 17 it { should ensure_length_of(:title).is_at_least(0).is_at_most(255) }
18   - it { should ensure_inclusion_of(:closed).in_array([true, false]) }
19 18 end
20 19  
21 20 describe "Scope" do
... ...
spec/models/issue_spec.rb
... ... @@ -9,7 +9,7 @@
9 9 # project_id :integer
10 10 # created_at :datetime not null
11 11 # updated_at :datetime not null
12   -# closed :boolean default(FALSE), not null
  12 +# state :string default(FALSE), not null
13 13 # position :integer default(0)
14 14 # branch_name :string(255)
15 15 # description :text
... ... @@ -44,34 +44,15 @@ describe Issue do
44 44 end
45 45 end
46 46  
47   - describe '#is_being_closed?' do
48   - it 'returns true if the closed attribute has changed and is now true' do
49   - subject.closed = true
50   - subject.is_being_closed?.should be_true
51   - end
52   - it 'returns false if the closed attribute has changed and is now false' do
53   - issue = create(:closed_issue)
54   - issue.closed = false
55   - issue.is_being_closed?.should be_false
56   - end
57   - it 'returns false if the closed attribute has not changed' do
58   - subject.is_being_closed?.should be_false
59   - end
60   - end
  47 + describe '#is_being_reassigned?' do
  48 + it 'returnes issues assigned to user' do
  49 + user = create :user
61 50  
  51 + 2.times do
  52 + issue = create :issue, assignee: user
  53 + end
62 54  
63   - describe '#is_being_reopened?' do
64   - it 'returns true if the closed attribute has changed and is now false' do
65   - issue = create(:closed_issue)
66   - issue.closed = false
67   - issue.is_being_reopened?.should be_true
68   - end
69   - it 'returns false if the closed attribute has changed and is now true' do
70   - subject.closed = true
71   - subject.is_being_reopened?.should be_false
72   - end
73   - it 'returns false if the closed attribute has not changed' do
74   - subject.is_being_reopened?.should be_false
  55 + Issue.open_for(user).count.should eq 2
75 56 end
76 57 end
77 58 end
... ...
spec/models/merge_request_spec.rb
... ... @@ -15,7 +15,7 @@
15 15 # st_commits :text(2147483647)
16 16 # st_diffs :text(2147483647)
17 17 # merged :boolean default(FALSE), not null
18   -# state :integer default(1), not null
  18 +# merge_status :integer default(1), not null
19 19 # milestone_id :integer
20 20 #
21 21  
... ... @@ -37,6 +37,10 @@ describe MergeRequest do
37 37 end
38 38  
39 39 describe "#mr_and_commit_notes" do
  40 +
  41 + end
  42 +
  43 + describe "#mr_and_commit_notes" do
40 44 let!(:merge_request) { create(:merge_request) }
41 45  
42 46 before do
... ... @@ -62,35 +66,4 @@ describe MergeRequest do
62 66 subject.is_being_reassigned?.should be_false
63 67 end
64 68 end
65   -
66   - describe '#is_being_closed?' do
67   - it 'returns true if the closed attribute has changed and is now true' do
68   - subject.closed = true
69   - subject.is_being_closed?.should be_true
70   - end
71   - it 'returns false if the closed attribute has changed and is now false' do
72   - merge_request = create(:closed_merge_request)
73   - merge_request.closed = false
74   - merge_request.is_being_closed?.should be_false
75   - end
76   - it 'returns false if the closed attribute has not changed' do
77   - subject.is_being_closed?.should be_false
78   - end
79   - end
80   -
81   -
82   - describe '#is_being_reopened?' do
83   - it 'returns true if the closed attribute has changed and is now false' do
84   - merge_request = create(:closed_merge_request)
85   - merge_request.closed = false
86   - merge_request.is_being_reopened?.should be_true
87   - end
88   - it 'returns false if the closed attribute has changed and is now true' do
89   - subject.closed = true
90   - subject.is_being_reopened?.should be_false
91   - end
92   - it 'returns false if the closed attribute has not changed' do
93   - subject.is_being_reopened?.should be_false
94   - end
95   - end
96 69 end
... ...
spec/models/milestone_spec.rb
... ... @@ -7,7 +7,7 @@
7 7 # project_id :integer not null
8 8 # description :text
9 9 # due_date :date
10   -# closed :boolean default(FALSE), not null
  10 +# state :string default(FALSE), not null
11 11 # created_at :datetime not null
12 12 # updated_at :datetime not null
13 13 #
... ... @@ -27,7 +27,6 @@ describe Milestone do
27 27 describe "Validation" do
28 28 it { should validate_presence_of(:title) }
29 29 it { should validate_presence_of(:project) }
30   - it { should ensure_inclusion_of(:closed).in_array([true, false]) }
31 30 end
32 31  
33 32 let(:milestone) { create(:milestone) }
... ... @@ -41,7 +40,7 @@ describe Milestone do
41 40  
42 41 it "should count closed issues" do
43 42 IssueObserver.current_user = issue.author
44   - issue.update_attributes(closed: true)
  43 + issue.close
45 44 milestone.issues << issue
46 45 milestone.percent_complete.should == 100
47 46 end
... ... @@ -96,7 +95,7 @@ describe Milestone do
96 95 describe :items_count do
97 96 before do
98 97 milestone.issues << create(:issue)
99   - milestone.issues << create(:issue, closed: true)
  98 + milestone.issues << create(:closed_issue)
100 99 milestone.merge_requests << create(:merge_request)
101 100 end
102 101  
... ... @@ -110,7 +109,35 @@ describe Milestone do
110 109 it { milestone.can_be_closed?.should be_true }
111 110 end
112 111  
113   - describe :open? do
114   - it { milestone.open?.should be_true }
  112 + describe :is_empty? do
  113 + before do
  114 + issue = create :closed_issue, milestone: milestone
  115 + merge_request = create :merge_request, milestone: milestone
  116 + end
  117 +
  118 + it 'Should return total count of issues and merge requests assigned to milestone' do
  119 + milestone.total_items_count.should eq 2
  120 + end
  121 + end
  122 +
  123 + describe :can_be_closed? do
  124 + before do
  125 + milestone = create :milestone
  126 + create :closed_issue, milestone: milestone
  127 +
  128 + issue = create :issue
  129 + end
  130 +
  131 + it 'should be true if milestone active and all nestied issues closed' do
  132 + milestone.can_be_closed?.should be_true
  133 + end
  134 +
  135 + it 'should be false if milestone active and not all nestied issues closed' do
  136 + issue.milestone = milestone
  137 + issue.save
  138 +
  139 + milestone.can_be_closed?.should be_false
  140 + end
115 141 end
  142 +
116 143 end
... ...
spec/models/project_spec.rb
... ... @@ -121,10 +121,7 @@ describe Project do
121 121 let(:project) { create(:project) }
122 122  
123 123 before do
124   - @merge_request = create(:merge_request,
125   - project: project,
126   - merged: false,
127   - closed: false)
  124 + @merge_request = create(:merge_request, project: project)
128 125 @key = create(:key, user_id: project.owner.id)
129 126 end
130 127  
... ... @@ -133,8 +130,7 @@ describe Project do
133 130 @merge_request.last_commit.id.should == "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a"
134 131 project.update_merge_requests("8716fc78f3c65bbf7bcf7b574febd583bc5d2812", "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a", "refs/heads/stable", @key.user)
135 132 @merge_request.reload
136   - @merge_request.merged.should be_true
137   - @merge_request.closed.should be_true
  133 + @merge_request.merged?.should be_true
138 134 end
139 135  
140 136 it "should update merge request commits with new one if pushed to source branch" do
... ...
spec/observers/issue_observer_spec.rb
1 1 require 'spec_helper'
2 2  
3 3 describe IssueObserver do
4   - let(:some_user) { double(:user, id: 1) }
5   - let(:assignee) { double(:user, id: 2) }
6   - let(:author) { double(:user, id: 3) }
7   - let(:issue) { double(:issue, id: 42, assignee: assignee, author: author) }
  4 + let(:some_user) { create :user }
  5 + let(:assignee) { create :user }
  6 + let(:author) { create :user }
  7 + let(:mock_issue) { double(:issue, id: 42, assignee: assignee, author: author) }
  8 + let(:assigned_issue) { create(:issue, assignee: assignee, author: author) }
  9 + let(:unassigned_issue) { create(:issue, author: author) }
  10 + let(:closed_assigned_issue) { create(:closed_issue, assignee: assignee, author: author) }
  11 + let(:closed_unassigned_issue) { create(:closed_issue, author: author) }
  12 +
8 13  
9 14 before(:each) { subject.stub(:current_user).and_return(some_user) }
10 15  
... ... @@ -21,137 +26,91 @@ describe IssueObserver do
21 26 end
22 27  
23 28 it 'sends an email to the assignee' do
24   - Notify.should_receive(:new_issue_email).with(issue.id)
  29 + Notify.should_receive(:new_issue_email).with(mock_issue.id)
25 30  
26   - subject.after_create(issue)
  31 + subject.after_create(mock_issue)
27 32 end
28 33  
29 34 it 'does not send an email to the assignee if assignee created the issue' do
30 35 subject.stub(:current_user).and_return(assignee)
31 36 Notify.should_not_receive(:new_issue_email)
32 37  
33   - subject.after_create(issue)
  38 + subject.after_create(mock_issue)
34 39 end
35 40 end
36 41  
37   - context '#after_update' do
38   - before(:each) do
39   - issue.stub(:is_being_reassigned?).and_return(false)
40   - issue.stub(:is_being_closed?).and_return(false)
41   - issue.stub(:is_being_reopened?).and_return(false)
42   - end
43   -
44   - it 'is called when an issue is changed' do
45   - changed = create(:issue, project: create(:project))
46   - subject.should_receive(:after_update)
47   -
48   - Issue.observers.enable :issue_observer do
49   - changed.description = 'I changed'
50   - changed.save
51   - end
52   - end
53   -
54   - context 'a reassigned email' do
55   - it 'is sent if the issue is being reassigned' do
56   - issue.should_receive(:is_being_reassigned?).and_return(true)
57   - subject.should_receive(:send_reassigned_email).with(issue)
58   -
59   - subject.after_update(issue)
60   - end
61   -
62   - it 'is not sent if the issue is not being reassigned' do
63   - issue.should_receive(:is_being_reassigned?).and_return(false)
64   - subject.should_not_receive(:send_reassigned_email)
65   -
66   - subject.after_update(issue)
67   - end
68   - end
69   -
  42 + context '#after_close' do
70 43 context 'a status "closed"' do
71 44 it 'note is created if the issue is being closed' do
72   - issue.should_receive(:is_being_closed?).and_return(true)
73   - Notify.should_receive(:issue_status_changed_email).twice
74   - Note.should_receive(:create_status_change_note).with(issue, some_user, 'closed')
75   -
76   - subject.after_update(issue)
77   - end
78   -
79   - it 'note is not created if the issue is not being closed' do
80   - issue.should_receive(:is_being_closed?).and_return(false)
81   - Note.should_not_receive(:create_status_change_note).with(issue, some_user, 'closed')
  45 + Note.should_receive(:create_status_change_note).with(assigned_issue, some_user, 'closed')
82 46  
83   - subject.after_update(issue)
  47 + assigned_issue.close
84 48 end
85 49  
86 50 it 'notification is delivered if the issue being closed' do
87   - issue.stub(:is_being_closed?).and_return(true)
88 51 Notify.should_receive(:issue_status_changed_email).twice
89   - Note.should_receive(:create_status_change_note).with(issue, some_user, 'closed')
90 52  
91   - subject.after_update(issue)
92   - end
93   -
94   - it 'notification is not delivered if the issue not being closed' do
95   - issue.stub(:is_being_closed?).and_return(false)
96   - Notify.should_not_receive(:issue_status_changed_email)
97   - Note.should_not_receive(:create_status_change_note).with(issue, some_user, 'closed')
98   -
99   - subject.after_update(issue)
  53 + assigned_issue.close
100 54 end
101 55  
102 56 it 'notification is delivered only to author if the issue being closed' do
103   - issue_without_assignee = double(:issue, id: 42, author: author, assignee: nil)
104   - issue_without_assignee.stub(:is_being_reassigned?).and_return(false)
105   - issue_without_assignee.stub(:is_being_closed?).and_return(true)
106   - issue_without_assignee.stub(:is_being_reopened?).and_return(false)
107 57 Notify.should_receive(:issue_status_changed_email).once
108   - Note.should_receive(:create_status_change_note).with(issue_without_assignee, some_user, 'closed')
  58 + Note.should_receive(:create_status_change_note).with(unassigned_issue, some_user, 'closed')
109 59  
110   - subject.after_update(issue_without_assignee)
  60 + unassigned_issue.close
111 61 end
112 62 end
113 63  
114 64 context 'a status "reopened"' do
115 65 it 'note is created if the issue is being reopened' do
  66 + Note.should_receive(:create_status_change_note).with(closed_assigned_issue, some_user, 'reopened')
  67 +
  68 + closed_assigned_issue.reopen
  69 + end
  70 +
  71 + it 'notification is delivered if the issue being reopened' do
116 72 Notify.should_receive(:issue_status_changed_email).twice
117   - issue.should_receive(:is_being_reopened?).and_return(true)
118   - Note.should_receive(:create_status_change_note).with(issue, some_user, 'reopened')
119 73  
120   - subject.after_update(issue)
  74 + closed_assigned_issue.reopen
121 75 end
122 76  
123   - it 'note is not created if the issue is not being reopened' do
124   - issue.should_receive(:is_being_reopened?).and_return(false)
125   - Note.should_not_receive(:create_status_change_note).with(issue, some_user, 'reopened')
  77 + it 'notification is delivered only to author if the issue being reopened' do
  78 + Notify.should_receive(:issue_status_changed_email).once
  79 + Note.should_receive(:create_status_change_note).with(closed_unassigned_issue, some_user, 'reopened')
126 80  
127   - subject.after_update(issue)
  81 + closed_unassigned_issue.reopen
128 82 end
  83 + end
  84 + end
129 85  
130   - it 'notification is delivered if the issue being reopened' do
131   - issue.stub(:is_being_reopened?).and_return(true)
132   - Notify.should_receive(:issue_status_changed_email).twice
133   - Note.should_receive(:create_status_change_note).with(issue, some_user, 'reopened')
  86 + context '#after_update' do
  87 + before(:each) do
  88 + mock_issue.stub(:is_being_reassigned?).and_return(false)
  89 + end
  90 +
  91 + it 'is called when an issue is changed' do
  92 + changed = create(:issue, project: create(:project))
  93 + subject.should_receive(:after_update)
134 94  
135   - subject.after_update(issue)
  95 + Issue.observers.enable :issue_observer do
  96 + changed.description = 'I changed'
  97 + changed.save
136 98 end
  99 + end
137 100  
138   - it 'notification is not delivered if the issue not being reopened' do
139   - issue.stub(:is_being_reopened?).and_return(false)
140   - Notify.should_not_receive(:issue_status_changed_email)
141   - Note.should_not_receive(:create_status_change_note).with(issue, some_user, 'reopened')
  101 + context 'a reassigned email' do
  102 + it 'is sent if the issue is being reassigned' do
  103 + mock_issue.should_receive(:is_being_reassigned?).and_return(true)
  104 + subject.should_receive(:send_reassigned_email).with(mock_issue)
142 105  
143   - subject.after_update(issue)
  106 + subject.after_update(mock_issue)
144 107 end
145 108  
146   - it 'notification is delivered only to author if the issue being reopened' do
147   - issue_without_assignee = double(:issue, id: 42, author: author, assignee: nil)
148   - issue_without_assignee.stub(:is_being_reassigned?).and_return(false)
149   - issue_without_assignee.stub(:is_being_closed?).and_return(false)
150   - issue_without_assignee.stub(:is_being_reopened?).and_return(true)
151   - Notify.should_receive(:issue_status_changed_email).once
152   - Note.should_receive(:create_status_change_note).with(issue_without_assignee, some_user, 'reopened')
  109 + it 'is not sent if the issue is not being reassigned' do
  110 + mock_issue.should_receive(:is_being_reassigned?).and_return(false)
  111 + subject.should_not_receive(:send_reassigned_email)
153 112  
154   - subject.after_update(issue_without_assignee)
  113 + subject.after_update(mock_issue)
155 114 end
156 115 end
157 116 end
... ... @@ -160,23 +119,23 @@ describe IssueObserver do
160 119 let(:previous_assignee) { double(:user, id: 3) }
161 120  
162 121 before(:each) do
163   - issue.stub(:assignee_id).and_return(assignee.id)
164   - issue.stub(:assignee_id_was).and_return(previous_assignee.id)
  122 + mock_issue.stub(:assignee_id).and_return(assignee.id)
  123 + mock_issue.stub(:assignee_id_was).and_return(previous_assignee.id)
165 124 end
166 125  
167 126 def it_sends_a_reassigned_email_to(recipient)
168   - Notify.should_receive(:reassigned_issue_email).with(recipient, issue.id, previous_assignee.id)
  127 + Notify.should_receive(:reassigned_issue_email).with(recipient, mock_issue.id, previous_assignee.id)
169 128 end
170 129  
171 130 def it_does_not_send_a_reassigned_email_to(recipient)
172   - Notify.should_not_receive(:reassigned_issue_email).with(recipient, issue.id, previous_assignee.id)
  131 + Notify.should_not_receive(:reassigned_issue_email).with(recipient, mock_issue.id, previous_assignee.id)
173 132 end
174 133  
175 134 it 'sends a reassigned email to the previous and current assignees' do
176 135 it_sends_a_reassigned_email_to assignee.id
177 136 it_sends_a_reassigned_email_to previous_assignee.id
178 137  
179   - subject.send(:send_reassigned_email, issue)
  138 + subject.send(:send_reassigned_email, mock_issue)
180 139 end
181 140  
182 141 context 'does not send an email to the user who made the reassignment' do
... ... @@ -185,14 +144,14 @@ describe IssueObserver do
185 144 it_sends_a_reassigned_email_to previous_assignee.id
186 145 it_does_not_send_a_reassigned_email_to assignee.id
187 146  
188   - subject.send(:send_reassigned_email, issue)
  147 + subject.send(:send_reassigned_email, mock_issue)
189 148 end
190 149 it 'if the user is the previous assignee' do
191 150 subject.stub(:current_user).and_return(previous_assignee)
192 151 it_sends_a_reassigned_email_to assignee.id
193 152 it_does_not_send_a_reassigned_email_to previous_assignee.id
194 153  
195   - subject.send(:send_reassigned_email, issue)
  154 + subject.send(:send_reassigned_email, mock_issue)
196 155 end
197 156 end
198 157 end
... ...
spec/observers/merge_request_observer_spec.rb
1 1 require 'spec_helper'
2 2  
3 3 describe MergeRequestObserver do
4   - let(:some_user) { double(:user, id: 1) }
5   - let(:assignee) { double(:user, id: 2) }
6   - let(:author) { double(:user, id: 3) }
7   - let(:mr) { double(:merge_request, id: 42, assignee: assignee, author: author) }
  4 + let(:some_user) { create :user }
  5 + let(:assignee) { create :user }
  6 + let(:author) { create :user }
  7 + let(:mr_mock) { double(:merge_request, id: 42, assignee: assignee, author: author) }
  8 + let(:assigned_mr) { create(:merge_request, assignee: assignee, author: author) }
  9 + let(:unassigned_mr) { create(:merge_request, author: author) }
  10 + let(:closed_assigned_mr) { create(:closed_merge_request, assignee: assignee, author: author) }
  11 + let(:closed_unassigned_mr) { create(:closed_merge_request, author: author) }
8 12  
9 13 before(:each) { subject.stub(:current_user).and_return(some_user) }
10 14  
... ... @@ -21,23 +25,21 @@ describe MergeRequestObserver do
21 25 end
22 26  
23 27 it 'sends an email to the assignee' do
24   - Notify.should_receive(:new_merge_request_email).with(mr.id)
25   - subject.after_create(mr)
  28 + Notify.should_receive(:new_merge_request_email).with(mr_mock.id)
  29 + subject.after_create(mr_mock)
26 30 end
27 31  
28 32 it 'does not send an email to the assignee if assignee created the merge request' do
29 33 subject.stub(:current_user).and_return(assignee)
30 34 Notify.should_not_receive(:new_merge_request_email)
31 35  
32   - subject.after_create(mr)
  36 + subject.after_create(mr_mock)
33 37 end
34 38 end
35 39  
36 40 context '#after_update' do
37 41 before(:each) do
38   - mr.stub(:is_being_reassigned?).and_return(false)
39   - mr.stub(:is_being_closed?).and_return(false)
40   - mr.stub(:is_being_reopened?).and_return(false)
  42 + mr_mock.stub(:is_being_reassigned?).and_return(false)
41 43 end
42 44  
43 45 it 'is called when a merge request is changed' do
... ... @@ -52,97 +54,50 @@ describe MergeRequestObserver do
52 54  
53 55 context 'a reassigned email' do
54 56 it 'is sent if the merge request is being reassigned' do
55   - mr.should_receive(:is_being_reassigned?).and_return(true)
56   - subject.should_receive(:send_reassigned_email).with(mr)
  57 + mr_mock.should_receive(:is_being_reassigned?).and_return(true)
  58 + subject.should_receive(:send_reassigned_email).with(mr_mock)
57 59  
58   - subject.after_update(mr)
  60 + subject.after_update(mr_mock)
59 61 end
60 62  
61 63 it 'is not sent if the merge request is not being reassigned' do
62   - mr.should_receive(:is_being_reassigned?).and_return(false)
  64 + mr_mock.should_receive(:is_being_reassigned?).and_return(false)
63 65 subject.should_not_receive(:send_reassigned_email)
64 66  
65   - subject.after_update(mr)
  67 + subject.after_update(mr_mock)
66 68 end
67 69 end
68 70  
  71 + end
  72 +
  73 + context '#after_close' do
69 74 context 'a status "closed"' do
70 75 it 'note is created if the merge request is being closed' do
71   - mr.should_receive(:is_being_closed?).and_return(true)
72   - Note.should_receive(:create_status_change_note).with(mr, some_user, 'closed')
73   -
74   - subject.after_update(mr)
75   - end
76   -
77   - it 'note is not created if the merge request is not being closed' do
78   - mr.should_receive(:is_being_closed?).and_return(false)
79   - Note.should_not_receive(:create_status_change_note).with(mr, some_user, 'closed')
80   -
81   - subject.after_update(mr)
82   - end
83   -
84   - it 'notification is delivered if the merge request being closed' do
85   - mr.stub(:is_being_closed?).and_return(true)
86   - Note.should_receive(:create_status_change_note).with(mr, some_user, 'closed')
87   -
88   - subject.after_update(mr)
89   - end
90   -
91   - it 'notification is not delivered if the merge request not being closed' do
92   - mr.stub(:is_being_closed?).and_return(false)
93   - Note.should_not_receive(:create_status_change_note).with(mr, some_user, 'closed')
  76 + Note.should_receive(:create_status_change_note).with(assigned_mr, some_user, 'closed')
94 77  
95   - subject.after_update(mr)
  78 + assigned_mr.close
96 79 end
97 80  
98 81 it 'notification is delivered only to author if the merge request is being closed' do
99   - mr_without_assignee = double(:merge_request, id: 42, author: author, assignee: nil)
100   - mr_without_assignee.stub(:is_being_reassigned?).and_return(false)
101   - mr_without_assignee.stub(:is_being_closed?).and_return(true)
102   - mr_without_assignee.stub(:is_being_reopened?).and_return(false)
103   - Note.should_receive(:create_status_change_note).with(mr_without_assignee, some_user, 'closed')
  82 + Note.should_receive(:create_status_change_note).with(unassigned_mr, some_user, 'closed')
104 83  
105   - subject.after_update(mr_without_assignee)
  84 + unassigned_mr.close
106 85 end
107 86 end
  87 + end
108 88  
  89 + context '#after_reopen' do
109 90 context 'a status "reopened"' do
110 91 it 'note is created if the merge request is being reopened' do
111   - mr.should_receive(:is_being_reopened?).and_return(true)
112   - Note.should_receive(:create_status_change_note).with(mr, some_user, 'reopened')
113   -
114   - subject.after_update(mr)
115   - end
116   -
117   - it 'note is not created if the merge request is not being reopened' do
118   - mr.should_receive(:is_being_reopened?).and_return(false)
119   - Note.should_not_receive(:create_status_change_note).with(mr, some_user, 'reopened')
120   -
121   - subject.after_update(mr)
122   - end
123   -
124   - it 'notification is delivered if the merge request being reopened' do
125   - mr.stub(:is_being_reopened?).and_return(true)
126   - Note.should_receive(:create_status_change_note).with(mr, some_user, 'reopened')
127   -
128   - subject.after_update(mr)
129   - end
130   -
131   - it 'notification is not delivered if the merge request is not being reopened' do
132   - mr.stub(:is_being_reopened?).and_return(false)
133   - Note.should_not_receive(:create_status_change_note).with(mr, some_user, 'reopened')
  92 + Note.should_receive(:create_status_change_note).with(closed_assigned_mr, some_user, 'reopened')
134 93  
135   - subject.after_update(mr)
  94 + closed_assigned_mr.reopen
136 95 end
137 96  
138 97 it 'notification is delivered only to author if the merge request is being reopened' do
139   - mr_without_assignee = double(:merge_request, id: 42, author: author, assignee: nil)
140   - mr_without_assignee.stub(:is_being_reassigned?).and_return(false)
141   - mr_without_assignee.stub(:is_being_closed?).and_return(false)
142   - mr_without_assignee.stub(:is_being_reopened?).and_return(true)
143   - Note.should_receive(:create_status_change_note).with(mr_without_assignee, some_user, 'reopened')
  98 + Note.should_receive(:create_status_change_note).with(closed_unassigned_mr, some_user, 'reopened')
144 99  
145   - subject.after_update(mr_without_assignee)
  100 + closed_unassigned_mr.reopen
146 101 end
147 102 end
148 103 end
... ... @@ -151,23 +106,23 @@ describe MergeRequestObserver do
151 106 let(:previous_assignee) { double(:user, id: 3) }
152 107  
153 108 before(:each) do
154   - mr.stub(:assignee_id).and_return(assignee.id)
155   - mr.stub(:assignee_id_was).and_return(previous_assignee.id)
  109 + mr_mock.stub(:assignee_id).and_return(assignee.id)
  110 + mr_mock.stub(:assignee_id_was).and_return(previous_assignee.id)
156 111 end
157 112  
158 113 def it_sends_a_reassigned_email_to(recipient)
159   - Notify.should_receive(:reassigned_merge_request_email).with(recipient, mr.id, previous_assignee.id)
  114 + Notify.should_receive(:reassigned_merge_request_email).with(recipient, mr_mock.id, previous_assignee.id)
160 115 end
161 116  
162 117 def it_does_not_send_a_reassigned_email_to(recipient)
163   - Notify.should_not_receive(:reassigned_merge_request_email).with(recipient, mr.id, previous_assignee.id)
  118 + Notify.should_not_receive(:reassigned_merge_request_email).with(recipient, mr_mock.id, previous_assignee.id)
164 119 end
165 120  
166 121 it 'sends a reassigned email to the previous and current assignees' do
167 122 it_sends_a_reassigned_email_to assignee.id
168 123 it_sends_a_reassigned_email_to previous_assignee.id
169 124  
170   - subject.send(:send_reassigned_email, mr)
  125 + subject.send(:send_reassigned_email, mr_mock)
171 126 end
172 127  
173 128 context 'does not send an email to the user who made the reassignment' do
... ... @@ -176,14 +131,14 @@ describe MergeRequestObserver do
176 131 it_sends_a_reassigned_email_to previous_assignee.id
177 132 it_does_not_send_a_reassigned_email_to assignee.id
178 133  
179   - subject.send(:send_reassigned_email, mr)
  134 + subject.send(:send_reassigned_email, mr_mock)
180 135 end
181 136 it 'if the user is the previous assignee' do
182 137 subject.stub(:current_user).and_return(previous_assignee)
183 138 it_sends_a_reassigned_email_to assignee.id
184 139 it_does_not_send_a_reassigned_email_to previous_assignee.id
185 140  
186   - subject.send(:send_reassigned_email, mr)
  141 + subject.send(:send_reassigned_email, mr_mock)
187 142 end
188 143 end
189 144 end
... ...
spec/requests/api/issues_spec.rb
... ... @@ -54,14 +54,24 @@ describe Gitlab::API do
54 54 end
55 55 end
56 56  
57   - describe "PUT /projects/:id/issues/:issue_id" do
  57 + describe "PUT /projects/:id/issues/:issue_id to update only title" do
58 58 it "should update a project issue" do
59 59 put api("/projects/#{project.id}/issues/#{issue.id}", user),
60   - title: 'updated title', labels: 'label2', closed: 1
  60 + title: 'updated title'
61 61 response.status.should == 200
  62 +
62 63 json_response['title'].should == 'updated title'
  64 + end
  65 + end
  66 +
  67 + describe "PUT /projects/:id/issues/:issue_id to update state and label" do
  68 + it "should update a project issue" do
  69 + put api("/projects/#{project.id}/issues/#{issue.id}", user),
  70 + labels: 'label2', state_event: "close"
  71 + response.status.should == 200
  72 +
63 73 json_response['labels'].should == ['label2']
64   - json_response['closed'].should be_true
  74 + json_response['state'].should eq "closed"
65 75 end
66 76 end
67 77  
... ...
spec/requests/api/merge_requests_spec.rb
... ... @@ -43,6 +43,23 @@ describe Gitlab::API do
43 43 end
44 44 end
45 45  
  46 + describe "PUT /projects/:id/merge_request/:merge_request_id to close MR" do
  47 + it "should return merge_request" do
  48 + put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), state_event: "close"
  49 + response.status.should == 200
  50 + json_response['state'].should == 'closed'
  51 + end
  52 + end
  53 +
  54 + describe "PUT /projects/:id/merge_request/:merge_request_id to merge MR" do
  55 + it "should return merge_request" do
  56 + put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), state_event: "merge"
  57 + response.status.should == 200
  58 + json_response['state'].should == 'merged'
  59 + end
  60 + end
  61 +
  62 +
46 63 describe "PUT /projects/:id/merge_request/:merge_request_id" do
47 64 it "should return merge_request" do
48 65 put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), title: "New title"
... ...
spec/requests/api/milestones_spec.rb
... ... @@ -44,4 +44,14 @@ describe Gitlab::API do
44 44 json_response['title'].should == 'updated title'
45 45 end
46 46 end
  47 +
  48 + describe "PUT /projects/:id/milestones/:milestone_id to close milestone" do
  49 + it "should update a project milestone" do
  50 + put api("/projects/#{project.id}/milestones/#{milestone.id}", user),
  51 + state_event: 'close'
  52 + response.status.should == 200
  53 +
  54 + json_response['state'].should == 'closed'
  55 + end
  56 + end
47 57 end
... ...
spec/requests/issues_spec.rb
... ... @@ -58,8 +58,7 @@ describe &quot;Issues&quot; do
58 58  
59 59 it "should be able to search on different statuses" do
60 60 issue = Issue.first # with title 'foobar'
61   - issue.closed = true
62   - issue.save
  61 + issue.close
63 62  
64 63 visit project_issues_path(project)
65 64 click_link 'Closed'
... ...