This patch requires a migration. It adds the "team_lead_id" column to
the sprints table. Unit tests have been modified to test for the
existence of the team lead.
On the sprint edit screen the product owner is able to select a product
team member to be the lead for the sprint. On the sprint list page there
is now a link to send the team lead an email.
Signed-off-by: Darryl L. Pierce <mcpierce(a)gmail.com>
---
app/models/sprint.rb | 9 ++++++-
app/views/sprints/_edit.html.erb | 9 ++++++
app/views/sprints/_list.html.erb | 7 +++-
app/views/sprints/show.html.erb | 5 +++
db/migrate/027_add_team_lead_to_sprint.rb | 38 ++++++++++++++++++++++++++++
test/fixtures/sprints.yml | 3 ++
test/functional/sprints_controller_test.rb | 9 +++---
test/unit/sprint_test.rb | 22 +++++++++++-----
8 files changed, 88 insertions(+), 14 deletions(-)
create mode 100644 db/migrate/027_add_team_lead_to_sprint.rb
diff --git a/app/models/sprint.rb b/app/models/sprint.rb
index cb009b1..cd99d4d 100644
--- a/app/models/sprint.rb
+++ b/app/models/sprint.rb
@@ -56,9 +56,13 @@ class Sprint < ActiveRecord::Base
validates_presence_of :goals,
:message => 'Please list the goals for this sprint.'
+ validates_presence_of :team_lead_id,
+ :message => 'A sprint must have a designated team lead.'
+
after_update :close_user_stories
belongs_to :product
+ belongs_to :team_lead, :class_name => 'User', :foreign_key =>
:team_lead_id
has_many :backlog_items,
:order => '(select priority from user_stories where id = user_story_id)
ASC',
@@ -128,7 +132,10 @@ class Sprint < ActiveRecord::Base
# Returns whether the specified user is allowed to populate this sprint.
def can_populate?(user)
- user && (user.id == product.owner.id) && pending?
+ user &&
+ ((user.id == product.owner.id) ||
+ (user.id == sprint.team_lead_id)) &&
+ pending?
end
# Returns whether the sprint can be moved to the given status.
diff --git a/app/views/sprints/_edit.html.erb b/app/views/sprints/_edit.html.erb
index c6cfaf0..9fca540 100644
--- a/app/views/sprints/_edit.html.erb
+++ b/app/views/sprints/_edit.html.erb
@@ -21,6 +21,15 @@
</td>
</tr>
+ <tr>
+ <td class="label-required">Team lead</td>
+ <td class="value">
+ <%= collection_select :sprint, :team_lead_id, @product.users, :id,
+ :display_name, {:include_blank => false} %>
+ <%= error_message_on (:sprint, :team_lead_id) %>
+ </td>
+ </tr>
+
<tr>
<td class="label-required">Starts on</td>
<td class="value">
diff --git a/app/views/sprints/_list.html.erb b/app/views/sprints/_list.html.erb
index f549b09..4d6f956 100644
--- a/app/views/sprints/_list.html.erb
+++ b/app/views/sprints/_list.html.erb
@@ -1,6 +1,7 @@
<table class="list">
<colgroup>
<col class="row_id" />
+ <col class="name" />
<col class="description" />
<col class="status" />
<col class="date" />
@@ -11,9 +12,10 @@
<thead>
<tr>
- <th class="title" colspan="7">Sprints</th>
+ <th class="title" colspan="8">Sprints</th>
<tr>
<th>#</th>
+ <th>Team Lead</th>
<th>Title</th>
<th>Status</th>
<th>Starts</th>
@@ -26,13 +28,14 @@
<tbody>
<% if @sprints.empty? %>
<tr>
- <td colspan="7">No sprints found...</td>
+ <td colspan="8">No sprints found...</td>
</tr>
<% else %>
<% @sprints.each_with_index do |sprint, index| %>
<% row_class = index%2 == 0 ? 'even' : 'odd' %>
<tr class="<%= row_class %>">
<td><%= "#{sprint.id}" %></td>
+ <td><%= mail_to sprint.team_lead.email, sprint.team_lead.display_name
%></td>
<td><%= sprint.title %></td>
<td><%= sprint.status_text %></td>
<td><%= show_date(sprint.start) %></td>
diff --git a/app/views/sprints/show.html.erb b/app/views/sprints/show.html.erb
index 2368d6d..a5a3264 100644
--- a/app/views/sprints/show.html.erb
+++ b/app/views/sprints/show.html.erb
@@ -33,6 +33,11 @@
</tr>
<tr>
+ <td class="label">Team lead:</td>
+ <td class="value"><%= mail_to @sprint.team_lead.email,
@sprint.team_lead.display_name %></td>
+ </tr>
+
+ <tr>
<td class="label">Starts:</td>
<td class="value"><%= show_date((a)sprint.start) %></td>
</tr>
diff --git a/db/migrate/027_add_team_lead_to_sprint.rb
b/db/migrate/027_add_team_lead_to_sprint.rb
new file mode 100644
index 0000000..e90fea9
--- /dev/null
+++ b/db/migrate/027_add_team_lead_to_sprint.rb
@@ -0,0 +1,38 @@
+# 02y_add_team_lead_to_sprint.rb
+# Copyright (C) 2009, Darryl L. Pierce <mcpierce(a)gmail.com>
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <
http://www.gnu.org/licenses/>.
+#
+
+class AddTeamLeadToSprint < ActiveRecord::Migration
+ def self.up
+ Sprint.transaction do
+ add_column :sprints, :team_lead_id, :integer
+
+ Sprint.find(:all).each do |sprint|
+ sprint.team_lead_id = sprint.product.owner_id
+ sprint.save
+ end
+
+ change_column :sprints, :team_lead_id, :integer, :null => false
+
+ execute 'alter table sprints add constraint fk_sprint_team_lead
+ foreign key (team_lead_id) references users(id)'
+ end
+ end
+
+ def self.down
+ remove_column :sprints, :team_lead_id
+ end
+end
diff --git a/test/fixtures/sprints.yml b/test/fixtures/sprints.yml
index 170ba19..704c0c0 100644
--- a/test/fixtures/sprints.yml
+++ b/test/fixtures/sprints.yml
@@ -5,6 +5,7 @@ active_sprint:
duration: 28
goals: Get things done.
status: <%= Sprint::STATUS_ACTIVE %>
+ team_lead_id: <%= Fixtures.identify(:projxp_owner) %>
inactive_sprint:
product_id: <%= Fixtures.identify(:projxp_web) %>
@@ -13,6 +14,7 @@ inactive_sprint:
duration: 28
goals: Got stuff done.
status: <%= Sprint::STATUS_PLANNED %>
+ team_lead_id: <%= Fixtures.identify(:projxp_owner) %>
closed_sprint:
product_id: <%= Fixtures.identify(:projxp_web) %>
@@ -21,3 +23,4 @@ closed_sprint:
duration: 28
goals: Get more stuff done.
status: <%= Sprint::STATUS_CLOSED %>
+ team_lead_id: <%= Fixtures.identify(:projxp_owner) %>
diff --git a/test/functional/sprints_controller_test.rb
b/test/functional/sprints_controller_test.rb
index 2432972..f39094a 100644
--- a/test/functional/sprints_controller_test.rb
+++ b/test/functional/sprints_controller_test.rb
@@ -39,10 +39,11 @@ class SprintsControllerTest < ActionController::TestCase
raise "Nonowner and owner must be different people!" if @owner.id ==
@nonowner.id
@new_sprint = {
- :title => "New Sprint",
- :start => Date.today,
- :duration => 28,
- :goals => "Get lots of things done!"
+ :title => "New Sprint",
+ :start => Date.today,
+ :duration => 28,
+ :goals => "Get lots of things done!",
+ :team_lead_id => @owner.id
}
@user_story = user_stories(:create_user_story_web_service_api)
diff --git a/test/unit/sprint_test.rb b/test/unit/sprint_test.rb
index d7c2fa6..fa8e5b5 100644
--- a/test/unit/sprint_test.rb
+++ b/test/unit/sprint_test.rb
@@ -24,12 +24,12 @@ class SprintTest < ActiveSupport::TestCase
def setup
@product = products(:projxp_web)
- @new_sprint = Sprint.new(
- :product_id => @product.id,
- :title => 'Sprint1',
- :start => Date.today - 7,
- :duration => 14,
- :goals => "Things I'd like to accomplish.")
+ @new_sprint = Sprint.new(:product_id => @product.id,
+ :title => 'Sprint1',
+ :start => Date.today- - 7,
+ :duration => 14,
+ :goals => "Things I'd like to
accomplish.",
+ :team_lead_id => @product.owner.id)
@developer = users(:mcpierce)
raise "Developer is not a member of the product team." unless
@product.is_member?(@developer)
@@ -84,6 +84,13 @@ class SprintTest < ActiveSupport::TestCase
flunk 'A sprint must have goals.' if @new_sprint.valid?
end
+ # Ensures that a sprint must have a team lead.
+ def test_valid_fails_without_team_lead
+ @new_sprint.team_lead_id = nil
+
+ flunk 'A sprint must have a team lead.' if @new_sprint.valid?
+ end
+
# Ensures that an unhealthy sprint returns the appropriate value.
def test_health_for_unhealthy_sprint
@new_sprint.backlog_items = @unhealthy_backlog
@@ -123,6 +130,7 @@ class SprintTest < ActiveSupport::TestCase
def test_end_date
# the sprint was set at 14 days, starting 7 days ago, so the end date should
# be 6 days from now
- assert_equal Date.today + 6, @new_sprint.end_date, "End date calculation is
wrong."
+ assert_equal @new_sprint.start + (@new_sprint.duration - 1), @new_sprint.end_date,
+ "End date calculation is wrong."
end
end
--
1.6.0.6