[PATCH] The owner column for unowned items contains an accept link. #183
by Darryl L. Pierce
If the item in the list is unowned, and the current user is allowed to
accept the item, then the item will contain a link to accept it.
Otherwise, it says "Nobody".
Signed-off-by: Darryl L. Pierce <mcpierce(a)gmail.com>
---
app/views/items/_list.html.erb | 12 +++++++++++-
doc/ChangeLog | 2 +-
2 files changed, 12 insertions(+), 2 deletions(-)
diff --git a/app/views/items/_list.html.erb b/app/views/items/_list.html.erb
index e3582ff..056a451 100644
--- a/app/views/items/_list.html.erb
+++ b/app/views/items/_list.html.erb
@@ -32,7 +32,17 @@
</td>
<% unless @this_user %>
- <td><%= link_to(item.owner.display_name, user_path(item.owner)) if item.owner %></td>
+ <td>
+ <% if item.owner %>
+ <%= link_to(item.owner.display_name, user_path(item.owner)) %>
+ <% else %>
+ <% if item.can_accept?(@user) %>
+ <%= link_to "Accept item...", accept_item_path(item), :confirm => "Accept this item? Are you sure?" %>
+ <% else %>
+ Nobody
+ <% end %>
+ <% end %>
+ </td>
<% end %>
<td><%= show_hours_as_ear(item) %>
diff --git a/doc/ChangeLog b/doc/ChangeLog
index 6ad0279..aeb4fce 100644
--- a/doc/ChangeLog
+++ b/doc/ChangeLog
@@ -1,13 +1,13 @@
Change Log (0.3.0):
* #156 - Backlog items can be marked as blocked.
* #167 - Blocker messages are included in the daily updates email.
- * #175 - Fixed display of products for an unapproved project. (BUG)
* #176 - Added a breadcrumb trail to the navigation bar.
* #177 - Epics cannot be created for unapproved projects.
* #178 - A product with no user stories cannot define a sprint.
* #179 - The sprint list page shows the number of backlog items per sprint.
* #181 - The estimated/actual/remaining hours are shown on the sprint list page.
* #182 - The member count on the sprint list goes to the member list page.
+ * #183 - Unown items have an accept link on the sprint backlog page.
* #199 - Admins can approve or reject proposed projects.
* #202 - Project admins are emailed when a new project is requested.
* #205 - Blocked backlog items show the blocker message on the details page.
--
1.6.0.6
14 years, 11 months
[PATCH] The blocker message is included in the daily email. #167
by Darryl L. Pierce
The email includes the text from the blocker message if an item is
marked as a blocker.
Signed-off-by: Darryl L. Pierce <mcpierce(a)gmail.com>
---
app/views/user_mailer/daily_updates.html.erb | 16 ++++++++++++++--
1 files changed, 14 insertions(+), 2 deletions(-)
diff --git a/app/views/user_mailer/daily_updates.html.erb b/app/views/user_mailer/daily_updates.html.erb
index 7b6d5b3..e7d05fb 100644
--- a/app/views/user_mailer/daily_updates.html.erb
+++ b/app/views/user_mailer/daily_updates.html.erb
@@ -9,12 +9,12 @@ Tasks performed:
<% end %>
<% end %>
-Open backlog items:
+Open backlog items (* indicates a blocked item):
<% if !@open_items || @open_items.empty? %>
None.
<% else %>
<% @open_items.each_with_index do |item, index| %>
- <%= "#{index + 1} #{item.user_story.title}" %>
+ <%= "#{index + 1} #{item.user_story.title} #{item.blocked? ? '*' : ''}" %>
<% end %>
<% end %>
@@ -26,3 +26,15 @@ Closed backlog items:
<%= "#{index + 1} #{item.user_story.title}" %>
<% end %>
<% end %>
+
+Blocked backlog items:
+<% if !@open_items || @open_items.empty? %>
+ None.
+<% else %>
+ <% @open_items.each_with_index do |item, index| %>
+ <% if item.blocked? %>
+ <%= "#{index + 1} #{item.user_story.title}:" %>
+ <%= RedCloth.new(item.blocker_message.body).to_html %>
+ <% end %>
+ <% end %>
+<% end %>
--
1.6.0.6
14 years, 11 months
[PATCH] Blocker history is displayed for a backlog item. #208
by Darryl L. Pierce
Added some javascript methods for toggling an element's visible state.
Added a new div to the backlog item details page. This element contains
the list of blocker messages for the current item. Each item is a link
to a page to show that message and it's thread.
Signed-off-by: Darryl L. Pierce <mcpierce(a)gmail.com>
---
app/views/items/show.html.erb | 25 +++++++++++++++++++++++++
app/views/layouts/default.html.erb | 35 +++++++++++++++++------------------
doc/ChangeLog | 2 +-
public/javascripts/alerts.js | 15 ---------------
public/javascripts/application.js | 21 +++++++++++++++++++++
public/stylesheets/details.css | 21 +++++++++++++++++++++
6 files changed, 85 insertions(+), 34 deletions(-)
delete mode 100644 public/javascripts/alerts.js
diff --git a/app/views/items/show.html.erb b/app/views/items/show.html.erb
index 6ba5f99..3041fd4 100644
--- a/app/views/items/show.html.erb
+++ b/app/views/items/show.html.erb
@@ -26,8 +26,33 @@
</dd>
<% end %>
</dl>
+
+ <% if @backlog_item.blocked? %>
+ <div id="blocker-messages">
+ <div id="heading">Blocking Message History - <a href="#" onclick="toggle_blocker_messages('blocker-message-history',this); false">Show</a></div>
+ <div id="blocker-message-history">
+ <ul>
+ <% @backlog_item.messages.each do |message| %>
+ <li>
+ <%= link_to "#{get_first_sentence(message.body)}", message_path(message) %>
+ <%= ": Posted #{show_date(message.posted, true)} by #{message.author.display_name}" %></li>
+ <% end %>
+ </ul>
+ </div>
+ </div>
</div>
+ <script type="text/javascript">
+ function toggle_blocker_messages(boxid,element) {
+ show_or_hide_element(boxid);
+ element.innerHTML = element_is_visible(boxid) ? 'Hide' : 'Show';
+ }
+
+ hide_element('blocker-message-history');
+ </script>
+
+ <% end %>
+
<div id="task-list">
<%= render :partial => 'tasks/list', :locals => {:tasks => @backlog_item.tasks} %>
</div>
diff --git a/app/views/layouts/default.html.erb b/app/views/layouts/default.html.erb
index c2c0cd8..6ac4a5a 100644
--- a/app/views/layouts/default.html.erb
+++ b/app/views/layouts/default.html.erb
@@ -9,7 +9,6 @@
<%= stylesheet_link_tag "forms.css" %>
<%= stylesheet_link_tag "details.css" %>
- <%= stylesheet_link_tag "alerts.css" %>
<%= stylesheet_link_tag "projxp.css" %>
<%= stylesheet_link_tag "sidebar.css" %>
<%= stylesheet_link_tag "tables.css" %>
@@ -34,25 +33,25 @@
<%= link_to "ProjXP", 'http://projxp.fedorahosted.org/' %>
</div>
- <div id="breadcrumbs">
- <%= link_to "Home", "/" %>
- <% if @breadcrumbs %>
- <% @breadcrumbs.each do |crumb| %>
- >
- <% if crumb.url? %>
- <%= link_to crumb.text, crumb.url %>
- <% else %>
- <%= crumb.text %>
- <% end %>
- <% end %>
- <% end %>
- </div>
+ <div id="breadcrumbs">
+ <%= link_to "Home", "/" %>
+ <% if @breadcrumbs %>
+ <% @breadcrumbs.each do |crumb| %>
+ >
+ <% if crumb.url? %>
+ <%= link_to crumb.text, crumb.url %>
+ <% else %>
+ <%= crumb.text %>
+ <% end %>
+ <% end %>
+ <% end %>
+ </div>
<ul id="site-nav">
<li><%= link_to "Projects (#{@project_count})", projects_path %></li>
- <li><%= link_to "Epics (#{@epic_count})", epics_path %></li>
+ <li><%= link_to "Epics (#{@epic_count})", epics_path %></li>
<li><%= link_to "Products (#{@product_count})", products_path %></li>
- <li><%= link_to "User Stories (#{@user_story_count})", stories_path %></li>
+ <li><%= link_to "User Stories (#{@user_story_count})", stories_path %></li>
<li><%= link_to "Sprints (#{@sprint_count})", sprints_path %></li>
<li><%= link_to "Users (#{@user_count})", users_path %></li>
<li><%= link_to "Reports", :controller => :report, :action => :index %></li>
@@ -71,12 +70,12 @@
<%# else, use javascript to show a fancier message box %>
<div id="message-box">
- <a href="#" id="close" onclick="hide_message_box('message-box')">X</a>
+ <a href="#" id="close" onclick="hide_element('message-box')">X</a>
<p><%= flash[:message] %></p>
</div>
<script type="text/javascript">
- show_message_box('message-box');
+ show_element('message-box');
</script>
<% end %>
diff --git a/doc/ChangeLog b/doc/ChangeLog
index 3a7ce21..38de3f6 100644
--- a/doc/ChangeLog
+++ b/doc/ChangeLog
@@ -12,6 +12,7 @@ Change Log (0.3.0):
* #205 - Blocked backlog items show the blocker message on the details page.
* #206 - Users can reply to a blocker message.
* #207 - An item re-marked as blocked starts a new message thread.
+ * #208 - Blocker history is shown for backlog items.
* #209 - A blocked item is shown in a different color on the item list page.
Change Log (0.2.0):
@@ -52,4 +53,3 @@ Change Log (0.2.0):
* #151 - Epics can be viewed from the projects list page.
* #152 - Users can view all epic stories for a project.
* #155 - Epic details can be viewed.
-
diff --git a/public/javascripts/alerts.js b/public/javascripts/alerts.js
deleted file mode 100644
index 0f70f09..0000000
--- a/public/javascripts/alerts.js
+++ /dev/null
@@ -1,15 +0,0 @@
-function show_message_box(boxid) {
- document.getElementById(boxid).style.display = 'block';
-}
-
-function hide_message_box(boxid) {
- document.getElementById(boxid).style.display = 'none';
-}
-
-function toggle_message_box(boxid, show_flag) {
- if (show_flag) {
- show_message_box(boxid);
- } else {
- hide_message_box(boxid);
- }
-}
\ No newline at end of file
diff --git a/public/javascripts/application.js b/public/javascripts/application.js
index c989f56..0efbb1c 100644
--- a/public/javascripts/application.js
+++ b/public/javascripts/application.js
@@ -15,3 +15,24 @@
// this program. If not, see <http://www.gnu.org/licenses/>.
//
+function show_element(boxid) {
+ document.getElementById(boxid).style.display = 'block';
+}
+
+function hide_element(boxid) {
+ document.getElementById(boxid).style.display = 'none';
+}
+
+function element_is_visible(boxid) {
+ return document.getElementById(boxid).style.display != 'none';
+}
+
+function show_or_hide_element(boxid) {
+ show = (element_is_visible(boxid) == false);
+
+ if (show) {
+ show_element(boxid);
+ } else {
+ hide_element(boxid);
+ }
+}
diff --git a/public/stylesheets/details.css b/public/stylesheets/details.css
index 7cd3047..56315fc 100644
--- a/public/stylesheets/details.css
+++ b/public/stylesheets/details.css
@@ -33,3 +33,24 @@ page-details logo img {
#page-details dd a {
color: #0f0f0f;
}
+
+/* backlog items */
+
+#blocker-messages {
+ width: 100%;
+ border-top: solid 1px #000000;
+}
+
+#blocker-messages div#heading {
+ text-indent: 15px;
+ font-weight: bold;
+}
+
+#blocker-messages #heading #blocker-view {
+ text-decoration: none;
+}
+
+#blocker-message-history ul {
+ list-style-image: none;
+ list-style-type: none;
+}
--
1.6.0.6
14 years, 11 months
[PATCH] Users can reply to blocker messages. #206
by Darryl L. Pierce
Defined a new controller, MessagesController, to handle controller
replies.
On the message view page, the user can click to enter a reply. They are
then taken to a reply page where they can enter their message and save
it.
On the item details page, a link to reply to the message is shown. If
the user clicks the link they are taken to the reply page. After posting
their reply they're returned to the item page.
Signed-off-by: Darryl L. Pierce <mcpierce(a)gmail.com>
---
app/controllers/messages_controller.rb | 69 ++++++++++++++++++
app/helpers/messages_helper.rb | 2 +
app/models/message.rb | 3 +
app/views/items/show.html.erb | 6 ++
app/views/messages/_message.html.erb | 9 +++
app/views/messages/reply.html.erb | 43 +++++++++++
app/views/messages/show.html.erb | 36 +++++++++
config/routes.rb | 13 ++++
test/fixtures/messages.yml | 7 ++-
test/functional/messages_controller_test.rb | 105 +++++++++++++++++++++++++++
10 files changed, 292 insertions(+), 1 deletions(-)
create mode 100644 app/controllers/messages_controller.rb
create mode 100644 app/helpers/messages_helper.rb
create mode 100644 app/views/messages/_message.html.erb
create mode 100644 app/views/messages/reply.html.erb
create mode 100644 app/views/messages/show.html.erb
create mode 100644 test/functional/messages_controller_test.rb
diff --git a/app/controllers/messages_controller.rb b/app/controllers/messages_controller.rb
new file mode 100644
index 0000000..7d02297
--- /dev/null
+++ b/app/controllers/messages_controller.rb
@@ -0,0 +1,69 @@
+# messages_controller.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/>.
+#
+
+# +MessageController+ enforces business rules on instances of +Message+.
+class MessagesController < ApplicationController
+ before_filter :authenticated, :except => [:show]
+ before_filter :load_message
+ before_filter :get_landing_page, :except => [:show]
+
+ # GET /messages/1
+ def show
+
+ end
+
+ # GET /message/1/reply
+ def reply
+ @reply = Message.new
+ end
+
+ # POST /messages/1
+ def post_reply
+ Message.transaction do
+ @reply = Message.new(params[:reply])
+ @reply.posted = Time.current
+ @reply.author = @user
+ @reply.reference = @message
+ @message.replies << @reply
+
+ respond_to do |format|
+ if @message.save
+ flash[:message] = "Reply posted."
+ if @landing_page
+ format.html {redirect_to @landing_page}
+ else
+ format.html {redirect_to message_path(@message)}
+ end
+ else
+ @reply.valid?
+ format.html {render :action => :reply}
+ end
+ end
+ end
+ end
+
+ private
+
+ def load_message
+ @message = Message.find_by_id(params[:id])
+
+ report_error "Invalid or missing message." unless @message
+ end
+
+ def get_landing_page
+ @landing_page = params[:dest]
+ end
+end
diff --git a/app/helpers/messages_helper.rb b/app/helpers/messages_helper.rb
new file mode 100644
index 0000000..f1bca9f
--- /dev/null
+++ b/app/helpers/messages_helper.rb
@@ -0,0 +1,2 @@
+module MessagesHelper
+end
diff --git a/app/models/message.rb b/app/models/message.rb
index 023b3e6..58966f4 100644
--- a/app/models/message.rb
+++ b/app/models/message.rb
@@ -29,6 +29,9 @@ class Message < ActiveRecord::Base
belongs_to :author, :class_name => 'User', :foreign_key => :author_id
+ has_many :replies, :class_name => 'Message', :foreign_key => :reference_id
+ has_one :reference, :class_name => 'Message', :foreign_key => :reference_id
+
named_scope :blocker_messages, lambda {|backlog_item|
{
:conditions => ['id in (select message_id from backlog_items_messages where backlog_item_id = ?)', backlog_item.id],
diff --git a/app/views/items/show.html.erb b/app/views/items/show.html.erb
index 30c2da1..6ba5f99 100644
--- a/app/views/items/show.html.erb
+++ b/app/views/items/show.html.erb
@@ -17,6 +17,12 @@
<b>By <%= link_to @backlog_item.blocker_message.author.display_name, user_path((a)backlog_item.blocker_message.author) %>,
Posted <%= show_date((a)backlog_item.blocker_message.posted, true) %>:</b></p>
<p><%= RedCloth.new((a)backlog_item.blocker_message.body).to_html %></p>
+ <% unless @backlog_item.blocker_message.replies.empty? %>
+ <p>There are <%= link_to "#{(a)backlog_item.blocker_message.replies.size} replies", message_path((a)backlog_item.blocker_message) %> currently.</p>
+ <% end %>
+ <% if @user %>
+ <p><%= link_to "Reply to this message...", reply_message_path((a)backlog_item.blocker_message, :dest => request.request_uri) %></p>
+ <% end %>
</dd>
<% end %>
</dl>
diff --git a/app/views/messages/_message.html.erb b/app/views/messages/_message.html.erb
new file mode 100644
index 0000000..a4b637b
--- /dev/null
+++ b/app/views/messages/_message.html.erb
@@ -0,0 +1,9 @@
+<div class="message">
+ <div class="heading">
+ <div class="date">Posted <%= show_date((a)message.posted, true) %></div>
+ <div class="author">Posted by <%= link_to @message.author.display_name, user_path((a)message.author) %></div>
+ </div>
+ <div class="body">
+ <%= RedCloth.new((a)message.body).to_html %>
+ </div>
+</div>
diff --git a/app/views/messages/reply.html.erb b/app/views/messages/reply.html.erb
new file mode 100644
index 0000000..e621df5
--- /dev/null
+++ b/app/views/messages/reply.html.erb
@@ -0,0 +1,43 @@
+<div id="content-no-sidebar">
+ <% form_for :reply, @reply,:url => post_reply_message_path(@message) do |form| %>
+
+ <% if @landing_page %>
+ <%= hidden_field_tag :dest, @landing_page %>
+ <% end %>
+
+ <table class="edit">
+ <caption>Post a Reply To:</caption>
+ <tr>
+ <td class="label">Posted:</td>
+ <td class="value"><%= show_date((a)message.posted, true) %></td>
+ </tr>
+
+ <tr>
+ <td class="label">Author:</td>
+ <td class="value"><%= link_to @message.author.display_name, user_path((a)message.author) %></td>
+ </tr>
+
+ <tr>
+ <td class="label">Message:</td>
+ <td class="value">
+ <%= RedCloth.new((a)message.body).to_html %>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="label-required">Your comments:<br />(you can use wiki markup)</td>
+ <td class="value">
+ <%= form.text_area :body, :cols => 100, :rows => 10 %>
+ <%= error_message_on(:reply, :body) %>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="buttons" colspan="2">
+ <%= submit_tag "Post Reply" %>
+ </td>
+ </tr>
+ </table>
+
+ <% end %>
+</div>
diff --git a/app/views/messages/show.html.erb b/app/views/messages/show.html.erb
new file mode 100644
index 0000000..f5f9035
--- /dev/null
+++ b/app/views/messages/show.html.erb
@@ -0,0 +1,36 @@
+<div id="content-no-sidebar">
+ <%= render :partial => 'message', :locals => {:message => @message} %>
+
+ <% if @user %>
+ [ <%= link_to "Reply To This Message", reply_message_path(@message) %> ]
+ <% end %>
+
+ <table class="main-list">
+ <caption>Replies</caption>
+ <thead>
+ <th scope="col">#</th>
+ <th scope="col" class="name">Content</th>
+ <th scope="col">Posted</th>
+ <th scope="col">Replies</th>
+ </thead>
+
+ <tbody>
+ <% @message.replies.each do |reply| %>
+
+ <tr class="<%= cycle('odd', 'even') %>">
+ <td><%= link_to "#{reply.id}", message_path(reply) %></td>
+ <td class="name">
+ <%= link_to reply.author.display_name, user_path(reply.author) %>
+ <%= RedCloth.new(get_first_sentence(reply.body)).to_html %>
+ <% if reply.reference %>
+ <%= link_to "In reply to #{reply.reference_id}", message_path(reply.reference) %>
+ <% end %>
+ </td>
+ <td><%= show_date(reply.posted, true) %></td>
+ <td><%= reply.replies %></td>
+ </tr>
+
+ <% end %>
+ </tbody>
+ </table>
+</div>
diff --git a/config/routes.rb b/config/routes.rb
index b0040f0..e52c84e 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -17,6 +17,7 @@
ActionController::Routing::Routes.draw do |map|
map.admin "/admin", :controller => 'admin', :action => 'index'
+
map.resource :admin do |admin|
admin.resource :server, :controller => 'ServerSettings'
end
@@ -26,20 +27,25 @@ ActionController::Routing::Routes.draw do |map|
:approve => :put,
:reject => :put
}
+
map.resources :epics, :member =>
{
:close => :put,
:reopen => :put
}
+
map.resources :projects, :member =>
{
:approve => :put,
:reject => :put
}
+
map.resources :stories
+
map.resources :products do |product|
product.resources :roles
end
+
map.resources :sprints, :member =>
{
:plan => :get,
@@ -47,6 +53,7 @@ ActionController::Routing::Routes.draw do |map|
:status => :put,
:members => :get
}
+
map.resources :items, :member =>
{
:accept => :get,
@@ -57,6 +64,7 @@ ActionController::Routing::Routes.draw do |map|
:reopen => :get,
:estimate => :post
}
+
map.resources :users, :member =>
{
:backlog => :get,
@@ -68,6 +76,11 @@ ActionController::Routing::Routes.draw do |map|
map.resources :tasks
+ map.resources :messages, :member =>
+ {
+ :reply => :get,
+ :post_reply => :post
+ }
# You can have the root of your site routed with map.root -- just remember to
# delete public/index.html.
diff --git a/test/fixtures/messages.yml b/test/fixtures/messages.yml
index 7e54d3a..01c1baf 100644
--- a/test/fixtures/messages.yml
+++ b/test/fixtures/messages.yml
@@ -1,4 +1,9 @@
blocker_message:
author_id: <%= Fixtures.identify(:mcpierce) %>
- posted: <%= Date.today %>
+ posted: <%= Time.current %>
body: This is a blocked item.
+
+blocker_message_reply:
+ author_id: <%= Fixtures.identify(:jdonuts) %>
+ posted: <%= Time.current %>
+ body: This is a reply.
diff --git a/test/functional/messages_controller_test.rb b/test/functional/messages_controller_test.rb
new file mode 100644
index 0000000..a051dcd
--- /dev/null
+++ b/test/functional/messages_controller_test.rb
@@ -0,0 +1,105 @@
+# 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/>.
+#
+
+require File.dirname(__FILE__) + '/../test_helper'
+
+class MessagesControllerTest < ActionController::TestCase
+ def setup
+ @user = users(:mcpierce)
+ @message = messages(:blocker_message)
+ @landing_page = dashboard_path
+
+ @new_reply = {:body => 'This is a reply', :reference_id => @message.id}
+ end
+
+ # Ensures that a valid id is require to view a message.
+ def test_show_with_invalid_id
+ get :show
+
+ assert_redirected_to error_path
+ end
+
+ # Ensures that showing a message works as expected.
+ def test_show
+ get :show, {:id => @message.id}
+
+ assert_response :success
+ assert assigns['message'], "Failed to load a message."
+ assert_equal @message.id, assigns['message'].id, "Failed to load the correct message."
+ end
+
+ # Ensures that entering a reply requires the user log in.
+ def test_reply_as_anonymous
+ get :reply
+
+ assert_redirected_to login_path
+ end
+
+ # Ensures that entering a reply requires a valid id.
+ def test_reply_with_invalid_id
+ get :reply, { }, {:user_id => @user.id}
+
+ assert_redirected_to error_path
+ end
+
+ # Ensures that editing a reply works as expected.
+ def test_reply
+ get :reply, {:id => @message.id}, {:user_id => @user.id}
+
+ assert_response :success
+ assert assigns['reply'], "Failed to create a reply object."
+ end
+
+ # Ensures that anonymous users cannot post replies.
+ def test_post_reply_as_anonymous
+ post :post_reply
+
+ assert_redirected_to login_path
+ end
+
+ # Ensures that posting a reply requires a valid message id.
+ def test_post_reply_with_invalid_message
+ post :post_reply, { }, {:user_id => @user.id}
+
+ assert_redirected_to error_path
+ end
+
+ # Ensures that, if the message is malformed, then the user is redirected to the src page.
+ def test_post_reply_with_malformed_message
+ post :post_reply, {:id => @message.id, :dest => @landing_page, :reply => {}}, {:user_id => @user.id}
+
+ assert_template 'messages/reply'
+ end
+
+ # Ensures that a reply is posted as expected, and the user redirected to the landing page.
+ def test_post_reply_with_landing_page
+ post :post_reply, {:id => @message.id, :dest => @landing_page, :reply => @new_reply}, {:user_id => @user.id}
+
+ assert_redirected_to @landing_page
+ result = Message.find_by_id((a)message.id).replies.last
+ assert result, "Did not save the reply."
+ assert_equal @new_reply[:body], result.body, "Did not save the message."
+ end
+
+ # Ensures that a landing page is provided.
+ def test_post_reply
+ post :post_reply, {:id => @message.id, :reply => @new_reply}, {:user_id => @user.id}
+
+ assert_redirected_to message_path(@message)
+ result = Message.find_by_id((a)message.id).replies.last
+ assert result, "Did not save the reply."
+ assert_equal @new_reply[:body], result.body, "Did not save the message."
+ end
+end
--
1.6.0.6
14 years, 12 months
[PATCH] Blocked items are shown in a different color. #209
by Darryl L. Pierce
On the item list page blocked backlog items are shown with a red
background, to call them out from other items.
Signed-off-by: Darryl L. Pierce <mcpierce(a)gmail.com>
---
app/views/items/_list.html.erb | 7 ++++++-
public/stylesheets/tables.css | 11 ++++++++++-
2 files changed, 16 insertions(+), 2 deletions(-)
diff --git a/app/views/items/_list.html.erb b/app/views/items/_list.html.erb
index 0130153..90262c8 100644
--- a/app/views/items/_list.html.erb
+++ b/app/views/items/_list.html.erb
@@ -25,11 +25,16 @@
<% sprint = item.sprint %>
<% product = sprint.product %>
- <tr class="state-<%= item.state %>">
+ <tr class="<% if item.blocked? %>
+ blocked
+ <% else %>
+ state-<%= item.state %>
+ <% end %>">
<td><%= "#{item.id}" %></td>
<td><%= "#{item.user_story.priority}" %></td>
<td class="name">
<%= link_to "#{item.user_story.title} (#{item.state_text})", item_path(item) %>
+ <% if item.blocked? %><strong>This item is currently BLOCKED.</strong><% end %>
<%= RedCloth.new(get_first_sentence(item.user_story.description)).to_html %>
<%= link_to "For sprint: #{sprint.title}", sprint_path(item.sprint) %>
</td>
diff --git a/public/stylesheets/tables.css b/public/stylesheets/tables.css
index c6f279e..a6a4786 100644
--- a/public/stylesheets/tables.css
+++ b/public/stylesheets/tables.css
@@ -97,6 +97,15 @@ table.main-list tr.status-3 { /* canceled */
}
/* backlog item states */
+table.main-list tr a {
+ color: #fff;
+}
+
+table.main-list tr.blocked { /* blocked */
+ background: #a00000;
+ color: #ff0;
+}
+
table.main-list tr.state-0 { /* pending */
background: #cf9;
}
@@ -114,7 +123,7 @@ table.main-list tr.state-3 { /* dropped */
}
table.main-list tr.state-4 { /* canceled */
- background: #f00;
+ background: #202020;
}
/* edit table styles */
--
1.6.0.6
14 years, 12 months
[PATCH] Blocker message shown on the details page. #205
by Darryl L. Pierce
Added a new section to the details page that is displayed if the item is
current blocked.
Signed-off-by: Darryl L. Pierce <mcpierce(a)gmail.com>
---
app/models/backlog_item.rb | 5 +++++
app/views/items/show.html.erb | 10 ++++++++++
2 files changed, 15 insertions(+), 0 deletions(-)
diff --git a/app/models/backlog_item.rb b/app/models/backlog_item.rb
index d3f2b7b..f546e46 100644
--- a/app/models/backlog_item.rb
+++ b/app/models/backlog_item.rb
@@ -117,6 +117,11 @@ class BacklogItem < ActiveRecord::Base
end
end
+ # Returns whether the item is current blocked or not.
+ def blocked?
+ blocked
+ end
+
# Sets the item as blocked.
def set_as_blocked(message)
self.blocked = true
diff --git a/app/views/items/show.html.erb b/app/views/items/show.html.erb
index dd10b1c..1801e12 100644
--- a/app/views/items/show.html.erb
+++ b/app/views/items/show.html.erb
@@ -9,6 +9,16 @@
<dt>Description</dt>
<dd><%= RedCloth.new((a)backlog_item.user_story.description).to_html %></dd>
+
+ <% if @backlog_item.blocked? %>
+ <dt>This item is currently blocked.</dt>
+ <dd>
+ <p>
+ <b>By <%= link_to @backlog_item.blocker_message.author.display_name, user_path((a)backlog_item.blocker_message.author) %>,
+ Posted <%= show_date((a)backlog_item.blocker_message.posted, true) %>:</b></p>
+ <p><%= RedCloth.new((a)backlog_item.blocker_message.body).to_html %></p>
+ </dd>
+ <% end %>
</dl>
</div>
--
1.6.0.6
14 years, 12 months
[PATCH] Items can be marked as blocked. #156
by Darryl L. Pierce
THIS PATCH REQUIRES A MIGRATION.
On the item details page, the owner of an active backlog item can set
the item as either blocked or not blocked.
If the item is blocked, the owner is able to enter a blocking message.
There they can set the blocked status and enter a message describing how
the item is blocked. This is stored in the messages table and mapped
back to the item via the blocker_messages table. If the user then goes
back to the blocker page they will only see the existing blocked
message.
If the user unmarks an item, then the blocked flag is removed. If they
then remark it as blocked, a new message gets saved.
Signed-off-by: Darryl L. Pierce <mcpierce(a)gmail.com>
---
app/controllers/items_controller.rb | 40 +++++++++++-
app/models/backlog_item.rb | 19 +++++-
app/models/blocker_message.rb | 23 ++++++
app/views/home/login.html.erb | 2 -
app/views/items/blocked.html.erb | 36 ++++++++++
app/views/items/show.html.erb | 9 +--
config/routes.rb | 12 ++--
db/migrate/013_create_backlog_items.rb | 1 +
db/migrate/019_create_blocker_messages.rb | 35 ++++++++++
public/javascripts/alerts.js | 8 ++
test/fixtures/backlog_items.yml | 8 ++
test/fixtures/blocker_messages.yml | 3 +
test/fixtures/messages.yml | 4 +-
test/functional/items_controller_test.rb | 104 +++++++++++++++++++++++++---
test/unit/blocker_message_test.rb | 8 ++
test/unit/sprint_test.rb | 4 +-
16 files changed, 284 insertions(+), 32 deletions(-)
create mode 100644 app/models/blocker_message.rb
create mode 100644 app/views/items/blocked.html.erb
create mode 100644 db/migrate/019_create_blocker_messages.rb
create mode 100644 test/fixtures/blocker_messages.yml
create mode 100644 test/unit/blocker_message_test.rb
diff --git a/app/controllers/items_controller.rb b/app/controllers/items_controller.rb
index 61a12d5..b24f511 100644
--- a/app/controllers/items_controller.rb
+++ b/app/controllers/items_controller.rb
@@ -21,8 +21,10 @@ class ItemsController < ApplicationController
before_filter :authenticated, :except => [:index, :show]
before_filter :load_backlog_item, :except => [:index]
before_filter :verify_can_delete, :only => [:destroy]
- before_filter :verify_sprint_is_active, :only => [:accept, :estimate, :drop, :complete, :reopen]
+ before_filter :verify_sprint_is_active, :only => [:accept, :estimate, :drop, :complete, :reopen, :blocked, :update_blocked]
before_filter :verify_is_member, :only => [:accept, :drop, :complete, :reopen]
+ before_filter :verify_is_owner, :only => [:drop, :complete, :estimate, :blocked, :update_blocked]
+ before_filter :verify_can_mark_blocked, :only => [:blocked, :update_blocked]
before_filter :load_hours, :only => [:estimate]
before_filter :path_to_list, :only => [:index]
before_filter :path_to_one, :only => [:show]
@@ -151,6 +153,33 @@ class ItemsController < ApplicationController
end
end
+ # GET /items/1/blocked
+ def blocked
+ params[:blocked] = @backlog_item.blocked
+ params[:message] = @backlog_item.blocked ? @backlog_item.blocker_message.body : ""
+ end
+
+ # PUT /items/1/update_blocked
+ def update_blocked
+ Message.transaction do
+ if params[:blocked]
+ message = Message.create(:author => @user, :body => params[:message], :posted => Date.today)
+ @backlog_item.set_as_blocked(message)
+ flash[:message] = "This item is now marked as blocked."
+ else
+ @backlog_item.blocked = false
+ flash[:message] = "This item is no longer marked as blocked."
+ end
+ if @backlog_item.save
+ respond_to do |format|
+ format.html {redirect_to item_path(@backlog_item)}
+ end
+ else
+ report_error "Unable to update item's blocked state at this time."
+ end
+ end
+ end
+
private
def unsupported
@@ -178,10 +207,19 @@ class ItemsController < ApplicationController
def verify_sprint_is_active
report_error "This sprint is not in the active state." unless @sprint.active?
end
+
def verify_is_member
report_error "You must be a member of the sprint team." unless @sprint.is_team_member?(@user)
end
+ def verify_is_owner
+ report_error "You must be owner for this backlog item." unless @backlog_item.owner?(@user)
+ end
+
+ def verify_can_mark_blocked
+ report_error "You may not mark this item as blocked." unless @backlog_item.can_mark_blocked?(@user)
+ end
+
def path_to_list
if @sprint || params[:sprint]
@sprint = Sprint.find_by_id(params[:sprint]) unless @sprint
diff --git a/app/models/backlog_item.rb b/app/models/backlog_item.rb
index f445d0b..884d7e2 100644
--- a/app/models/backlog_item.rb
+++ b/app/models/backlog_item.rb
@@ -36,6 +36,9 @@ class BacklogItem < ActiveRecord::Base
has_many :tasks
has_many :remaining_hours_estimates
+ has_many :blocker_messages, :foreign_key => 'item_id', :dependent => :destroy
+ has_many :messages, :through => :blocker_messages
+
STATE_PENDING = 0
STATE_ASSIGNED = 1
STATE_COMPLETED = 2
@@ -93,6 +96,10 @@ class BacklogItem < ActiveRecord::Base
:estimated_on => Date.today)
end
+ def blocker_message
+ messages.first unless messages.empty?
+ end
+
# Reports if the task if owned.
def owned?
owner_id && (state == STATE_ASSIGNED)
@@ -111,6 +118,12 @@ class BacklogItem < ActiveRecord::Base
end
end
+ # Sets the item as blocked.
+ def set_as_blocked(message)
+ self.blocked = true
+ blocker_messages.build(:item => self, :message_id => message.id)
+ end
+
# Sets the state to dropped.
def drop
self.owner = nil
@@ -187,12 +200,12 @@ class BacklogItem < ActiveRecord::Base
[STATE_COMPLETED, STATE_CANCELED].include?(state)
end
- private
-
def owner?(user)
- user && owner && (user.id == owner.id)
+ user && owner && (user.id == owner_id)
end
+ private
+
def product_owner?(user)
user && (sprint.product.owner_id == user.id)
end
diff --git a/app/models/blocker_message.rb b/app/models/blocker_message.rb
new file mode 100644
index 0000000..a18f589
--- /dev/null
+++ b/app/models/blocker_message.rb
@@ -0,0 +1,23 @@
+# blocker_message.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/>.
+#
+
+# +BlockerMessage+ associates a blocked +BacklogItem+ with its descriptive
+# +Message+ describing the blocked state.
+class BlockerMessage < ActiveRecord::Base
+ belongs_to :item, :class_name => 'BacklogItem', :foreign_key => 'item_id'
+ belongs_to :message, :dependent => :destroy
+end
diff --git a/app/views/home/login.html.erb b/app/views/home/login.html.erb
index dcd9624..3e076c1 100644
--- a/app/views/home/login.html.erb
+++ b/app/views/home/login.html.erb
@@ -21,5 +21,3 @@
</table>
<% end %>
</div>
-
-
diff --git a/app/views/items/blocked.html.erb b/app/views/items/blocked.html.erb
new file mode 100644
index 0000000..143fac5
--- /dev/null
+++ b/app/views/items/blocked.html.erb
@@ -0,0 +1,36 @@
+<div id="content-no-sidebar">
+ <% form_tag update_blocked_item_path(@backlog_item), :method => :put do %>
+
+ <table class="edit">
+ <caption>
+ <%= @backlog_item.user_story.title %>
+ </caption>
+
+ <tr>
+ <td class="label" />
+ <td class="value">
+ <%= check_box_tag :blocked, true, params[:blocked] %>
+ <%= label_tag :blocked, 'This item is current blocked.' %>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="label">Author:</td>
+ <td class="value"><%= @user.display_name %></td>
+ </tr>
+
+ <tr>
+ <td class="label-required">Message body<br />(can use wiki markup)</td>
+ <td class="value">
+ <%= text_area_tag :message, params[:message], :cols => 100, :rows => 10, :readonly => @backlog_item.blocked %></td>
+ </tr>
+
+ <tr>
+ <td colspan="2" class="buttons">
+ <%= submit_tag %>
+ </td>
+ </tr>
+ </table>
+
+ <% end %>
+</div>
diff --git a/app/views/items/show.html.erb b/app/views/items/show.html.erb
index 5e8363e..3dbc04f 100644
--- a/app/views/items/show.html.erb
+++ b/app/views/items/show.html.erb
@@ -12,11 +12,6 @@
</dl>
</div>
- <%# if this is the owner of the item then allow them to mark it as a blocked %>
- <% if @backlog_item.owner?(@user) %>
- <%= render :partial => 'blocker', :locals => {:item => @backlog_item} %>
- <% end %>
-
<div id="task-list">
<%= render :partial => 'tasks/list', :locals => {:tasks => @backlog_item.tasks} %>
</div>
@@ -37,6 +32,10 @@
:confirm => "Accept this item? Are you sure?", :class => "command" %>
<% end %>
+ <% if @backlog_item.can_mark_blocked?(@user) %>
+ <%= link_to "Set blocked status", blocked_item_path(@backlog_item), :class => "command" %>
+ <% end %>
+
<% if @backlog_item.can_drop?(@user) %>
<%= link_to "Drop this item...", drop_item_path(@backlog_item, :url => request.request_uri),
:confirm => "Drop this item? Are you sure?", :class => "command" %>
diff --git a/config/routes.rb b/config/routes.rb
index 4f19aec..58385ab 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -49,11 +49,13 @@ ActionController::Routing::Routes.draw do |map|
}
map.resources :items, :member =>
{
- :accept => :get,
- :drop => :get,
- :complete => :get,
- :reopen => :get,
- :estimate => :post
+ :accept => :get,
+ :blocked => :get,
+ :update_blocked => :put,
+ :drop => :get,
+ :complete => :get,
+ :reopen => :get,
+ :estimate => :post
}
map.resources :users, :member =>
{
diff --git a/db/migrate/013_create_backlog_items.rb b/db/migrate/013_create_backlog_items.rb
index 44d1138..22ef1f2 100644
--- a/db/migrate/013_create_backlog_items.rb
+++ b/db/migrate/013_create_backlog_items.rb
@@ -25,6 +25,7 @@ class CreateBacklogItems < ActiveRecord::Migration
t.decimal :estimated_hours, :null => false, :default => 0.0, :precision => 5, :scale => 2
t.boolean :completed, :null => false, :default => false
t.boolean :discovered, :null => false, :default => false
+ t.boolean :blocked, :null => false, :default => false
t.timestamps
end
diff --git a/db/migrate/019_create_blocker_messages.rb b/db/migrate/019_create_blocker_messages.rb
new file mode 100644
index 0000000..342fec3
--- /dev/null
+++ b/db/migrate/019_create_blocker_messages.rb
@@ -0,0 +1,35 @@
+# 019_create_item_block_messages.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 CreateBlockerMessages < ActiveRecord::Migration
+ def self.up
+ create_table :blocker_messages do |t|
+ t.integer :item_id, :null => false
+ t.integer :message_id, :null => false
+
+ t.timestamps
+ end
+
+ execute 'alter table blocker_messages add constraint fk_blocker_message_item
+ foreign key (item_id) references backlog_items(id)'
+ execute 'alter table blocker_messages add constraint fk_blocker_message_message
+ foreign key (message_id) references messages(id)'
+ end
+
+ def self.down
+ drop_table :blocker_messages
+ end
+end
diff --git a/public/javascripts/alerts.js b/public/javascripts/alerts.js
index bbc52a4..0f70f09 100644
--- a/public/javascripts/alerts.js
+++ b/public/javascripts/alerts.js
@@ -5,3 +5,11 @@ function show_message_box(boxid) {
function hide_message_box(boxid) {
document.getElementById(boxid).style.display = 'none';
}
+
+function toggle_message_box(boxid, show_flag) {
+ if (show_flag) {
+ show_message_box(boxid);
+ } else {
+ hide_message_box(boxid);
+ }
+}
\ No newline at end of file
diff --git a/test/fixtures/backlog_items.yml b/test/fixtures/backlog_items.yml
index 4089b67..1b0cc23 100644
--- a/test/fixtures/backlog_items.yml
+++ b/test/fixtures/backlog_items.yml
@@ -37,3 +37,11 @@ closed_sprint_backlog_item:
owner_id: <%= Fixtures.identify(:mcpierce) %>
estimated_hours: 4.5
state: <%= BacklogItem::STATE_COMPLETED %>
+
+blocked_backlog_item:
+ sprint_id: <%= Fixtures.identify(:active_sprint) %>
+ user_story_id: <%= Fixtures.identify(:create_update_profile) %>
+ owner_id: <%= Fixtures.identify(:mcpierce) %>
+ estimated_hours: 5.0
+ state: <%= BacklogItem::STATE_ASSIGNED %>
+ blocked: true
diff --git a/test/fixtures/blocker_messages.yml b/test/fixtures/blocker_messages.yml
new file mode 100644
index 0000000..4c0aac8
--- /dev/null
+++ b/test/fixtures/blocker_messages.yml
@@ -0,0 +1,3 @@
+blocked_item:
+ message_id: <%= Fixtures.identify(:blocker_message)
+ item_id: <%+ Fixtures.identify(::blocked_Backlog_item)
diff --git a/test/fixtures/messages.yml b/test/fixtures/messages.yml
index 5712932..7e54d3a 100644
--- a/test/fixtures/messages.yml
+++ b/test/fixtures/messages.yml
@@ -1,4 +1,4 @@
-blocker_message_one:
+blocker_message:
author_id: <%= Fixtures.identify(:mcpierce) %>
posted: <%= Date.today %>
- body: This is a comment.
+ body: This is a blocked item.
diff --git a/test/functional/items_controller_test.rb b/test/functional/items_controller_test.rb
index 589dfa6..3e7ebec 100644
--- a/test/functional/items_controller_test.rb
+++ b/test/functional/items_controller_test.rb
@@ -52,11 +52,17 @@ class ItemsControllerTest < ActionController::TestCase
@item_owner = @item.owner
@item_nonowner = @owner
raise "Item owner and nonowner cannot be the same person!" if @item_owner.id == @item_nonowner.id
+
+ @completed_item = backlog_items(:closed_backlog_item)
+ raise "Item must be marked closed." unless @completed_item.completed?
+
+ @blocked_item = backlog_items(:blocked_backlog_item)
+ raise "Item must be marked as blocked." unless @blocked_item.blocked
end
# Ensures that viewing an index works as expected.
def test_index_with_sprint
- get :index, { :sprint_id => @sprint.id }
+ get :index, {:sprint_id => @sprint.id}
assert_response :success
end
@@ -70,14 +76,14 @@ class ItemsControllerTest < ActionController::TestCase
# Ensures that a valid backlog item is required.
def test_show_with_invalid_item
- get :show, { }
+ get :show, {}
assert_redirected_to error_path
end
# Ensures the item can be shown.
def test_show
- get :show, { :id => @item.id }
+ get :show, {:id => @item.id}
assert_response :success
assert assigns['backlog_item'], "Failed to load a backlog item."
@@ -95,14 +101,14 @@ class ItemsControllerTest < ActionController::TestCase
# Ensures that the item is required.
def test_destroy_with_invalid_item
- delete :destroy, { }, { :user_id => @owner.id }
+ delete :destroy, {}, {:user_id => @owner.id}
assert_redirected_to error_path
end
# Ensures that only the owner can delete.
def test_destroy_as_nonowner
- get :destroy, { :id => @item.id }, { :user_id => @nonowner.id }
+ get :destroy, {:id => @item.id}, {:user_id => @nonowner.id}
assert_redirected_to error_path
assert BacklogItem.find_by_id((a)item.id), "Item should not have been deleted."
@@ -110,7 +116,7 @@ class ItemsControllerTest < ActionController::TestCase
# Ensures that only an item on a pending sprint can be deleted.
def test_destroy_for_active_sprint
- get :destroy, { :id => @item.id }, { :user_id => @owner.id }
+ get :destroy, {:id => @item.id}, {:user_id => @owner.id}
assert_redirected_to error_path
assert BacklogItem.find_by_id((a)item.id), "Item should not have been deleted."
@@ -118,7 +124,7 @@ class ItemsControllerTest < ActionController::TestCase
# Ensures that an item on a pending sprint can be deleted.
def test_destroy
- get :destroy, { :id => @pending_item.id }, { :user_id => @owner.id }
+ get :destroy, {:id => @pending_item.id}, {:user_id => @owner.id}
assert_redirected_to items_path(:sprint => @pending_item.sprint_id)
assert !BacklogItem.find_by_id((a)pending_item.id), "Item should have been deleted."
@@ -133,14 +139,14 @@ class ItemsControllerTest < ActionController::TestCase
# Ensures that a valid item id is required.
def test_estimate_with_invalid_item
- post :estimate, { }, { :user_id => @item_owner.id }
+ 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 }
+ 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,
@@ -149,7 +155,7 @@ class ItemsControllerTest < ActionController::TestCase
# Ensures that the sprint must be active for an estimate to be updated.
def test_estimate_on_inactive_sprint
- post :estimate, { :id => @pending_item.id }, { :user_id => @item_owner.id }
+ 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,
@@ -158,7 +164,7 @@ class ItemsControllerTest < ActionController::TestCase
# 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 }
+ 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,
@@ -167,10 +173,84 @@ class ItemsControllerTest < ActionController::TestCase
# Ensures that updating an estimate works as expected.
def test_estimate
- post :estimate, { :id => @item.id, :hours => 1.7 }, { :user_id => @item_owner.id }
+ 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
+
+ assert_redirected_to login_path
+ end
+
+ # Ensures that an item id is required.
+ def test_blocked_with_invalid_item_id
+ get :block, {}, {:user_id => @item_owner.id}
+
+ assert_redirected_to error_path
+ end
+
+ # Ensures that only the item owner can set an item as blocked.
+ def test_blocked_with_nonowner
+ get :blocked, {:id => @item.id}, {:user_id => @item_nonowner.id}
+
+ assert_redirected_to error_path
+ end
+
+ # Ensures that only an active item can be marked as blocked.
+ def test_blocked_with_completed_item
+ get :blocked, {:id => @completed_item.id}, {:user_id => @completed_item.owner_id}
+
+ assert_redirected_to error_path
+ end
+
+ # Ensures that marking an item as blocked works as expected.
+ def test_blocked
+ get :blocked, {:id => @item.id},{:user_id => @item.owner_id}
+
+ assert_response :success
+ assert assigns['backlog_item'], "Failed to load backlog item."
+ end
+
+ # Ensures an anonymous user can't update an items blocked status.
+ def test_update_blocked_as_anonymous
+ put :update_blocked
+
+ assert_redirected_to login_path
+ end
+
+ # Ensures that a valid item id is required.
+ def test_update_blocked_with_invalid_id
+ put :update_blocked, {}, {:user_id => @owner.id}
+
+ assert_redirected_to error_path
+ end
+
+ # Ensures that only the item owner can update an item's blocked status.
+ def test_update_blocked_as_nonowner
+ put :update_blocked, {:id => @item.id}, {:user_id => @item_nonowner.id}
+
+ assert_redirected_to error_path
+ end
+
+ # Ensures that only an active item can be updated.
+ def test_update_blocked_with_completed_item
+ put :update_blocked, {:id => @completed_item.id}, {:user_id => @completed_item.owner_id}
+
+ assert_redirected_to error_path
+ end
+
+ # Ensures that update the item's status works as expected.
+ def test_update_blocked
+ put :update_blocked, {:id => @item.id, :blocked => true, :message => "Message"}, {: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."
+ end
end
diff --git a/test/unit/blocker_message_test.rb b/test/unit/blocker_message_test.rb
new file mode 100644
index 0000000..122ce49
--- /dev/null
+++ b/test/unit/blocker_message_test.rb
@@ -0,0 +1,8 @@
+require 'test_helper'
+
+class ItemBlockMessageTest < ActiveSupport::TestCase
+ # Replace this with your real tests.
+ def test_truth
+ assert true
+ end
+end
diff --git a/test/unit/sprint_test.rb b/test/unit/sprint_test.rb
index 43c2f47..a3ca8b5 100644
--- a/test/unit/sprint_test.rb
+++ b/test/unit/sprint_test.rb
@@ -110,7 +110,7 @@ class SprintTest < ActiveSupport::TestCase
# Ensures the estimated hours for a sprint are accurate.
def test_estimated_hours
- assert_equal 12.5,
+ assert_equal 17.5,
@existing_sprint.estimated_hours,
'The estimated hours are wrong.'
end
@@ -124,7 +124,7 @@ class SprintTest < ActiveSupport::TestCase
# Ensures the remaining hours are correct.
def test_remaining_hours
- assert_equal 8.0,
+ assert_equal 13.0,
@existing_sprint.remaining_hours.to_f,
'The remaining hours are wrong.'
end
--
1.6.0.6
15 years
[PATCH] Backlog items have types. #138
by Darryl L. Pierce
From: Darryl Pierce <mcpierce(a)gmail.com>
A boolean flag determines whether a backlog item was discovered or
whether it was planned. If the flag is true then it was discovered
(added after the sprint was started). Otherwise, if false, it was
planned.
Signed-off-by: Darryl L. Pierce <mcpierce(a)gmail.com>
---
db/migrate/013_create_backlog_items.rb | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)
diff --git a/db/migrate/013_create_backlog_items.rb b/db/migrate/013_create_backlog_items.rb
index 59832f7..44d1138 100644
--- a/db/migrate/013_create_backlog_items.rb
+++ b/db/migrate/013_create_backlog_items.rb
@@ -24,6 +24,7 @@ class CreateBacklogItems < ActiveRecord::Migration
t.boolean :completed, :null => false, :default => false
t.decimal :estimated_hours, :null => false, :default => 0.0, :precision => 5, :scale => 2
t.boolean :completed, :null => false, :default => false
+ t.boolean :discovered, :null => false, :default => false
t.timestamps
end
--
1.6.0.6
15 years
[PATCH] Introduces the message model and table. #204
by Darryl L. Pierce
THIS PATCH REQUIRES A MIGRATION
The new table contains a foreign key to the author, and includes the
data and body of the message. It also includes a reference to another
message if the current is in reply to another message.
Signed-off-by: Darryl L. Pierce <mcpierce(a)gmail.com>
---
app/models/message.rb | 2 +
db/migrate/018_create_messages.rb | 38 +++++++++++++++++++++++++++++++++++++
test/fixtures/messages.yml | 11 ++++++++++
test/unit/message_test.rb | 8 +++++++
4 files changed, 59 insertions(+), 0 deletions(-)
create mode 100644 app/models/message.rb
create mode 100644 db/migrate/018_create_messages.rb
create mode 100644 test/fixtures/messages.yml
create mode 100644 test/unit/message_test.rb
diff --git a/app/models/message.rb b/app/models/message.rb
new file mode 100644
index 0000000..590e3e3
--- /dev/null
+++ b/app/models/message.rb
@@ -0,0 +1,2 @@
+class Message < ActiveRecord::Base
+end
diff --git a/db/migrate/018_create_messages.rb b/db/migrate/018_create_messages.rb
new file mode 100644
index 0000000..7d1197a
--- /dev/null
+++ b/db/migrate/018_create_messages.rb
@@ -0,0 +1,38 @@
+# 018_create_messages.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 CreateMessages < ActiveRecord::Migration
+ def self.up
+ create_table :messages do |t|
+ t.integer :author_id, :null => false
+ t.datetime :posted, :null => false
+ t.integer :reference_id, :null => true
+ t.text :body, :null => false
+
+ t.timestamps
+ end
+
+ execute 'alter table messages add constraint fk_message_author
+ foreign key(author_id) references users(id)'
+ execute 'alter table messages add constraint fk_message_references
+ foreign key(reference_id) references messages(id)'
+ end
+
+ def self.down
+ drop_table :messages
+ end
+end
diff --git a/test/fixtures/messages.yml b/test/fixtures/messages.yml
new file mode 100644
index 0000000..a621608
--- /dev/null
+++ b/test/fixtures/messages.yml
@@ -0,0 +1,11 @@
+# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
+
+one:
+ author_id: 1
+ posted: 2009-05-06 08:19:35
+ body: MyText
+
+two:
+ author_id: 1
+ posted: 2009-05-06 08:19:35
+ body: MyText
diff --git a/test/unit/message_test.rb b/test/unit/message_test.rb
new file mode 100644
index 0000000..eb11506
--- /dev/null
+++ b/test/unit/message_test.rb
@@ -0,0 +1,8 @@
+require 'test_helper'
+
+class MessageTest < ActiveSupport::TestCase
+ # Replace this with your real tests.
+ def test_truth
+ assert true
+ end
+end
--
1.6.0.6
15 years
[PATCH] Member count links to member list for sprints. #182
by Darryl L. Pierce
The column is a link to the list of team members for that particular
sprint.
Signed-off-by: Darryl L. Pierce <mcpierce(a)gmail.com>
---
app/controllers/sprints_controller.rb | 2 +-
app/views/sprints/index.html.erb | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/controllers/sprints_controller.rb b/app/controllers/sprints_controller.rb
index 8a8881a..f84e216 100644
--- a/app/controllers/sprints_controller.rb
+++ b/app/controllers/sprints_controller.rb
@@ -203,7 +203,7 @@ class SprintsController < ApplicationController
end
end
- # GET /product/1/sprint/1/members
+ # GET /sprint/1/members
def members
add_breadcrumb "Members", members_sprint_path(@sprint)
end
diff --git a/app/views/sprints/index.html.erb b/app/views/sprints/index.html.erb
index d731f2a..bd5b84b 100644
--- a/app/views/sprints/index.html.erb
+++ b/app/views/sprints/index.html.erb
@@ -35,7 +35,7 @@
user_path(sprint.team_lead) %>
</td>
<td><%= link_to "#{sprint.backlog_items.size}", items_path(:sprint => sprint) %></td>
- <td><%= sprint.members.size %></td>
+ <td><%= link_to "#{sprint.members.size}", members_sprint_path(sprint) %></td>
<td><%= sprint.status_text %></td>
<td><%= "#{show_date(sprint.start)} (#{sprint.duration} days)" %></td>
</tr>
--
1.6.2
15 years