When a user estimates the remaining hours on a backlog item,
accepts, drops, completes, reopens, defers or marks an item as
blocked, then the product's RSS feed is updated.
Signed-off-by: Darryl L. Pierce <mcpierce(a)gmail.com>
---
app/controllers/items_controller.rb | 32 +++++++++++---
app/views/items/show.html.erb | 18 ++++++++
doc/ChangeLog | 1 +
test/functional/items_controller_test.rb | 60 ++++-----------------------
test/functional/user_items_test.rb | 65 ++++++++++++++++++++++++++++++
5 files changed, 118 insertions(+), 58 deletions(-)
diff --git a/app/controllers/items_controller.rb b/app/controllers/items_controller.rb
index 29addad..6ed0e74 100644
--- a/app/controllers/items_controller.rb
+++ b/app/controllers/items_controller.rb
@@ -97,9 +97,11 @@ class ItemsController < ApplicationController
BacklogItem.transaction do
if @backlog_item.user_can_accept?(@user)
@backlog_item.assign_to_user(@user)
- if @backlog_item.save
+ add_product_rss_entry("Backlog item ##{(a)backlog_item.id} accepted.",
+ "#{(a)user.display_name} accepted
\"#{(a)backlog_item.user_story.title}\".")
+ if @backlog_item.save && @backlog_item.sprint.product.save
respond_to do |format|
- flash[:message] = "This backlog item is now owned by
#{(a)backlog_item.owner.display_name}."
+ flash[:message] = "This backlog item is now owned by
#{(a)user.display_name}."
format.html {redirect_to params[:source] ? params[:source] :
item_path(@backlog_item)}
end
else
@@ -115,7 +117,9 @@ class ItemsController < ApplicationController
def drop
BacklogItem.transaction do
if @backlog_item.user_can_drop?(@user)
- if @backlog_item.drop
+ add_product_rss_entry("Backlog item ##{(a)backlog_item.id} dropped.",
+ "#{(a)user.display_name} dropped
\"#{(a)backlog_item.user_story.title}\".")
+ if @backlog_item.drop && @backlog_item.sprint.product.save
flash[:message] = "This item has been dropped."
respond_to do |format|
format.html {redirect_to items_path(:sprint => @backlog_item.sprint)}
@@ -133,7 +137,9 @@ class ItemsController < ApplicationController
def complete
BacklogItem.transaction do
if @backlog_item.user_can_complete?(@user)
- if @backlog_item.complete
+ add_product_rss_entry("Backlog item ##{(a)backlog_item.id} completed.",
+ "#{(a)user.display_name} completed
\"#{(a)backlog_item.user_story.title}\".")
+ if @backlog_item.complete && @backlog_item.sprint.product.save
respond_to do |format|
flash[:message] = "This item is now marked as completed."
format.html {redirect_to items_path(:sprint => @backlog_item.sprint)}
@@ -151,7 +157,9 @@ class ItemsController < ApplicationController
def reopen
BacklogItem.transaction do
if @backlog_item.user_can_reopen?(@user)
- if @backlog_item.reopen
+ add_product_rss_entry("Backlog item ##{(a)backlog_item.id} reopened.",
+ "#{(a)user.display_name} reopened
\"#{(a)backlog_item.user_story.title}\".")
+ if @backlog_item.reopen && @backlog_item.sprint.product.save
flash[:message] = "This backlog item has been reopened."
respond_to do |format|
format.html {redirect_to params[:source] ? params[:source] :
item_path(@backlog_item)}
@@ -169,7 +177,8 @@ class ItemsController < ApplicationController
def defer
BacklogItem.transaction do
@backlog_item.defer
-
+ add_product_rss_entry("Backlog item ##{(a)backlog_item.id} deferred.",
+ "#{(a)user.display_name} deferred
\"#{(a)backlog_item.user_story.title}\".")
if @backlog_item.save
flash[:message] = "Item has been deferred."
respond_to do |format|
@@ -186,8 +195,10 @@ class ItemsController < ApplicationController
if @backlog_item.can_estimate?(@user)
old_hours = @backlog_item.remaining_hours
@backlog_item.update_remaining_hours((a)hours.to_f, @user)
+ add_product_rss_entry("Backlog item estimate changed.",
+ "Hours changed from #{old_hours} to #{(a)hours.to_f} by
#{(a)user.display_name}.")
BacklogItem.transaction do
- if @backlog_item.save
+ if @backlog_item.save and @backlog_item.sprint.product.save
respond_to do |format|
flash[:message] = "Estimated hours updated from #{old_hours} to
#{@hours} hours."
format.html { redirect_to item_path(@backlog_item) }
@@ -229,6 +240,8 @@ class ItemsController < ApplicationController
flash[:message] = "Item blocked status unchanged."
end
end
+ add_product_rss_entry("Backlog item ##{(a)backlog_item.id} is blocked.",
+ "#{(a)user.display_name} has marked this item as blocked:
\"#{(a)backlog_item.user_story.title}\".")
if @backlog_item.save && @message.save
respond_to do |format|
format.html {redirect_to item_path(@backlog_item)}
@@ -301,6 +314,11 @@ class ItemsController < ApplicationController
report_error "You may not mark this item as blocked." unless
@backlog_item.can_mark_blocked?(@user)
end
+ def add_product_rss_entry(title, content)
+ product = @backlog_item.sprint.product
+ product.rss_entries << create_rss_entry(title, item_path(@backlog_item),
content, @user)
+ end
+
def path_to_list
if @sprint || params[:sprint]
@sprint = Sprint.find_by_id(params[:sprint]) unless @sprint
diff --git a/app/views/items/show.html.erb b/app/views/items/show.html.erb
index 87f30ae..dc64d13 100644
--- a/app/views/items/show.html.erb
+++ b/app/views/items/show.html.erb
@@ -26,6 +26,24 @@
</dd>
<% end %>
</dl>
+
+ <% if @backlog_item.can_estimate?(@user) %>
+ <div>
+ <%= form_tag(estimate_item_path(@backlog_item)) %>
+ <table class="edit">
+ <caption>Update estimated hours</caption>
+ <tbody>
+ <tr>
+ <td class="label">Hours</td>
+ <td class="value"><%= text_field_tag :hours,
@backlog_item.remaining_hours %></td>
+ </tr>
+ <tr>
+ <td class="buttons" colspan="2"><%= submit_tag
"Update" %></td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <% end %>
</div>
<% if @backlog_item.blocked? %>
diff --git a/doc/ChangeLog b/doc/ChangeLog
index b1de50a..9e1aaa8 100644
--- a/doc/ChangeLog
+++ b/doc/ChangeLog
@@ -41,6 +41,7 @@ Change Log (0.3.0):
* #208 - Blocker history is shown for backlog items.
* #209 - A blocked item is shown in a different color on the item list page.
* #210 - Epics with no user stories should only be deleted. (BUG)
+ * #211 - Backlog item events show up in the product's RSS feed.
* #213 - Users can disable receiving emails when replies are posted.
* #216 - The user's most recent postings are displayed on their details page.
* #217 - All of a user's posts can be viewed.
diff --git a/test/functional/items_controller_test.rb
b/test/functional/items_controller_test.rb
index ca721a6..ff2c074 100644
--- a/test/functional/items_controller_test.rb
+++ b/test/functional/items_controller_test.rb
@@ -217,56 +217,6 @@ class ItemsControllerTest < ActionController::TestCase
assert !BacklogItem.find_by_id((a)pending_item.id), "Item should have been
deleted."
end
- # Ensures that anonymous users can't update estimaetes.
- def test_estimate_as_anonymous
- post :estimate
-
- assert_redirected_to login_path
- end
-
- # Ensures that a valid item id is required.
- def test_estimate_with_invalid_item
- post :estimate, {}, {:user_id => @item_owner.id}
-
- assert_redirected_to error_path
- end
-
- # Ensures that an hours estimate is required.
- def test_estimate_without_hours
- post :estimate, {:id => @item.id}, {:user_id => @item_owner.id}
-
- assert_redirected_to error_path
- assert_equal @item.remaining_hours, BacklogItem.find_by_id((a)item.id).remaining_hours,
- "Hours should not have been updated."
- end
-
- # Ensures that the sprint must be active for an estimate to be updated.
- def test_estimate_on_planned_sprint
- post :estimate, {:id => @pending_item.id}, {:user_id => @item_owner.id}
-
- assert_redirected_to error_path
- assert_equal @pending_item.remaining_hours,
BacklogItem.find_by_id((a)pending_item.id).remaining_hours,
- "Remaining hours should not have been updated!"
- end
-
- # Ensures that only the item owner can update the estimate hours.
- def test_estimate_as_nonowner
- post :estimate, {:id => @item.id, :hours => @item.remaining_hours + 1},
{:user_id => @item_nonowner.id}
-
- assert_redirected_to error_path
- assert_equal @item.remaining_hours,
BacklogItem.find_by_id((a)item.id).remaining_hours,
- "Remaining hours should not have been updated."
- end
-
- # Ensures that updating an estimate works as expected.
- def test_estimate
- post :estimate, {:id => @item.id, :hours => 1.7}, {:user_id =>
@item_owner.id}
-
- assert_redirected_to item_path(@item)
- assert_equal 1.7, BacklogItem.find_by_id((a)item.id).remaining_hours.to_f,
- "Hours should have been updated."
- end
-
# Ensures that anonymous users can't set the blocked status for an item.
def test_blocked_as_anonymous
get :blocked
@@ -354,12 +304,17 @@ class ItemsControllerTest < ActionController::TestCase
# Ensures that update the item's status works as expected.
def test_update_blocked
- put :update_blocked, {:id => @item.id, :blocked => true, :message =>
{:posted => Time.current, :body => 'Message', :author_id =>
@item.owner_id}}, {:user_id => @item.owner_id}
+ count = @item.sprint.product.rss_entries.size
+ put :update_blocked,
+ {:id => @item.id, :blocked => true, :message => {:posted => Time.current,
:body => 'Message', :author_id => @item.owner_id}},
+ {:user_id => @item.owner_id}
assert_redirected_to item_path(@item)
result = BacklogItem.find_by_id((a)item.id)
assert result.blocked, "Item should have been marked as blocked."
assert_equal "Message", result.blocker_message.body, "The message was
not properly saved."
+ newcount = Product.find_by_id((a)item.sprint.product_id).rss_entries.size
+ assert_equal count + 1, newcount, "An RSS entry should have been created for
this event."
end
# Ensures that anonymous users cannot defer items.
@@ -385,10 +340,13 @@ class ItemsControllerTest < ActionController::TestCase
# Ensure that deferring works as expected.
def test_defer
+ count = @item.sprint.product.rss_entries.size
put :defer, {:id => @item.id}, {:user_id => @team_lead.id}
assert_redirected_to items_path(:sprint => @item.sprint)
result = BacklogItem.find_by_id((a)item.id)
assert result.deferred?, "Item should have been deferred."
+ newcount = Product.find_by_id((a)item.sprint.product_id).rss_entries.size
+ assert_equal count + 1, newcount, "An RSS entry should have been created for
this event."
end
end
diff --git a/test/functional/user_items_test.rb b/test/functional/user_items_test.rb
index b1dbf18..7db7d91 100644
--- a/test/functional/user_items_test.rb
+++ b/test/functional/user_items_test.rb
@@ -73,6 +73,59 @@ class UserItemsTest < ActionController::TestCase
raise "user is a member!" if @other_product.is_member?(@nonmember)
end
+ # Ensures that anonymous users can't update estimaetes.
+ def test_estimate_as_anonymous
+ post :estimate
+
+ assert_redirected_to login_path
+ end
+
+ # Ensures that a valid item id is required.
+ def test_estimate_with_invalid_item
+ post :estimate, {}, {:user_id => @owned_item.owner_id}
+
+ assert_redirected_to error_path
+ end
+
+ # Ensures that an hours estimate is required.
+ def test_estimate_without_hours
+ post :estimate, {:id => @owned_item.id}, {:user_id => @owned_item.owner_id}
+
+ assert_redirected_to error_path
+ assert_equal @owned_item.remaining_hours,
BacklogItem.find_by_id((a)owned_item.id).remaining_hours,
+ "Hours should not have been updated."
+ end
+
+ # Ensures that the sprint must be active for an estimate to be updated.
+ def test_estimate_on_planned_sprint
+ post :estimate, {:id => @pending_item.id}, {:user_id => @member.id}
+
+ assert_redirected_to error_path
+ assert_equal @pending_item.remaining_hours,
BacklogItem.find_by_id((a)pending_item.id).remaining_hours,
+ "Remaining hours should not have been updated!"
+ end
+
+ # Ensures that only the item owner can update the estimate hours.
+ def test_estimate_as_nonowner
+ post :estimate, {:id => @owned_item.id, :hours => @owned_item.remaining_hours +
1}, {:user_id => @nonmember.id}
+
+ assert_redirected_to error_path
+ assert_equal @owned_item.remaining_hours,
BacklogItem.find_by_id((a)owned_item.id).remaining_hours,
+ "Remaining hours should not have been updated."
+ end
+
+ # Ensures that updating an estimate works as expected.
+ def test_estimate
+ count = @owned_item.sprint.product.rss_entries.size
+ post :estimate, {:id => @owned_item.id, :hours => 1.7}, {:user_id =>
@owned_item.owner_id}
+
+ assert_redirected_to item_path(@owned_item)
+ assert_equal 1.7, BacklogItem.find_by_id((a)owned_item.id).remaining_hours.to_f,
+ "Hours should have been updated."
+ newcount = Product.find_by_id((a)owned_item.sprint.product_id).rss_entries.size
+ assert_equal count + 1, newcount, "A new RSS entry should have been
added."
+ end
+
# Ensures that you have to be logged in.
def test_accept_as_anonymous
get :accept
@@ -123,12 +176,15 @@ class UserItemsTest < ActionController::TestCase
# Ensures that accepting works as expected.
def test_accept
+ count = @unowned_item.sprint.product.rss_entries.size
get :accept,{:id => @unowned_item.id},{:user_id => @member.id}
assert_redirected_to item_path(@unowned_item)
result = BacklogItem.find_by_id((a)unowned_item.id)
assert_equal @member.id, result.owner_id, "Owner should have been
assigned."
assert result.assigned?, "Item should be marked as assigned."
+ newcount = Product.find_by_id((a)unowned_item.sprint.product_id).rss_entries.size
+ assert_equal count + 1, newcount, "An RSS entry should have been created for
this event."
end
# Ensures an anonymous user can't drop an item.
@@ -156,6 +212,7 @@ class UserItemsTest < ActionController::TestCase
# Ensures that dropping works as expected.
def test_drop
+ count = @owned_item.sprint.product.rss_entries.size
get :drop, {:id => @owned_item.id},{:user_id => @member.id}
assert_redirected_to items_path(:sprint => @sprint)
@@ -163,6 +220,8 @@ class UserItemsTest < ActionController::TestCase
result = BacklogItem.find_by_id((a)owned_item.id)
assert !result.owner_id,"Owner should have been removed."
assert result.pending?, "Items hould be marked as pending."
+ newcount = Product.find_by_id((a)owned_item.sprint.product_id).rss_entries.size
+ assert_equal count + 1, newcount, "An RSS entry should have been created for
this event."
end
# Ensures an anonymous user can't complete an item.
@@ -190,11 +249,14 @@ class UserItemsTest < ActionController::TestCase
# Ensures that completing works as expected.
def test_complete
+ count = @owned_item.sprint.product.rss_entries.size
get :complete,{:id => @owned_item.id}, {:user_id => @owned_item.owner_id}
assert_redirected_to items_path(:sprint => @sprint)
result = BacklogItem.find_by_id((a)owned_item.id)
assert result.completed?, "Item should be marked as completed."
+ newcount = Product.find_by_id((a)owned_item.sprint.product_id).rss_entries.size
+ assert_equal count + 1, newcount, "An RSS entry should have been created for
this event."
end
# Ensures an anonymous user can't reopen an item.
@@ -231,11 +293,14 @@ class UserItemsTest < ActionController::TestCase
# Ensures that reopening works as expected.
def test_reopen
+ count = @closed_item.sprint.product.rss_entries.size
get :reopen,{:id => @closed_item.id},{:user_id => @member.id}
assert_redirected_to item_path(@closed_item)
result = BacklogItem.find_by_id((a)closed_item.id)
assert_nil result.owner_id, "Should not have been reassigned."
assert result.pending?, "Item should be marked as pending."
+ newcount = Product.find_by_id((a)item.sprint.product_id).rss_entries.size
+ assert_equal count + 1, newcount, "An RSS entry should have been created for
this event."
end
end
--
1.6.2.5