@ -0,0 +1,109 @@ |
||||
class Impediment < Task |
||||
unloadable |
||||
|
||||
acts_as_list :scope => :project |
||||
|
||||
after_save :update_blocks_list |
||||
|
||||
safe_attributes "blocks_ids", |
||||
:if => lambda {|impediment, user| |
||||
(impediment.new_record? && user.allowed_to?(:create_impediments, impediment.project)) || |
||||
user.allowed_to?(:update_impediments, impediment.project) |
||||
} |
||||
|
||||
def self.find(*args) |
||||
if args[1] && args[1][:conditions] |
||||
if args[1][:conditions].is_a?(Hash) |
||||
args[1][:conditions][:parent_id] = nil |
||||
args[1][:conditions][:tracker_id] = self.tracker |
||||
elsif args[1][:conditions].is_a?(Array) |
||||
args[1][:conditions][0] += " AND parent_id is NULL AND tracker_id = #{self.tracker}" |
||||
end |
||||
else |
||||
args << {:conditions => {:parent_id => nil, :tracker_id => self.tracker}} |
||||
end |
||||
|
||||
super |
||||
end |
||||
|
||||
def self.find_all_updated_since(since, project_id) |
||||
super(since, project_id, true) |
||||
end |
||||
|
||||
def blocks_ids=(ids) |
||||
@blocks_ids_list = [ids] if ids.is_a?(Integer) |
||||
@blocks_ids_list = ids.split(/\D+/).map{|id| id.to_i} if ids.is_a?(String) |
||||
@blocks_ids_list = ids.map {|id| id.to_i} if ids.is_a?(Array) |
||||
end |
||||
|
||||
def blocks_ids |
||||
@blocks_ids_list ||= relations_from.select{ |rel| rel.relation_type == IssueRelation::TYPE_BLOCKS }.collect(&:issue_to_id) |
||||
end |
||||
|
||||
def move_after(prev_id) |
||||
#our super's, move after is using awesome_nested_set which is inapproriate for impediments. |
||||
#Impediments always have a parent_id of nil, it is their defining criteria. |
||||
#Therefore we use the acts_as_list method |
||||
#TODO: create a module to be included by story and impediment, for now this code is just copy&paste |
||||
|
||||
# remove so the potential 'prev' has a correct position |
||||
remove_from_list |
||||
|
||||
begin |
||||
prev = self.class.find(prev_id) |
||||
rescue ActiveRecord::RecordNotFound |
||||
prev = nil |
||||
end |
||||
|
||||
# if it's the first story, move it to the 1st position |
||||
if prev.blank? |
||||
insert_at |
||||
move_to_top |
||||
|
||||
# if its predecessor has no position (shouldn't happen), make it |
||||
# the last story |
||||
elsif !prev.in_list? |
||||
insert_at |
||||
move_to_bottom |
||||
|
||||
# there's a valid predecessor |
||||
else |
||||
insert_at(prev.position + 1) |
||||
end |
||||
end |
||||
|
||||
private |
||||
|
||||
def update_blocks_list |
||||
relations_from = [] if relations_from.nil? |
||||
remove_from_blocks_list |
||||
add_to_blocks_list |
||||
end |
||||
|
||||
def remove_from_blocks_list |
||||
self.relations_from.delete(self.relations_from.select{|rel| rel.relation_type == IssueRelation::TYPE_BLOCKS && !blocks_ids.include?(rel.issue_to_id) }) |
||||
end |
||||
|
||||
def add_to_blocks_list |
||||
currently_blocking = relations_from.select{|rel| rel.relation_type == IssueRelation::TYPE_BLOCKS}.collect(&:issue_to_id) |
||||
|
||||
(self.blocks_ids - currently_blocking).each{ |id| |
||||
rel = IssueRelation.new(:relation_type => IssueRelation::TYPE_BLOCKS, :issue_from => self) |
||||
rel.issue_to_id = id #attr_protected |
||||
self.relations_from << rel |
||||
} |
||||
end |
||||
|
||||
def validate |
||||
validate_blocks_list |
||||
end |
||||
|
||||
def validate_blocks_list |
||||
if blocks_ids.size == 0 |
||||
errors.add :blocks_ids, :must_block_at_least_one_issue |
||||
else |
||||
issues = Issue.find_all_by_id(blocks_ids) |
||||
errors.add :blocks_ids, :can_only_contain_issues_of_current_sprint if issues.size == 0 || issues.any?{|i| i.fixed_version != self.fixed_version } |
||||
end |
||||
end |
||||
end |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 529 B After Width: | Height: | Size: 529 B |
Before Width: | Height: | Size: 613 B After Width: | Height: | Size: 613 B |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 180 B After Width: | Height: | Size: 180 B |
Before Width: | Height: | Size: 178 B After Width: | Height: | Size: 178 B |
Before Width: | Height: | Size: 120 B After Width: | Height: | Size: 120 B |
Before Width: | Height: | Size: 105 B After Width: | Height: | Size: 105 B |
Before Width: | Height: | Size: 111 B After Width: | Height: | Size: 111 B |
Before Width: | Height: | Size: 110 B After Width: | Height: | Size: 110 B |
Before Width: | Height: | Size: 119 B After Width: | Height: | Size: 119 B |
Before Width: | Height: | Size: 101 B After Width: | Height: | Size: 101 B |
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 613 B After Width: | Height: | Size: 613 B |
@ -0,0 +1,4 @@ |
||||
Factory.define(:impediment, :parent => :issue, :class => Impediment) do |t| |
||||
t.association :tracker, :factory => :tracker_task |
||||
t.subject "Impeding progress" |
||||
end |
@ -0,0 +1,3 @@ |
||||
Factory.define(:task, :parent => :issue, :class => Task) do |t| |
||||
t.association :tracker, :factory => :tracker_task |
||||
end |
@ -0,0 +1,300 @@ |
||||
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') |
||||
|
||||
describe Impediment do |
||||
let(:user) { @user ||= Factory.create(:user) } |
||||
let(:role) { @role ||= Factory.create(:role) } |
||||
let(:tracker_feature) { @tracker_feature ||= Factory.create(:tracker_feature) } |
||||
let(:tracker_task) { @tracker_task ||= Factory.create(:tracker_task) } |
||||
let(:issue_priority) { @issue_priority ||= Factory.create(:priority, :is_default => true) } |
||||
let(:task) { Factory.build(:task, :tracker => tracker_task, |
||||
:project => project, |
||||
:author => user, |
||||
:priority => issue_priority, |
||||
:status => issue_status1) } |
||||
let(:feature) { Factory.build(:issue, :tracker => tracker_feature, |
||||
:project => project, |
||||
:author => user, |
||||
:priority => issue_priority, |
||||
:status => issue_status1) } |
||||
let(:version) { Factory.create(:version, :project => project) } |
||||
|
||||
let(:project) do |
||||
unless @project |
||||
@project = Factory.build(:project) |
||||
@project.members = [Factory.build(:member, :principal => user, |
||||
:project => @project, |
||||
:roles => [role])] |
||||
end |
||||
@project |
||||
end |
||||
|
||||
let(:issue_status1) { @status1 ||= Factory.create(:issue_status, :name => "status 1", :is_default => true) } |
||||
let(:issue_status2) { @status2 ||= Factory.create(:issue_status, :name => "status 2") } |
||||
let(:tracker_workflow) { @workflow ||= Workflow.create(:tracker_id => tracker_task.id, |
||||
:old_status => issue_status1, |
||||
:new_status => issue_status2, |
||||
:role => role) } |
||||
let(:impediment) { Factory.build(:impediment, :author => user, |
||||
:fixed_version => version, |
||||
:assigned_to => user, |
||||
:priority => issue_priority, |
||||
:project => project, |
||||
:tracker => tracker_task, |
||||
:status => issue_status1)} |
||||
|
||||
before(:each) do |
||||
Setting.plugin_redmine_backlogs = {:points_burn_direction => "down", |
||||
:wiki_template => "", |
||||
:card_spec => "Sattleford VM-5040", |
||||
:story_trackers => [tracker_feature.id.to_s], |
||||
:task_tracker => tracker_task.id.to_s } |
||||
|
||||
User.current = user |
||||
issue_priority.save |
||||
issue_status1.save |
||||
project.save |
||||
tracker_workflow.save |
||||
end |
||||
|
||||
describe "class methods" do |
||||
describe :create_with_relationships do |
||||
before(:each) do |
||||
@impediment_subject = "Impediment A" |
||||
role.permissions = [:create_impediments] |
||||
role.save |
||||
end |
||||
|
||||
shared_examples_for "impediment creation" do |
||||
it { @impediment.subject.should eql @impediment_subject } |
||||
it { @impediment.author.should eql User.current } |
||||
it { @impediment.project.should eql project } |
||||
it { @impediment.fixed_version.should eql version } |
||||
it { @impediment.priority.should eql issue_priority} |
||||
it { @impediment.status.should eql issue_status1 } |
||||
it { @impediment.tracker.should eql tracker_task } |
||||
it { @impediment.assigned_to.should eql user } |
||||
end |
||||
|
||||
shared_examples_for "impediment creation with 1 blocking relationship" do |
||||
it_should_behave_like "impediment creation" |
||||
it { @impediment.should have(1).relations_from } |
||||
it { @impediment.relations_from[0].issue_to.should eql feature } |
||||
it { @impediment.relations_from[0].relation_type.should eql IssueRelation::TYPE_BLOCKS } |
||||
end |
||||
|
||||
shared_examples_for "impediment creation with no blocking relationship" do |
||||
it_should_behave_like "impediment creation" |
||||
it { @impediment.should have(0).relations_from } |
||||
end |
||||
|
||||
describe "WITH a blocking relationship to a story" do |
||||
describe "WITH the story having the same version" do |
||||
before(:each) do |
||||
feature.fixed_version = version |
||||
feature.save |
||||
@impediment = Impediment.create_with_relationships({:subject => @impediment_subject, |
||||
:assigned_to_id => user.id, |
||||
:blocks_ids => feature.id.to_s, |
||||
:status_id => issue_status1.id, |
||||
:fixed_version_id => version.id}, |
||||
project.id) |
||||
|
||||
end |
||||
|
||||
it_should_behave_like "impediment creation with 1 blocking relationship" |
||||
it { @impediment.should_not be_new_record } |
||||
it { @impediment.relations_from[0].should_not be_new_record } |
||||
end |
||||
|
||||
describe "WITH the story having another version" do |
||||
before(:each) do |
||||
feature.fixed_version = Factory.create(:version, :project => project, :name => "another version") |
||||
feature.save |
||||
@impediment = Impediment.create_with_relationships({:subject => @impediment_subject, |
||||
:assigned_to_id => user.id, |
||||
:blocks_ids => feature.id.to_s, |
||||
:status_id => issue_status1.id, |
||||
:fixed_version_id => version.id}, |
||||
project.id) |
||||
end |
||||
|
||||
it_should_behave_like "impediment creation with no blocking relationship" |
||||
it { @impediment.should be_new_record } |
||||
it { @impediment.errors[:blocks_ids].should eql I18n.t(:can_only_contain_issues_of_current_sprint, :scope => [:activerecord, :errors, :models, :impediment, :attributes, :blocks_ids]) } |
||||
end |
||||
|
||||
describe "WITH the story being non existent" do |
||||
before(:each) do |
||||
@impediment = Impediment.create_with_relationships({:subject => @impediment_subject, |
||||
:assigned_to_id => user.id, |
||||
:blocks_ids => "0", |
||||
:status_id => issue_status1.id, |
||||
:fixed_version_id => version.id}, |
||||
project.id) |
||||
end |
||||
|
||||
it_should_behave_like "impediment creation with no blocking relationship" |
||||
it { @impediment.should be_new_record } |
||||
it { @impediment.errors[:blocks_ids].should eql I18n.t(:can_only_contain_issues_of_current_sprint, :scope => [:activerecord, :errors, :models, :impediment, :attributes, :blocks_ids]) } |
||||
end |
||||
end |
||||
|
||||
describe "WITHOUT a blocking relationship defined" do |
||||
before(:each) do |
||||
@impediment = Impediment.create_with_relationships({:subject => @impediment_subject, |
||||
:assigned_to_id => user.id, |
||||
:blocks_ids => "", |
||||
:status_id => issue_status1.id, |
||||
:fixed_version_id => version.id}, |
||||
project.id) |
||||
end |
||||
|
||||
it_should_behave_like "impediment creation with no blocking relationship" |
||||
it { @impediment.should be_new_record } |
||||
it { @impediment.errors[:blocks_ids].should eql I18n.t(:must_block_at_least_one_issue, :scope => [:activerecord, :errors, :models, :impediment, :attributes, :blocks_ids]) } |
||||
end |
||||
end |
||||
end |
||||
|
||||
describe "instance methods" do |
||||
describe :update_with_relationships do |
||||
before(:each) do |
||||
role.permissions = [:update_impediments] |
||||
role.save |
||||
|
||||
feature.fixed_version = version |
||||
feature.save |
||||
|
||||
@impediment = impediment |
||||
@impediment.blocks_ids = feature.id.to_s |
||||
@impediment.save |
||||
end |
||||
|
||||
shared_examples_for "impediment update" do |
||||
it { @impediment.author.should eql user } |
||||
it { @impediment.project.should eql project } |
||||
it { @impediment.fixed_version.should eql version } |
||||
it { @impediment.priority.should eql issue_priority} |
||||
it { @impediment.status.should eql issue_status1 } |
||||
it { @impediment.tracker.should eql tracker_task } |
||||
it { @impediment.blocks_ids.should eql @blocks.split(/\D+/).map{|id| id.to_i} } |
||||
end |
||||
|
||||
shared_examples_for "impediment update with changed blocking relationship" do |
||||
it_should_behave_like "impediment update" |
||||
it { @impediment.should have(1).relations_from } |
||||
it { @impediment.relations_from[0].should_not be_new_record } |
||||
it { @impediment.relations_from[0].issue_to.should eql @story } |
||||
it { @impediment.relations_from[0].relation_type.should eql IssueRelation::TYPE_BLOCKS } |
||||
end |
||||
|
||||
shared_examples_for "impediment update with unchanged blocking relationship" do |
||||
it_should_behave_like "impediment update" |
||||
it { @impediment.should have(1).relations_from } |
||||
it { @impediment.relations_from[0].should_not be_changed } |
||||
it { @impediment.relations_from[0].issue_to.should eql feature } |
||||
it { @impediment.relations_from[0].relation_type.should eql IssueRelation::TYPE_BLOCKS } |
||||
end |
||||
|
||||
describe "WHEN changing the blocking relationship to another story" do |
||||
before(:each) do |
||||
@story = Factory.build(:issue, :subject => "another story", |
||||
:tracker => tracker_feature, |
||||
:project => project, |
||||
:author => user, |
||||
:priority => issue_priority, |
||||
:status => issue_status1) |
||||
end |
||||
|
||||
describe "WITH the story having the same version" do |
||||
before(:each) do |
||||
@story.fixed_version = version |
||||
@story.save |
||||
@blocks = @story.id.to_s |
||||
@impediment.update_with_relationships({:blocks_ids => @blocks, |
||||
:status_id => issue_status1.id.to_s}) |
||||
end |
||||
|
||||
it_should_behave_like "impediment update with changed blocking relationship" |
||||
it { @impediment.should_not be_changed } |
||||
end |
||||
|
||||
describe "WITH the story having another version" do |
||||
before(:each) do |
||||
@story.fixed_version = Factory.create(:version, :project => project, :name => "another version") |
||||
@story.save |
||||
@blocks = @story.id.to_s |
||||
@impediment.update_with_relationships({:blocks_ids => @blocks, |
||||
:status_id => issue_status1.id.to_s}) |
||||
end |
||||
|
||||
it_should_behave_like "impediment update with unchanged blocking relationship" |
||||
it { @impediment.should be_changed } |
||||
it { @impediment.errors[:blocks_ids].should eql I18n.t(:can_only_contain_issues_of_current_sprint, :scope => [:activerecord, :errors, :models, :impediment, :attributes, :blocks_ids]) } |
||||
end |
||||
|
||||
describe "WITH the story beeing non existent" do |
||||
before(:each) do |
||||
@blocks = "0" |
||||
@impediment.update_with_relationships({:blocks_ids => @blocks, |
||||
:status_id => issue_status1.id.to_s}) |
||||
end |
||||
|
||||
it_should_behave_like "impediment update with unchanged blocking relationship" |
||||
it { @impediment.should be_changed } |
||||
it { @impediment.errors[:blocks_ids].should eql I18n.t(:can_only_contain_issues_of_current_sprint, :scope => [:activerecord, :errors, :models, :impediment, :attributes, :blocks_ids]) } |
||||
end |
||||
end |
||||
|
||||
describe "WITHOUT a blocking relationship defined" do |
||||
before(:each) do |
||||
@blocks = "" |
||||
@impediment.update_with_relationships({:blocks_ids => @blocks, |
||||
:status_id => issue_status1.id.to_s}) |
||||
end |
||||
|
||||
it_should_behave_like "impediment update with unchanged blocking relationship" |
||||
it { @impediment.should be_changed } |
||||
it { @impediment.errors[:blocks_ids].should eql I18n.t(:must_block_at_least_one_issue, :scope => [:activerecord, :errors, :models, :impediment, :attributes, :blocks_ids]) } |
||||
end |
||||
end |
||||
|
||||
describe "blocks_ids=/blocks_ids" do |
||||
describe "WITH an integer" do |
||||
it do |
||||
impediment.blocks_ids = 2 |
||||
impediment.blocks_ids.should eql [2] |
||||
end |
||||
end |
||||
|
||||
describe "WITH a string" do |
||||
it do |
||||
impediment.blocks_ids = "1, 2, 3" |
||||
impediment.blocks_ids.should eql [1,2,3] |
||||
end |
||||
end |
||||
|
||||
describe "WITH an array" do |
||||
it do |
||||
impediment.blocks_ids = [1,2,3] |
||||
impediment.blocks_ids.should eql [1,2,3] |
||||
end |
||||
end |
||||
|
||||
describe "WITH only prior blockers defined" do |
||||
before(:each) do |
||||
feature.fixed_version = version |
||||
feature.save |
||||
task.fixed_version = version |
||||
task.save |
||||
|
||||
impediment.relations_from = [IssueRelation.new(:issue_from => impediment, :issue_to => feature, :relation_type => IssueRelation::TYPE_BLOCKS), |
||||
IssueRelation.new(:issue_from => impediment, :issue_to => task, :relation_type => IssueRelation::TYPE_BLOCKS)] |
||||
true |
||||
end |
||||
|
||||
it { impediment.blocks_ids.should eql [feature.id, task.id] } |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,54 @@ |
||||
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') |
||||
|
||||
describe Task do |
||||
let(:user) { @user ||= Factory.create(:user) } |
||||
let(:tracker_feature) { @tracker_feature ||= Factory.create(:tracker_feature) } |
||||
let(:tracker_task) { @tracker_task ||= Factory.create(:tracker_task) } |
||||
let(:issue_priority) { @issue_priority ||= Factory.create(:priority) } |
||||
let(:task) { Factory.build(:task, :tracker => tracker_task, |
||||
:project => project, |
||||
:author => user, |
||||
:priority => issue_priority, |
||||
:status => issue_status) } |
||||
let(:feature) { Factory.build(:issue, :tracker => tracker_feature, |
||||
:project => project, |
||||
:author => user, |
||||
:priority => issue_priority, |
||||
:status => issue_status) } |
||||
let(:version) { @version ||= Factory.create(:version, :project => project) } |
||||
let(:project) { @project ||= Factory.create(:project) } |
||||
let(:issue_status) { @status ||= Factory.create(:issue_status) } |
||||
|
||||
before(:each) do |
||||
Setting.plugin_redmine_backlogs = {"points_burn_direction" => "down", |
||||
"wiki_template" => "", |
||||
"card_spec" => "Sattleford VM-5040", |
||||
"story_trackers" => [tracker_feature.id.to_s], |
||||
"task_tracker" => tracker_task.id.to_s } |
||||
end |
||||
|
||||
describe "Instance Methods" do |
||||
before(:each) do |
||||
|
||||
end |
||||
|
||||
describe :impediment? do |
||||
describe "WITHOUT parent" do |
||||
before(:each) do |
||||
end |
||||
|
||||
it { task.should be_impediment } |
||||
end |
||||
|
||||
describe "WITH parent" do |
||||
before(:each) do |
||||
feat = feature |
||||
feat.save |
||||
task.parent_issue_id = feat.id |
||||
end |
||||
|
||||
it { task.should_not be_impediment } |
||||
end |
||||
end |
||||
end |
||||
end |