Commit 155703c6 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

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

Conflicts:
	app/models/issue.rb
	app/models/merge_request.rb
parents 52028dcd 6e333d50
......@@ -70,6 +70,9 @@ gem "github-markup", "~> 0.7.4", require: 'github/markup'
# Servers
gem "unicorn", "~> 4.4.0"
# State machine
gem "state_machine"
# Issue tags
gem "acts-as-taggable-on", "2.3.3"
......
......@@ -425,6 +425,7 @@ GEM
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
stamp (0.3.0)
state_machine (1.1.2)
temple (0.5.5)
test_after_commit (0.0.1)
therubyracer (0.10.2)
......@@ -536,6 +537,7 @@ DEPENDENCIES
slim
spinach-rails
stamp
state_machine
test_after_commit
therubyracer
thin
......
......@@ -27,7 +27,7 @@ class MergeRequest
this.$el.find(selector)
initMergeWidget: ->
this.showState( @opts.current_state )
this.showState( @opts.current_status )
if this.$('.automerge_widget').length and @opts.check_enable
$.get @opts.url_to_automerge_check, (data) =>
......
......@@ -14,7 +14,7 @@ class MergeRequestsLoadContext < BaseContext
end
merge_requests = merge_requests.page(params[:page]).per(20)
merge_requests = merge_requests.includes(:author, :project).order("closed, created_at desc")
merge_requests = merge_requests.includes(:author, :project).order("state, created_at desc")
# Filter by specific assignee_id (or lack thereof)?
if params[:assignee_id].present?
......
......@@ -73,14 +73,14 @@ class MergeRequestsController < ProjectResourceController
if @merge_request.unchecked?
@merge_request.check_if_can_be_merged
end
render json: {state: @merge_request.human_state}
render json: {merge_status: @merge_request.human_merge_status}
rescue Gitlab::SatelliteNotExistError
render json: {state: :no_satellite}
render json: {merge_status: :no_satellite}
end
def automerge
return access_denied! unless can?(current_user, :accept_mr, @project)
if @merge_request.open? && @merge_request.can_be_merged?
if @merge_request.opened? && @merge_request.can_be_merged?
@merge_request.should_remove_source_branch = params[:should_remove_source_branch]
@merge_request.automerge!(current_user)
@status = true
......
......@@ -12,7 +12,7 @@ class MilestonesController < ProjectResourceController
def index
@milestones = case params[:f]
when 'all'; @project.milestones.order("closed, due_date DESC")
when 'all'; @project.milestones.order("state, due_date DESC")
when 'closed'; @project.milestones.closed.order("due_date DESC")
else @project.milestones.active.order("due_date ASC")
end
......
......@@ -6,7 +6,7 @@ module IssuesHelper
def issue_css_classes issue
classes = "issue"
classes << " closed" if issue.closed
classes << " closed" if issue.closed?
classes << " today" if issue.today?
classes
end
......
......@@ -12,7 +12,7 @@ module MergeRequestsHelper
def mr_css_classes mr
classes = "merge_request"
classes << " closed" if mr.closed
classes << " closed" if mr.closed?
classes << " merged" if mr.merged?
classes
end
......
......@@ -17,10 +17,9 @@ module Issuable
validates :project, presence: true
validates :author, presence: true
validates :title, presence: true, length: { within: 0..255 }
validates :closed, inclusion: { in: [true, false] }
scope :opened, -> { where(closed: false) }
scope :closed, -> { where(closed: true) }
scope :opened, -> { with_state(:opened) }
scope :closed, -> { with_state(:closed) }
scope :of_group, ->(group) { where(project_id: group.project_ids) }
scope :of_user_team, ->(team) { where(project_id: team.project_ids, assignee_id: team.member_ids) }
scope :assigned, ->(u) { where(assignee_id: u.id)}
......@@ -62,14 +61,6 @@ module Issuable
assignee_id_changed?
end
def is_being_closed?
closed_changed? && closed
end
def is_being_reopened?
closed_changed? && !closed
end
#
# Votes
#
......
......@@ -9,7 +9,7 @@
# project_id :integer
# created_at :datetime not null
# updated_at :datetime not null
# closed :boolean default(FALSE), not null
# state :string default(FALSE), not null
# position :integer default(0)
# branch_name :string(255)
# description :text
......@@ -19,8 +19,9 @@
class Issue < ActiveRecord::Base
include Issuable
attr_accessible :title, :assignee_id, :closed, :position, :description,
:milestone_id, :label_list, :author_id_of_changes
attr_accessible :title, :assignee_id, :position, :description,
:milestone_id, :label_list, :author_id_of_changes,
:state_event
acts_as_taggable_on :labels
......@@ -33,4 +34,20 @@ class Issue < ActiveRecord::Base
opened.assigned(user)
end
end
state_machine :state, initial: :opened do
event :close do
transition [:reopened, :opened] => :closed
end
event :reopen do
transition closed: :reopened
end
state :opened
state :reopened
state :closed
end
end
......@@ -9,15 +9,14 @@
# author_id :integer
# assignee_id :integer
# title :string(255)
# closed :boolean default(FALSE), not null
# state :string(255) not null
# created_at :datetime not null
# updated_at :datetime not null
# st_commits :text(2147483647)
# st_diffs :text(2147483647)
# merged :boolean default(FALSE), not null
# state :integer default(1), not null
# milestone_id :integer
# merge_status :integer default(1), not null
#
# milestone_id :integer
require Rails.root.join("app/models/commit")
require Rails.root.join("lib/static_model")
......@@ -25,11 +24,33 @@ require Rails.root.join("lib/static_model")
class MergeRequest < ActiveRecord::Base
include Issuable
attr_accessible :title, :assignee_id, :closed, :target_branch, :source_branch, :milestone_id,
:author_id_of_changes
attr_accessible :title, :assignee_id, :target_branch, :source_branch, :milestone_id,
:author_id_of_changes, :state_event
attr_accessor :should_remove_source_branch
state_machine :state, initial: :opened do
event :close do
transition [:reopened, :opened] => :closed
end
event :merge do
transition [:reopened, :opened] => :merged
end
event :reopen do
transition closed: :reopened
end
state :opened
state :reopened
state :closed
state :merged
end
BROKEN_DIFF = "--broken-diff"
UNCHECKED = 1
......@@ -43,8 +64,13 @@ class MergeRequest < ActiveRecord::Base
validates :target_branch, presence: true
validate :validate_branches
scope :merged, -> { with_state(:merged) }
class << self
def find_all_by_branch(branch_name)
where("source_branch LIKE :branch OR target_branch LIKE :branch", branch: branch_name)
end
def cared(user)
where('assignee_id = :user OR author_id = :user', user: user.id)
end
......@@ -58,13 +84,13 @@ class MergeRequest < ActiveRecord::Base
end
end
def human_state
states = {
def human_merge_status
merge_statuses = {
CAN_BE_MERGED => "can_be_merged",
CANNOT_BE_MERGED => "cannot_be_merged",
UNCHECKED => "unchecked"
}
states[self.state]
merge_statuses[self.merge_status]
end
def validate_branches
......@@ -79,20 +105,20 @@ class MergeRequest < ActiveRecord::Base
end
def unchecked?
state == UNCHECKED
merge_status == UNCHECKED
end
def mark_as_unchecked
self.state = UNCHECKED
self.merge_status = UNCHECKED
self.save
end
def can_be_merged?
state == CAN_BE_MERGED
merge_status == CAN_BE_MERGED
end
def check_if_can_be_merged
self.state = if Gitlab::Satellite::MergeAction.new(self.author, self).can_be_merged?
self.merge_status = if Gitlab::Satellite::MergeAction.new(self.author, self).can_be_merged?
CAN_BE_MERGED
else
CANNOT_BE_MERGED
......@@ -105,7 +131,7 @@ class MergeRequest < ActiveRecord::Base
end
def reloaded_diffs
if open? && unmerged_diffs.any?
if opened? && unmerged_diffs.any?
self.st_diffs = unmerged_diffs
self.save
end
......@@ -135,10 +161,6 @@ class MergeRequest < ActiveRecord::Base
commits.first
end
def merged?
merged && merge_event
end
def merge_event
self.project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::MERGED).last
end
......@@ -153,26 +175,16 @@ class MergeRequest < ActiveRecord::Base
def probably_merged?
unmerged_commits.empty? &&
commits.any? && open?
end
def open?
!closed
end
def mark_as_merged!
self.merged = true
self.closed = true
save
commits.any? && opened?
end
def mark_as_unmergable
self.state = CANNOT_BE_MERGED
self.merge_status = CANNOT_BE_MERGED
self.save
end
def reloaded_commits
if open? && unmerged_commits.any?
if opened? && unmerged_commits.any?
self.st_commits = unmerged_commits
save
end
......@@ -188,7 +200,8 @@ class MergeRequest < ActiveRecord::Base
end
def merge!(user_id)
self.mark_as_merged!
self.merge
Event.create(
project: self.project,
action: Event::MERGED,
......
......@@ -13,19 +13,32 @@
#
class Milestone < ActiveRecord::Base
attr_accessible :title, :description, :due_date, :closed, :author_id_of_changes
attr_accessible :title, :description, :due_date, :state_event, :author_id_of_changes
attr_accessor :author_id_of_changes
belongs_to :project
has_many :issues
has_many :merge_requests
scope :active, -> { where(closed: false) }
scope :closed, -> { where(closed: true) }
scope :active, -> { with_state(:active) }
scope :closed, -> { with_state(:closed) }
validates :title, presence: true
validates :project, presence: true
validates :closed, inclusion: { in: [true, false] }
state_machine :state, initial: :active do
event :close do
transition active: :closed
end
event :activate do
transition closed: :active
end
state :closed
state :active
end
def expired?
if due_date
......@@ -68,17 +81,13 @@ class Milestone < ActiveRecord::Base
end
def can_be_closed?
open? && issues.opened.count.zero?
active? && issues.opened.count.zero?
end
def is_empty?
total_items_count.zero?
end
def open?
!closed
end
def author_id
author_id_of_changes
end
......
......@@ -43,7 +43,7 @@ class Project < ActiveRecord::Base
has_many :events, dependent: :destroy
has_many :merge_requests, dependent: :destroy
has_many :issues, dependent: :destroy, order: "closed, created_at DESC"
has_many :issues, dependent: :destroy, order: "state, created_at DESC"
has_many :milestones, dependent: :destroy
has_many :users_projects, dependent: :destroy
has_many :notes, dependent: :destroy
......
......@@ -20,15 +20,23 @@ class ActivityObserver < ActiveRecord::Observer
end
end
def after_save(record)
if record.changed.include?("closed") && record.author_id_of_changes
def after_close(record, transition)
Event.create(
project: record.project,
target_id: record.id,
target_type: record.class.name,
action: (record.closed ? Event::CLOSED : Event::REOPENED),
action: Event::CLOSED,
author_id: record.author_id_of_changes
)
end
def after_reopen(record, transition)
Event.create(
project: record.project,
target_id: record.id,
target_type: record.class.name,
action: Event::REOPENED,
author_id: record.author_id_of_changes
)
end
end
......@@ -7,22 +7,31 @@ class IssueObserver < ActiveRecord::Observer
end
end
def after_update(issue)
def after_close(issue, transition)
send_reassigned_email(issue) if issue.is_being_reassigned?
status = nil
status = 'closed' if issue.is_being_closed?
status = 'reopened' if issue.is_being_reopened?
if status
Note.create_status_change_note(issue, current_user, status)
[issue.author, issue.assignee].compact.each do |recipient|
Notify.delay.issue_status_changed_email(recipient.id, issue.id, status, current_user.id)
create_note(issue)
end
def after_reopen(issue, transition)
send_reassigned_email(issue) if issue.is_being_reassigned?
create_note(issue)
end
def after_update(issue)
send_reassigned_email(issue) if issue.is_being_reassigned?
end
protected
def create_note(issue)
Note.create_status_change_note(issue, current_user, issue.state)
[issue.author, issue.assignee].compact.each do |recipient|
Notify.delay.issue_status_changed_email(recipient.id, issue.id, issue.state, current_user.id)
end
end
def send_reassigned_email(issue)
recipient_ids = [issue.assignee_id, issue.assignee_id_was].keep_if {|id| id && id != current_user.id }
......
......@@ -7,15 +7,20 @@ class MergeRequestObserver < ActiveRecord::Observer
end
end
def after_update(merge_request)
def after_close(merge_request, transition)
send_reassigned_email(merge_request) if merge_request.is_being_reassigned?
Note.create_status_change_note(merge_request, current_user, merge_request.state)
end
def after_reopen(merge_request, transition)
send_reassigned_email(merge_request) if merge_request.is_being_reassigned?
status = nil
status = 'closed' if merge_request.is_being_closed?
status = 'reopened' if merge_request.is_being_reopened?
if status
Note.create_status_change_note(merge_request, current_user, status)
Note.create_status_change_note(merge_request, current_user, merge_request.state)
end
def after_update(merge_request)
send_reassigned_email(merge_request) if merge_request.is_being_reassigned?
end
protected
......
......@@ -8,10 +8,10 @@
%i.icon-comment
= issue.notes.count
- if can? current_user, :modify_issue, issue
- if issue.closed
= 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
- if issue.closed?
= 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
- else
= 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
= 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
= link_to edit_project_issue_path(issue.project, issue), class: "btn btn-small edit-issue-link grouped" do
%i.icon-edit
Edit
......
......@@ -7,10 +7,10 @@
%span.pull-right
- if can?(current_user, :admin_project, @project) || @issue.author == current_user
- if @issue.closed
= link_to 'Reopen', project_issue_path(@project, @issue, issue: {closed: false }, status_only: true), method: :put, class: "btn grouped reopen_issue"
- if @issue.closed?
= link_to 'Reopen', project_issue_path(@project, @issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn grouped reopen_issue"
- else
= link_to 'Close', project_issue_path(@project, @issue, issue: {closed: true }, status_only: true), method: :put, class: "btn grouped close_issue", title: "Close Issue"
= 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"
- if can?(current_user, :admin_project, @project) || @issue.author == current_user
= link_to edit_project_issue_path(@project, @issue), class: "btn grouped" do
%i.icon-edit
......@@ -27,7 +27,7 @@
.ui-box.ui-box-show
.ui-box-head
%h4.box-title
- if @issue.closed
- if @issue.closed?
.error.status_info Closed
= gfm escape_once(@issue.title)
......
......@@ -29,10 +29,10 @@
$(function(){
merge_request = new MergeRequest({
url_to_automerge_check: "#{automerge_check_project_merge_request_path(@project, @merge_request)}",
check_enable: #{@merge_request.state == MergeRequest::UNCHECKED ? "true" : "false"},
check_enable: #{@merge_request.merge_status == MergeRequest::UNCHECKED ? "true" : "false"},
url_to_ci_check: "#{ci_status_project_merge_request_path(@project, @merge_request)}",
ci_enable: #{@project.gitlab_ci? ? "true" : "false"},
current_state: "#{@merge_request.human_state}",
current_status: "#{@merge_request.human_merge_status}",
action: "#{controller.action_name}"
});
});
......
......@@ -3,7 +3,7 @@
%strong Only masters can accept MR
- if @merge_request.open? && @commits.any? && can?(current_user, :accept_mr, @project)
- if @merge_request.opened? && @commits.any? && can?(current_user, :accept_mr, @project)
.automerge_widget.can_be_merged{style: "display:none"}
.alert.alert-success
%span
......
.ui-box.ui-box-show
.ui-box-head
%h4.box-title
- if @merge_request.merged
- if @merge_request.merged?
.error.status_info
%i.icon-ok
Merged
- elsif @merge_request.closed
- elsif @merge_request.closed?
.error.status_info Closed
= gfm escape_once(@merge_request.title)
......@@ -21,14 +21,14 @@
%strong= link_to_gfm truncate(milestone.title, length: 20), project_milestone_path(milestone.project, milestone)
- if @merge_request.closed
- if @merge_request.closed?
.ui-box-bottom
%span
Closed by #{link_to_member(@project, @merge_request.closed_event.author)}
%small #{time_ago_in_words(@merge_request.closed_event.created_at)} ago.
- if @merge_request.merged?
.ui-box-bottom
%span
Merged by #{link_to_member(@project, @merge_request.merge_event.author)}
%small #{time_ago_in_words(@merge_request.merge_event.created_at)} ago.
- elsif @merge_request.closed_event
%span
Closed by #{link_to_member(@project, @merge_request.closed_event.author)}
%small #{time_ago_in_words(@merge_request.closed_event.created_at)} ago.
- if @merge_request.open? && @commits.any?
- if @merge_request.opened? && @commits.any?
.ci_widget.ci-success{style: "display:none"}
.alert.alert-success
%i.icon-ok
......
......@@ -7,7 +7,7 @@
%span.pull-right
- if can?(current_user, :modify_merge_request, @merge_request)
- if @merge_request.open?
- if @merge_request.opened?
.left.btn-group
%a.btn.grouped.dropdown-toggle{ data: {toggle: :dropdown} }
%i.icon-download-alt
......@@ -17,7 +17,7 @@
%li= link_to "Email Patches", project_merge_request_path(@project, @merge_request, format: :patch)
%li= link_to "Plain Diff", project_merge_request_path(@project, @merge_request, format: :diff)
= 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"
= 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"
= link_to edit_project_merge_request_path(@project, @merge_request), class: "btn grouped" do
%i.icon-edit
......
%li{class: "milestone milestone-#{milestone.closed ? 'closed' : 'open'}", id: dom_id(milestone) }
%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone) }
.pull-right
- if can?(current_user, :admin_milestone, milestone.project) and milestone.open?
- if can?(current_user, :admin_milestone, milestone.project) and milestone.active?
= link_to edit_project_milestone_path(milestone.project, milestone), class: "btn btn-small edit-milestone-link grouped" do
%i.icon-edit
Edit
%h4
= link_to_gfm truncate(milestone.title, length: 100), project_milestone_path(milestone.project, milestone)
- if milestone.expired? and not milestone.closed
- if milestone.expired? and not milestone.closed?
%span.cred (Expired)
%small
= milestone.expires_at
......
......@@ -9,7 +9,7 @@
&larr; To milestones list
.span6
.pull-right
- unless @milestone.closed
- unless @milestone.closed?
= link_to new_project_issue_path(@project, issue: { milestone_id: @milestone.id }), class: "btn btn-small grouped", title: "New Issue" do
%i.icon-plus
New Issue
......@@ -25,12 +25,12 @@
%hr
%p
%span All issues for this milestone are closed. You may close milestone now.
= link_to 'Close Milestone', project_milestone_path(@project, @milestone, milestone: {closed: true }), method: :put, class: "btn btn-small btn-remove"
= link_to 'Close Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-small btn-remove"
.ui-box.ui-box-show
.ui-box-head
%h4.box-title
- if @milestone.closed
- if @milestone.closed?
.error.status_info Closed
- elsif @milestone.expired?
.error.status_info Expired
......@@ -63,7 +63,7 @@
%li=link_to('All Issues', '#')
%ul.well-list
- @issues.each do |issue|
%li{data: {closed: issue.closed}}
%li{data: {closed: issue.closed?}}
= link_to [@project, issue] do
%span.badge.badge-info ##{issue.id}
&ndash;
......@@ -77,7 +77,7 @@
%li=link_to('All Merge Requests', '#')
%ul.well-list
- @merge_requests.each do |merge_request|
%li{data: {closed: merge_request.closed}}
%li{data: {closed: merge_request.closed?}}
= link_to [@project, merge_request] do
%span.badge.badge-info ##{merge_request.id}
&ndash;
......
class RenameStateToMergeStatusInMilestone < ActiveRecord::Migration
def change
rename_column :merge_requests, :state, :merge_status
end
end
class AddStateToIssue < ActiveRecord::Migration
def change
add_column :issues, :state, :string
end
end
class AddStateToMergeRequest < ActiveRecord::Migration
def change
add_column :merge_requests, :state, :string
end
end
class AddStateToMilestone < ActiveRecord::Migration
def change
add_column :milestones, :state, :string
end
end
class ConvertClosedToStateInIssue < ActiveRecord::Migration
def up
Issue.transaction do
Issue.where(closed: true).update_all("state = 'closed'")
Issue.where(closed: false).update_all("state = 'opened'")
end
end
def down
Issue.transaction do
Issue.where(state: :closed).update_all("closed = 1")
end
end
end
class ConvertClosedToStateInMergeRequest < ActiveRecord::Migration
def up
MergeRequest.transaction do
MergeRequest.where("closed = 1 AND merged = 1").update_all("state = 'merged'")
MergeRequest.where("closed = 1 AND merged = 0").update_all("state = 'closed'")
MergeRequest.where("closed = 0").update_all("state = 'opened'")
end
end
def down
MergeRequest.transaction do
MergeRequest.where(state: :closed).update_all("closed = 1")
MergeRequest.where(state: :merged).update_all("closed = 1, merged = 1")
end
end
end
class ConvertClosedToStateInMilestone < ActiveRecord::Migration
def up
Milestone.transaction do
Milestone.where(closed: false).update_all("state = 'opened'")
Milestone.where(closed: false).update_all("state = 'active'")
end
end
def down
Milestone.transaction do
Milestone.where(state: :closed).update_all("closed = 1")
end
end
end
class RemoveMergedFromMergeRequest < ActiveRecord::Migration
def up
remove_column :merge_requests, :merged
end
def down
add_column :merge_requests, :merged, :boolean, default: true, null: false
end
end
class RemoveClosedFromIssue < ActiveRecord::Migration
def up
remove_column :issues, :closed
end
def down
add_column :issues, :closed, :boolean
end
end
class RemoveClosedFromMergeRequest < ActiveRecord::Migration
def up
remove_column :merge_requests, :closed
end
def down
add_column :merge_requests, :closed, :boolean
end
end
class RemoveClosedFromMilestone < ActiveRecord::Migration
def up
remove_column :milestones, :closed
end
def down
add_column :milestones, :closed, :boolean
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20130131070232) do
ActiveRecord::Schema.define(:version => 20130218141554) do
create_table "events", :force => true do |t|
t.string "target_type"
......@@ -39,16 +39,15 @@ ActiveRecord::Schema.define(:version => 20130131070232) do
t.integer "project_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.boolean "closed", :default => false, :null => false
t.integer "position", :default => 0
t.string "branch_name"
t.text "description"
t.integer "milestone_id"
t.string "state"
end
add_index "issues", ["assignee_id"], :name => "index_issues_on_assignee_id"
add_index "issues", ["author_id"], :name => "index_issues_on_author_id"
add_index "issues", ["closed"], :name => "index_issues_on_closed"
add_index "issues", ["created_at"], :name => "index_issues_on_created_at"
add_index "issues", ["milestone_id"], :name => "index_issues_on_milestone_id"
add_index "issues", ["project_id"], :name => "index_issues_on_project_id"
......@@ -75,19 +74,17 @@ ActiveRecord::Schema.define(:version => 20130131070232) do
t.integer "author_id"
t.integer "assignee_id"
t.string "title"
t.boolean "closed", :default => false, :null => false
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.text "st_commits", :limit => 2147483647
t.text "st_diffs", :limit => 2147483647
t.boolean "merged", :default => false, :null => false
t.integer "state", :default => 1, :null => false
t.integer "merge_status", :default => 1, :null => false
t.integer "milestone_id"
t.string "state"
end
add_index "merge_requests", ["assignee_id"], :name => "index_merge_requests_on_assignee_id"
add_index "merge_requests", ["author_id"], :name => "index_merge_requests_on_author_id"
add_index "merge_requests", ["closed"], :name => "index_merge_requests_on_closed"
add_index "merge_requests", ["created_at"], :name => "index_merge_requests_on_created_at"
add_index "merge_requests", ["milestone_id"], :name => "index_merge_requests_on_milestone_id"
add_index "merge_requests", ["project_id"], :name => "index_merge_requests_on_project_id"
......@@ -100,9 +97,9 @@ ActiveRecord::Schema.define(:version => 20130131070232) do
t.integer "project_id", :null => false
t.text "description"
t.date "due_date"
t.boolean "closed", :default => false, :null => false
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.string "state"
end
add_index "milestones", ["due_date"], :name => "index_milestones_on_due_date"
......
......@@ -122,10 +122,9 @@ class ProjectIssues < Spinach::FeatureSteps
And 'project "Shop" have "Release 0.3" closed issue' do
project = Project.find_by_name("Shop")
create(:issue,
create(:closed_issue,
:title => "Release 0.3",
:project => project,
:author => project.users.first,
:closed => true)
:author => project.users.first)
end
end
......@@ -26,7 +26,7 @@ class ProjectMergeRequests < Spinach::FeatureSteps
Then 'I should see closed merge request "Bug NS-04"' do
mr = MergeRequest.find_by_title("Bug NS-04")
mr.closed.should be_true
mr.closed?.should be_true
page.should have_content "Closed by"
end
......@@ -80,11 +80,10 @@ class ProjectMergeRequests < Spinach::FeatureSteps
And 'project "Shop" have "Feature NS-03" closed merge request' do
project = Project.find_by_name("Shop")
create(:merge_request,
create(:closed_merge_request,
title: "Feature NS-03",
project: project,
author: project.users.first,
closed: true)
author: project.users.first)
end
And 'I switch to the diff tab' do
......
......@@ -40,7 +40,6 @@ module Gitlab
expose :projects, using: Entities::Project
end
class RepoObject < Grape::Entity
expose :name, :commit
expose :protected do |repo, options|
......@@ -63,7 +62,7 @@ module Gitlab
class Milestone < Grape::Entity
expose :id
expose (:project_id) {|milestone| milestone.project.id}
expose :title, :description, :due_date, :closed, :updated_at, :created_at
expose :title, :description, :due_date, :state, :updated_at, :created_at
end
class Issue < Grape::Entity
......@@ -73,7 +72,7 @@ module Gitlab
expose :label_list, as: :labels
expose :milestone, using: Entities::Milestone
expose :assignee, :author, using: Entities::UserBasic
expose :closed, :updated_at, :created_at
expose :state, :updated_at, :created_at
end
class SSHKey < Grape::Entity
......@@ -81,7 +80,7 @@ module Gitlab
end
class MergeRequest < Grape::Entity
expose :id, :target_branch, :source_branch, :project_id, :title, :closed, :merged
expose :id, :target_branch, :source_branch, :project_id, :title, :state
expose :author, :assignee, using: Entities::UserBasic
end
......
......@@ -69,14 +69,14 @@ module Gitlab
# assignee_id (optional) - The ID of a user to assign issue
# milestone_id (optional) - The ID of a milestone to assign issue
# labels (optional) - The labels of an issue
# closed (optional) - The state of an issue (0 = false, 1 = true)
# state (optional) - The state of an issue (close|reopen)
# Example Request:
# PUT /projects/:id/issues/:issue_id
put ":id/issues/:issue_id" do
@issue = user_project.issues.find(params[:issue_id])
authorize! :modify_issue, @issue
attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id, :closed]
attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id, :state_event]
attrs[:label_list] = params[:labels] if params[:labels].present?
IssueObserver.current_user = current_user
if @issue.update_attributes attrs
......
......@@ -73,12 +73,12 @@ module Gitlab
# target_branch - The target branch
# assignee_id - Assignee user ID
# title - Title of MR
# closed - Status of MR. true - closed
# state_event - Status of MR. (close|reopen|merge)
# Example:
# PUT /projects/:id/merge_request/:merge_request_id
#
put ":id/merge_request/:merge_request_id" do
attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :closed]
attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :state_event]
merge_request = user_project.merge_requests.find(params[:merge_request_id])
authorize! :modify_merge_request, merge_request
......
......@@ -59,14 +59,14 @@ module Gitlab
# title (optional) - The title of a milestone
# description (optional) - The description of a milestone
# due_date (optional) - The due date of a milestone
# closed (optional) - The status of the milestone
# state (optional) - The status of the milestone (close|activate)
# Example Request:
# PUT /projects/:id/milestones/:milestone_id
put ":id/milestones/:milestone_id" do
authorize! :admin_milestone, user_project
@milestone = user_project.milestones.find(params[:milestone_id])
attrs = attributes_for_keys [:title, :description, :due_date, :closed]
attrs = attributes_for_keys [:title, :description, :due_date, :state_event]
if @milestone.update_attributes attrs
present @milestone, with: Entities::Milestone
else
......
......@@ -54,10 +54,15 @@ FactoryGirl.define do
project
trait :closed do
closed true
state :closed
end
trait :reopened do
state :reopened
end
factory :closed_issue, traits: [:closed]
factory :reopened_issue, traits: [:reopened]
end
factory :merge_request do
......@@ -67,10 +72,6 @@ FactoryGirl.define do
source_branch "master"
target_branch "stable"
trait :closed do
closed true
end
# pick 3 commits "at random" (from bcf03b5d~3 to bcf03b5d)
trait :with_diffs do
target_branch "master" # pretend bcf03b5d~3
......@@ -85,7 +86,16 @@ FactoryGirl.define do
end
end
trait :closed do
state :closed
end
trait :reopened do
state :reopened
end
factory :closed_merge_request, traits: [:closed]
factory :reopened_merge_request, traits: [:reopened]
factory :merge_request_with_diffs, traits: [:with_diffs]
end
......@@ -159,6 +169,12 @@ FactoryGirl.define do
factory :milestone do
title
project
trait :closed do
state :closed
end
factory :closed_milestone, traits: [:closed]
end
factory :system_hook do
......
......@@ -15,7 +15,6 @@ describe Issue, "Issuable" do
it { should validate_presence_of(:author) }
it { should validate_presence_of(:title) }
it { should ensure_length_of(:title).is_at_least(0).is_at_most(255) }
it { should ensure_inclusion_of(:closed).in_array([true, false]) }
end
describe "Scope" do
......
......@@ -9,7 +9,7 @@
# project_id :integer
# created_at :datetime not null
# updated_at :datetime not null
# closed :boolean default(FALSE), not null
# state :string default(FALSE), not null
# position :integer default(0)
# branch_name :string(255)
# description :text
......@@ -44,34 +44,15 @@ describe Issue do
end
end
describe '#is_being_closed?' do
it 'returns true if the closed attribute has changed and is now true' do
subject.closed = true
subject.is_being_closed?.should be_true
end
it 'returns false if the closed attribute has changed and is now false' do
issue = create(:closed_issue)
issue.closed = false
issue.is_being_closed?.should be_false
end
it 'returns false if the closed attribute has not changed' do
subject.is_being_closed?.should be_false
end
end
describe '#is_being_reassigned?' do
it 'returnes issues assigned to user' do
user = create :user
describe '#is_being_reopened?' do
it 'returns true if the closed attribute has changed and is now false' do
issue = create(:closed_issue)
issue.closed = false
issue.is_being_reopened?.should be_true
2.times do
issue = create :issue, assignee: user
end
it 'returns false if the closed attribute has changed and is now true' do
subject.closed = true
subject.is_being_reopened?.should be_false
end
it 'returns false if the closed attribute has not changed' do
subject.is_being_reopened?.should be_false
Issue.open_for(user).count.should eq 2
end
end
end
......@@ -15,7 +15,7 @@
# st_commits :text(2147483647)
# st_diffs :text(2147483647)
# merged :boolean default(FALSE), not null
# state :integer default(1), not null
# merge_status :integer default(1), not null
# milestone_id :integer
#
......@@ -36,6 +36,10 @@ describe MergeRequest do
it { should include_module(Issuable) }
end
describe "#mr_and_commit_notes" do
end
describe "#mr_and_commit_notes" do
let!(:merge_request) { create(:merge_request) }
......@@ -62,35 +66,4 @@ describe MergeRequest do
subject.is_being_reassigned?.should be_false
end
end
describe '#is_being_closed?' do
it 'returns true if the closed attribute has changed and is now true' do
subject.closed = true
subject.is_being_closed?.should be_true
end
it 'returns false if the closed attribute has changed and is now false' do
merge_request = create(:closed_merge_request)
merge_request.closed = false
merge_request.is_being_closed?.should be_false
end
it 'returns false if the closed attribute has not changed' do
subject.is_being_closed?.should be_false
end
end
describe '#is_being_reopened?' do
it 'returns true if the closed attribute has changed and is now false' do
merge_request = create(:closed_merge_request)
merge_request.closed = false
merge_request.is_being_reopened?.should be_true
end
it 'returns false if the closed attribute has changed and is now true' do
subject.closed = true
subject.is_being_reopened?.should be_false
end
it 'returns false if the closed attribute has not changed' do
subject.is_being_reopened?.should be_false
end
end
end
......@@ -7,7 +7,7 @@
# project_id :integer not null
# description :text
# due_date :date
# closed :boolean default(FALSE), not null
# state :string default(FALSE), not null
# created_at :datetime not null
# updated_at :datetime not null
#
......@@ -27,7 +27,6 @@ describe Milestone do
describe "Validation" do
it { should validate_presence_of(:title) }
it { should validate_presence_of(:project) }
it { should ensure_inclusion_of(:closed).in_array([true, false]) }
end
let(:milestone) { create(:milestone) }
......@@ -41,7 +40,7 @@ describe Milestone do
it "should count closed issues" do
IssueObserver.current_user = issue.author
issue.update_attributes(closed: true)
issue.close
milestone.issues << issue
milestone.percent_complete.should == 100
end
......@@ -96,7 +95,7 @@ describe Milestone do
describe :items_count do
before do
milestone.issues << create(:issue)
milestone.issues << create(:issue, closed: true)
milestone.issues << create(:closed_issue)
milestone.merge_requests << create(:merge_request)
end
......@@ -110,7 +109,35 @@ describe Milestone do
it { milestone.can_be_closed?.should be_true }
end
describe :open? do
it { milestone.open?.should be_true }
describe :is_empty? do
before do
issue = create :closed_issue, milestone: milestone
merge_request = create :merge_request, milestone: milestone
end
it 'Should return total count of issues and merge requests assigned to milestone' do
milestone.total_items_count.should eq 2
end
end
describe :can_be_closed? do
before do
milestone = create :milestone
create :closed_issue, milestone: milestone
issue = create :issue
end
it 'should be true if milestone active and all nestied issues closed' do
milestone.can_be_closed?.should be_true
end
it 'should be false if milestone active and not all nestied issues closed' do
issue.milestone = milestone
issue.save
milestone.can_be_closed?.should be_false
end
end
end
......@@ -121,10 +121,7 @@ describe Project do
let(:project) { create(:project) }
before do
@merge_request = create(:merge_request,
project: project,
merged: false,
closed: false)
@merge_request = create(:merge_request, project: project)
@key = create(:key, user_id: project.owner.id)
end
......@@ -133,8 +130,7 @@ describe Project do
@merge_request.last_commit.id.should == "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a"
project.update_merge_requests("8716fc78f3c65bbf7bcf7b574febd583bc5d2812", "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a", "refs/heads/stable", @key.user)
@merge_request.reload
@merge_request.merged.should be_true
@merge_request.closed.should be_true
@merge_request.merged?.should be_true
end
it "should update merge request commits with new one if pushed to source branch" do
......
This diff is collapsed.
require 'spec_helper'
describe MergeRequestObserver do
let(:some_user) { double(:user, id: 1) }
let(:assignee) { double(:user, id: 2) }
let(:author) { double(:user, id: 3) }
let(:mr) { double(:merge_request, id: 42, assignee: assignee, author: author) }
let(:some_user) { create :user }
let(:assignee) { create :user }
let(:author) { create :user }
let(:mr_mock) { double(:merge_request, id: 42, assignee: assignee, author: author) }
let(:assigned_mr) { create(:merge_request, assignee: assignee, author: author) }
let(:unassigned_mr) { create(:merge_request, author: author) }
let(:closed_assigned_mr) { create(:closed_merge_request, assignee: assignee, author: author) }
let(:closed_unassigned_mr) { create(:closed_merge_request, author: author) }
before(:each) { subject.stub(:current_user).and_return(some_user) }
......@@ -21,23 +25,21 @@ describe MergeRequestObserver do
end
it 'sends an email to the assignee' do
Notify.should_receive(:new_merge_request_email).with(mr.id)
subject.after_create(mr)
Notify.should_receive(:new_merge_request_email).with(mr_mock.id)
subject.after_create(mr_mock)
end
it 'does not send an email to the assignee if assignee created the merge request' do
subject.stub(:current_user).and_return(assignee)
Notify.should_not_receive(:new_merge_request_email)
subject.after_create(mr)
subject.after_create(mr_mock)
end
end
context '#after_update' do
before(:each) do
mr.stub(:is_being_reassigned?).and_return(false)
mr.stub(:is_being_closed?).and_return(false)
mr.stub(:is_being_reopened?).and_return(false)
mr_mock.stub(:is_being_reassigned?).and_return(false)
end
it 'is called when a merge request is changed' do
......@@ -52,97 +54,50 @@ describe MergeRequestObserver do
context 'a reassigned email' do
it 'is sent if the merge request is being reassigned' do
mr.should_receive(:is_being_reassigned?).and_return(true)
subject.should_receive(:send_reassigned_email).with(mr)
mr_mock.should_receive(:is_being_reassigned?).and_return(true)
subject.should_receive(:send_reassigned_email).with(mr_mock)
subject.after_update(mr)
subject.after_update(mr_mock)
end
it 'is not sent if the merge request is not being reassigned' do
mr.should_receive(:is_being_reassigned?).and_return(false)
mr_mock.should_receive(:is_being_reassigned?).and_return(false)
subject.should_not_receive(:send_reassigned_email)
subject.after_update(mr)
subject.after_update(mr_mock)
end
end
context 'a status "closed"' do
it 'note is created if the merge request is being closed' do
mr.should_receive(:is_being_closed?).and_return(true)
Note.should_receive(:create_status_change_note).with(mr, some_user, 'closed')
subject.after_update(mr)
end
it 'note is not created if the merge request is not being closed' do
mr.should_receive(:is_being_closed?).and_return(false)
Note.should_not_receive(:create_status_change_note).with(mr, some_user, 'closed')
subject.after_update(mr)
end
it 'notification is delivered if the merge request being closed' do
mr.stub(:is_being_closed?).and_return(true)
Note.should_receive(:create_status_change_note).with(mr, some_user, 'closed')
subject.after_update(mr)
end
it 'notification is not delivered if the merge request not being closed' do
mr.stub(:is_being_closed?).and_return(false)
Note.should_not_receive(:create_status_change_note).with(mr, some_user, 'closed')
context '#after_close' do
context 'a status "closed"' do
it 'note is created if the merge request is being closed' do
Note.should_receive(:create_status_change_note).with(assigned_mr, some_user, 'closed')
subject.after_update(mr)
assigned_mr.close
end
it 'notification is delivered only to author if the merge request is being closed' do
mr_without_assignee = double(:merge_request, id: 42, author: author, assignee: nil)
mr_without_assignee.stub(:is_being_reassigned?).and_return(false)
mr_without_assignee.stub(:is_being_closed?).and_return(true)
mr_without_assignee.stub(:is_being_reopened?).and_return(false)
Note.should_receive(:create_status_change_note).with(mr_without_assignee, some_user, 'closed')
Note.should_receive(:create_status_change_note).with(unassigned_mr, some_user, 'closed')
subject.after_update(mr_without_assignee)
unassigned_mr.close
end
end
end
context '#after_reopen' do
context 'a status "reopened"' do
it 'note is created if the merge request is being reopened' do
mr.should_receive(:is_being_reopened?).and_return(true)
Note.should_receive(:create_status_change_note).with(mr, some_user, 'reopened')
subject.after_update(mr)
end
it 'note is not created if the merge request is not being reopened' do
mr.should_receive(:is_being_reopened?).and_return(false)
Note.should_not_receive(:create_status_change_note).with(mr, some_user, 'reopened')
subject.after_update(mr)
end
it 'notification is delivered if the merge request being reopened' do
mr.stub(:is_being_reopened?).and_return(true)
Note.should_receive(:create_status_change_note).with(mr, some_user, 'reopened')
subject.after_update(mr)
end
it 'notification is not delivered if the merge request is not being reopened' do
mr.stub(:is_being_reopened?).and_return(false)
Note.should_not_receive(:create_status_change_note).with(mr, some_user, 'reopened')
Note.should_receive(:create_status_change_note).with(closed_assigned_mr, some_user, 'reopened')
subject.after_update(mr)
closed_assigned_mr.reopen
end
it 'notification is delivered only to author if the merge request is being reopened' do
mr_without_assignee = double(:merge_request, id: 42, author: author, assignee: nil)
mr_without_assignee.stub(:is_being_reassigned?).and_return(false)
mr_without_assignee.stub(:is_being_closed?).and_return(false)
mr_without_assignee.stub(:is_being_reopened?).and_return(true)
Note.should_receive(:create_status_change_note).with(mr_without_assignee, some_user, 'reopened')
Note.should_receive(:create_status_change_note).with(closed_unassigned_mr, some_user, 'reopened')
subject.after_update(mr_without_assignee)
closed_unassigned_mr.reopen
end
end
end
......@@ -151,23 +106,23 @@ describe MergeRequestObserver do
let(:previous_assignee) { double(:user, id: 3) }
before(:each) do
mr.stub(:assignee_id).and_return(assignee.id)
mr.stub(:assignee_id_was).and_return(previous_assignee.id)
mr_mock.stub(:assignee_id).and_return(assignee.id)
mr_mock.stub(:assignee_id_was).and_return(previous_assignee.id)
end
def it_sends_a_reassigned_email_to(recipient)
Notify.should_receive(:reassigned_merge_request_email).with(recipient, mr.id, previous_assignee.id)
Notify.should_receive(:reassigned_merge_request_email).with(recipient, mr_mock.id, previous_assignee.id)
end
def it_does_not_send_a_reassigned_email_to(recipient)
Notify.should_not_receive(:reassigned_merge_request_email).with(recipient, mr.id, previous_assignee.id)
Notify.should_not_receive(:reassigned_merge_request_email).with(recipient, mr_mock.id, previous_assignee.id)
end
it 'sends a reassigned email to the previous and current assignees' do
it_sends_a_reassigned_email_to assignee.id
it_sends_a_reassigned_email_to previous_assignee.id
subject.send(:send_reassigned_email, mr)
subject.send(:send_reassigned_email, mr_mock)
end
context 'does not send an email to the user who made the reassignment' do
......@@ -176,14 +131,14 @@ describe MergeRequestObserver do
it_sends_a_reassigned_email_to previous_assignee.id
it_does_not_send_a_reassigned_email_to assignee.id
subject.send(:send_reassigned_email, mr)
subject.send(:send_reassigned_email, mr_mock)
end
it 'if the user is the previous assignee' do
subject.stub(:current_user).and_return(previous_assignee)
it_sends_a_reassigned_email_to assignee.id
it_does_not_send_a_reassigned_email_to previous_assignee.id
subject.send(:send_reassigned_email, mr)
subject.send(:send_reassigned_email, mr_mock)
end
end
end
......
......@@ -54,14 +54,24 @@ describe Gitlab::API do
end
end
describe "PUT /projects/:id/issues/:issue_id" do
describe "PUT /projects/:id/issues/:issue_id to update only title" do
it "should update a project issue" do
put api("/projects/#{project.id}/issues/#{issue.id}", user),
title: 'updated title', labels: 'label2', closed: 1
title: 'updated title'
response.status.should == 200
json_response['title'].should == 'updated title'
end
end
describe "PUT /projects/:id/issues/:issue_id to update state and label" do
it "should update a project issue" do
put api("/projects/#{project.id}/issues/#{issue.id}", user),
labels: 'label2', state_event: "close"
response.status.should == 200
json_response['labels'].should == ['label2']
json_response['closed'].should be_true
json_response['state'].should eq "closed"
end
end
......
......@@ -43,6 +43,23 @@ describe Gitlab::API do
end
end
describe "PUT /projects/:id/merge_request/:merge_request_id to close MR" do
it "should return merge_request" do
put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), state_event: "close"
response.status.should == 200
json_response['state'].should == 'closed'
end
end
describe "PUT /projects/:id/merge_request/:merge_request_id to merge MR" do
it "should return merge_request" do
put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), state_event: "merge"
response.status.should == 200
json_response['state'].should == 'merged'
end
end
describe "PUT /projects/:id/merge_request/:merge_request_id" do
it "should return merge_request" do
put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), title: "New title"
......
......@@ -44,4 +44,14 @@ describe Gitlab::API do
json_response['title'].should == 'updated title'
end
end
describe "PUT /projects/:id/milestones/:milestone_id to close milestone" do
it "should update a project milestone" do
put api("/projects/#{project.id}/milestones/#{milestone.id}", user),
state_event: 'close'
response.status.should == 200
json_response['state'].should == 'closed'
end
end
end
......@@ -58,8 +58,7 @@ describe "Issues" do
it "should be able to search on different statuses" do
issue = Issue.first # with title 'foobar'
issue.closed = true
issue.save
issue.close
visit project_issues_path(project)
click_link 'Closed'
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment