[Projxp-devel] [2400] Sprints have an explicit state.
Darryl Pierce
mcpierce at gmail.com
Sat Oct 11 18:12:18 EDT 2008
This patch contains the code for adding a column, named status, to the
sprints table. Additionally, there is code in the controller for
allowing the product owner to change the status of a sprint, and also
code in the sprint view to display the status.
Index: test/functional/sprint_controller_test.rb
===================================================================
--- test/functional/sprint_controller_test.rb (revision 30)
+++ test/functional/sprint_controller_test.rb (working copy)
@@ -30,8 +30,11 @@
:start => DateTime.now.to_s(:db),
:duration => 14
}
- @sprint = sprints(:initial_development_sprint)
+ @sprint = sprints(:initial_development_sprint)
+ @product = @sprint.product
+ @owner = @product.owner
@nonowner = users(:jdonuts)
+ flunk "Owner and nonowner cannot be the same!" if @owner.id == @nonowner.id
@user_story = user_stories(:create_update_profile)
@user_story_ids = [@user_story.id]
end
@@ -47,11 +50,11 @@
# Ensures that sprints can be viewed.
#
def test_view
- get :view, {:id => sprints(:initial_development_sprint).id}
+ get :view, {:id => @sprint.id}
assert_response :success
assert assigns['sprint'], 'Did not load a sprint.'
- assert_equal sprints(:initial_development_sprint).id, assigns['sprint'].id,
+ assert_equal @sprint.id, assigns['sprint'].id,
'Did not load the correct sprint.'
end
@@ -66,7 +69,7 @@
# Ensures that the product is valid before creating a sprint.
#
def test_create_with_invalid_product
- get :create, {:product => 9999}, {:user_id => users(:mcpierce).id}
+ get :create, {:product => 9999}, {:user_id => @owner.id}
assert_redirected_to :controller => :home, :action => :error
end
@@ -74,15 +77,17 @@
# Ensures that only the product owner can create a sprint.
#
def test_create_requires_product_owner
- get :create, {:product => products(:cairo_web).id}, {:user_id =>
users(:jdonuts).id}
+ get :create,
+ {:product => @product.id},
+ {:user_id => @nonowner.id}
- assert_redirected_to :controller => :product, :action => :view,
:id => products(:cairo_web).id
+ assert_redirected_to :controller => :product, :action => :view,
:id => @product.id
end
# Ensures that a well-formed create request works as expected.
#
def test_create
- get :create, {:product => products(:cairo_web)}, {:user_id =>
products(:cairo_web).owner_id}
+ get :create, {:product => @product}, {:user_id => @owner.id}
assert_response :success
end
@@ -93,8 +98,8 @@
@details[:duration] = -2
post :create,
- {:product => products(:cairo_web).id, :sprint => @details},
- {:user_id => products(:cairo_web).owner_id}
+ {:product => @product.id, :sprint => @details},
+ {:user_id => @owner.id}
assert_response :success
assert_template 'sprint/create'
@@ -104,8 +109,8 @@
#
def test_create_as_save
post :create,
- {:product => products(:cairo_web).id, :sprint => @details},
- {:user_id => products(:cairo_web).owner_id}
+ {:product => @product.id, :sprint => @details},
+ {:user_id => @owner.id}
sprint = Sprint.find_by_title(@details[:title])
assert sprint, 'Failed to save the sprint.'
@@ -123,7 +128,7 @@
# Ensures that an invalid sprint id fails.
#
def test_modify_with_invalid_sprint_id
- get :modify, {:id => 9999}, {:user_id => users(:mcpierce).id}
+ get :modify, {:id => 9999}, {:user_id => @owner.id}
assert_redirected_to :controller => :home, :action => :error
end
@@ -132,19 +137,18 @@
#
def test_modify_requires_product_owner
get :modify,
- {:id => sprints(:initial_development_sprint).id},
- {:user_id => users(:jdonuts).id}
+ {:id => @sprint.id},
+ {:user_id => @nonowner.id}
- assert_redirected_to :controller => :product, :action => :view,
- :id => sprints(:initial_development_sprint).product_id
+ assert_redirected_to :action => :view, :id => @sprint.id
end
# Ensures that a proper request to modify a sprint works.
#
def test_modify
get :modify,
- {:id => sprints(:initial_development_sprint).id},
- {:user_id => products(:cairo_web).owner_id}
+ {:id => @sprint.id},
+ {:user_id => @owner.id}
assert_response :success
end
@@ -152,7 +156,7 @@
# Ensures that a modify as save with an invalid sprint fails.
#
def test_modify_as_save_with_invalid_sprint
- sprint = sprints(:initial_development_sprint)
+ sprint = @sprint
sprint.duration = -2
post :modify,
@@ -167,7 +171,7 @@
# Ensures that a modify as save works as expected.
#
def test_modify_as_save
- sprint = sprints(:initial_development_sprint)
+ sprint = @sprint
sprint.duration *= 2
post :modify,
@@ -199,7 +203,7 @@
def test_populate_not_as_product_owner
get :populate, {:id => @sprint.id}, {:user_id => @nonowner.id}
- assert_redirected_to :controller => :product, :action => :view,
:id => @sprint.product
+ assert_redirected_to :action => :view, :id => @sprint.id
end
# Ensures that entering the sprint populate view works.
@@ -261,4 +265,58 @@
assert_redirected_to :action => :view, :id => @sprint.id
end
+
+ # Ensures that anonymous users cannot change a sprint's status.
+ #
+ def test_status_as_anonymous
+ get :status
+
+ assert_redirected_to :controller => :home, :action => :login
+ end
+
+ # Ensures that you cannot change status on an invalid sprint.
+ #
+ def test_status_with_invalid_id
+ get :status, {:id => 9999}, {:user_id => @owner.id}
+
+ assert_redirected_to :controller => :home, :action => :error
+ end
+
+ # Ensures that someone who's not the product owner can't change the status.
+ #
+ def test_status_as_nonowner
+ status = @sprint.status
+
+ get :status, {:id => @sprint.id}, {:user_id => @nonowner.id}
+
+ assert_redirected_to :action => :view, :id => @sprint.id
+ result = Sprint.find_by_id(@sprint.id)
+ assert_equal status, result.status, 'Status should not have changed.'
+ end
+
+ # Ensures that a status must be included.
+ #
+ def test_status_without_value
+ status = @sprint.status
+
+ post :status, {:id => @sprint.id}, {:user_id => @owner.id}
+
+ assert_redirected_to :action => :view, :id => @sprint.id
+ result = Sprint.find_by_id(@sprint.id)
+ assert_equal status, result.status, 'status should not have changed.'
+ end
+
+ # Ensures that a status must be valid.
+ #
+ def test_status_with_invalid_value
+ status = @sprint.status
+
+ post :status,
+ {:id => @sprint.id, :status => 9999},
+ {:user_id => @owner.id}
+
+ assert_redirected_to :action => :view, :id => @sprint.id
+ result = Sprint.find_by_id(@sprint.id)
+ assert_equal status, result.status, 'status should not have changed.'
+ end
end
Index: test/fixtures/sprints.yml
===================================================================
--- test/fixtures/sprints.yml (revision 30)
+++ test/fixtures/sprints.yml (working copy)
@@ -4,3 +4,4 @@
start: <%= DateTime.now.to_s(:db) %>
duration: 28
goals: Get things done.
+ status: <%= Sprint::STATUS_PLANNED %>
\ No newline at end of file
Index: app/helpers/sprint_helper.rb
===================================================================
--- app/helpers/sprint_helper.rb (revision 30)
+++ app/helpers/sprint_helper.rb (working copy)
@@ -21,4 +21,8 @@
return false
end
+
+ def user_can_change_sprint_status(user, sprint)
+ user && (user.id == sprint.product.owner.id)
+ end
end
Index: app/models/sprint.rb
===================================================================
--- app/models/sprint.rb (revision 30)
+++ app/models/sprint.rb (working copy)
@@ -61,6 +61,18 @@
has_many :backlog_items
has_many :user_stories, :through => :backlog_items, :order => :priority
+ STATUS_PLANNED = 0
+ STATUS_ACTIVE = 1
+ STATUS_CLOSED = 2
+ STATUS_CANCELED = 3
+ STATUS_TEXT =
+ {
+ 'Planned' => STATUS_PLANNED,
+ 'Active' => STATUS_ACTIVE,
+ 'Closed' => STATUS_CLOSED,
+ 'Canceled' => STATUS_CANCELED
+ }.sort_by { |k,v| v }
+
def end_date
self.start + (self.duration - 1)
end
@@ -96,9 +108,9 @@
return result
end
- # Returns the overage hours on the project. Overage hours are based on the
+ # Returns the overage hours on the project. Overage hours are based on the
# number of hours over estimation a backlog item took before it was
completed.
- #
+ #
def overage_hours
result = 0.0
Index: app/controllers/sprint_controller.rb
===================================================================
--- app/controllers/sprint_controller.rb (revision 30)
+++ app/controllers/sprint_controller.rb (working copy)
@@ -18,9 +18,9 @@
# +SprintController+ handles the business rules for sprints.
#
class SprintController < ApplicationController
- before_filter :authenticated, :only => [:create, :modify,
:populate, :cancel]
+ before_filter :authenticated, :only => [:create, :modify,
:populate, :cancel, :status]
before_filter :load_product, :only => [:create]
- before_filter :load_sprint, :only => [:view, :modify, :populate]
+ before_filter :load_sprint, :only => [:view, :modify,
:populate, :status]
before_filter :validate_estimates, :only => [:populate]
before_filter :only_product_owner, :except => [:view]
@@ -119,6 +119,28 @@
def cancel
end
+
+ def status
+ if request.post?
+ if params[:status] &&
(0..Sprint::STATUS_TEXT.size).include?(params[:status])
+ begin
+ Sprint.transaction do
+ @sprint.status = params[:status]
+
+ @sprint.save
+ end
+
+ rescue Exception => error
+ flash[:message] = error.message
+ end
+
+ redirect_to :action => :view, :id => @sprint.id
+ else
+ flash[:message] = 'No status was specified or was invalid.'
+ redirect_to :action => :view, :id => @sprint.id
+ end
+ end
+ end
private
@@ -162,7 +184,11 @@
unless @product && @product.owner && (@product.owner.id == @user.id)
flash[:message] = 'Only the product owner can perform that action.'
- redirect_to :controller => :product, :action => :view, :id => @product
+ if @sprint
+ redirect_to :action => :view, :id => @sprint.id
+ else
+ redirect_to :controller => :product, :action => :view, :id =>
@product.id
+ end
end
end
Index: app/views/sprint/view.html.erb
===================================================================
--- app/views/sprint/view.html.erb (revision 30)
+++ app/views/sprint/view.html.erb (working copy)
@@ -1,8 +1,10 @@
<div style="width: 100%; overflow: auto">
<div style="float: left; width: 33%;">
<div class="toolbar">
+
<%= link_to "Edit", :action => :modify, :id => @sprint %>
<%= link_to "Populate", :action => :populate, :id => @sprint %>
+
</div>
<table class="details">
<thead>
@@ -13,6 +15,21 @@
<tbody>
<tr>
+ <td class="label">Status:</td>
+ <td class="value">
+ <% if user_can_change_sprint_status(@user, @sprint) %>
+ <% form_tag(:action => :status, :id => @sprint.id) do %>
+ <%= select_tag "status",
+ options_for_select(Sprint::STATUS_TEXT, @sprint.status) %>
+ <%= submit_tag "Apply" %>
+ <% end %>
+ <% else %>
+ <%= Sprint::STATUS_TEXT[@sprint.status] %>
+ <% end %>
+ </td>
+ </tr>
+
+ <tr>
<td class="label">Starts:</td>
<td class="value"><%= @sprint.start.to_s(:date) %></td>
</tr>
@@ -28,11 +45,6 @@
</tr>
<tr>
- <td class="label">Status:</td>
- <td class="value"><%= show_sprint_health @sprint %></td>
- </tr>
-
- <tr>
<td class="text" colspan="2"><%= simple_format @sprint.goals %></td>
</tr>
</tbody>
Index: db/schema.rb
===================================================================
--- db/schema.rb (revision 30)
+++ db/schema.rb (working copy)
@@ -9,7 +9,7 @@
#
# It's strongly recommended to check this file into your version
control system.
-ActiveRecord::Schema.define(:version => 12) do
+ActiveRecord::Schema.define(:version => 13) do
create_table "backlog_items", :force => true do |t|
t.integer "sprint_id",
:null => false
@@ -93,13 +93,14 @@
add_index "sessions", ["updated_at"], :name => "index_sessions_on_updated_at"
create_table "sprints", :force => true do |t|
- t.integer "product_id", :null => false
- t.string "title", :limit => 100, :null => false
- t.date "start", :null => false
- t.integer "duration", :null => false
- t.string "goals", :limit => 1000, :null => false
+ t.integer "product_id", :null => false
+ t.string "title", :limit => 100, :null => false
+ t.date "start", :null => false
+ t.integer "duration", :null => false
+ t.string "goals", :limit => 1000, :null => false
t.datetime "created_at"
t.datetime "updated_at"
+ t.integer "status", :default => 0, :null => false
end
add_index "sprints", ["product_id"], :name => "fk_sprint_product"
--
Darryl L. Pierce <mcpierce at gmail.com>
Visit the Infobahn Offramp: <http://mcpierce.multiply.com>
"Bury me next to my wife. Nothing too fancy..." - Ulysses S. Grant
More information about the Projxp-devel
mailing list