[PATCH] A user cannot delete a backlog item from sprint if that item has tasks. #45
by LAN-SUN-LUK Benjamin
Very short patch for this defect.
Signed-off-by: Benjamin LAN-SUN-LUK <benjamin.lan-sun-luk(a)supinfo.com>
---
app/models/backlog_item.rb | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/app/models/backlog_item.rb b/app/models/backlog_item.rb
index 4fd4501..299b43a 100644
--- a/app/models/backlog_item.rb
+++ b/app/models/backlog_item.rb
@@ -33,7 +33,7 @@ class BacklogItem < ActiveRecord::Base
belongs_to :owner, :class_name => 'User', :foreign_key => 'owner_id'
belongs_to :backup, :class_name => 'User', :foreign_key => 'backup_id'
- has_many :tasks
+ has_many :tasks, :dependent => :destroy
has_many :remaining_hours_estimates
STATE_PENDING = 0
--
1.6.0.2
15 years, 4 months
[PROJXP [PATCH] Added a mailing list to the product definition. #64
by Darryl L. Pierce
From: Darryl L. Pierce <dpierce(a)redhat.com>
A mailing list is now validated for format, but is not required
for a product to created.
Created a new colgroup for details tables to make sure the label
is shown at a minimum size.
Signed-off-by: Darryl L. Pierce <dpierce(a)redhat.com>
---
app/models/product.rb | 6 +++++
app/views/products/_edit.html.erb | 20 ++++++++++++++----
app/views/products/show.html.erb | 18 ++++++++++++++--
db/migrate/021_add_mailing_list_to_product.rb | 26 +++++++++++++++++++++++++
db/schema.rb | 25 +++--------------------
public/stylesheets/details.css | 8 +++++++
public/stylesheets/forms.css | 8 +++++++
test/unit/product_test.rb | 21 +++++++++++++++++--
8 files changed, 100 insertions(+), 32 deletions(-)
create mode 100644 db/migrate/021_add_mailing_list_to_product.rb
diff --git a/app/models/product.rb b/app/models/product.rb
index fc2523f..18a6fe6 100644
--- a/app/models/product.rb
+++ b/app/models/product.rb
@@ -27,6 +27,12 @@ class Product < ActiveRecord::Base
validates_presence_of :name,
:message => 'A product must have a name.'
+ validates_format_of :mailing_list,
+ :allow_blank => true,
+ :allow_nil => true,
+ :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i,
+ :message => 'Mailing list must be a valid email address.'
+
belongs_to :project
belongs_to :owner,
:class_name => 'User',
diff --git a/app/views/products/_edit.html.erb b/app/views/products/_edit.html.erb
index 60c818c..e554591 100644
--- a/app/views/products/_edit.html.erb
+++ b/app/views/products/_edit.html.erb
@@ -11,6 +11,11 @@
<%= hidden_field :product, :project_id %>
<table class="edit">
+ <colgroup>
+ <col class="label" />
+ <col class="value" />
+ </colgroup>
+
<tbody>
<tr>
<td class="label-required">Project</td>
@@ -18,11 +23,11 @@
</tr>
<tr>
- <td class="label-required">Name</td>
- <td class="value">
- <%= form.text_field :name %>
- <%= error_message_on(:product, :name) %>
- </td>
+ <td class="label-required">Name</td>
+ <td class="value">
+ <%= form.text_field :name %>
+ <%= error_message_on(:product, :name) %>
+ </td>
</tr>
<tr>
@@ -43,6 +48,11 @@
</tr>
<tr>
+ <td class="label">Mailing list</td>
+ <td class="value"><%= form.text_field :mailing_list %></td>
+ </tr>
+
+ <tr>
<td class="label">Description</td>
<td class="value">
<%= form.text_area :description %>
diff --git a/app/views/products/show.html.erb b/app/views/products/show.html.erb
index 3cbaf63..ac843a2 100644
--- a/app/views/products/show.html.erb
+++ b/app/views/products/show.html.erb
@@ -1,4 +1,9 @@
<table class="detail">
+ <colgroup>
+ <col class="label" />
+ <col class="value" />
+ </colgroup>
+
<thead>
<tr>
<th class="title" colspan="2"><%= "Details For #{(a)product.name}" %></th>
@@ -25,6 +30,13 @@
<td class="value"><%= link_to @product.owner.display_name, user_path((a)product.owner) %></td>
</tr>
+ <% unless @product.mailing_list.empty? %>
+ <tr>
+ <td class="label">Mailing list:</td>
+ <td class="value"><%= mail_to @product.mailing_list, @product.mailing_list %>
+ </tr>
+ <% end %>
+
<tr>
<td class="label">For Project:</td>
<td class="value"><%= link_to @product.project.name, project_path((a)product.project) %></td>
@@ -35,9 +47,9 @@
<td class="value">
<%= link_to "#{(a)product.active_roles.size > 0 ? @product.active_roles.size : 'No'} Active Members",
product_roles_path(@product) %>
- <% if @product.is_owner?(@user) %>
- <%= "(#{(a)product.pending_roles.size > 0 ? @product.pending_roles.size : 'No'} Role Requests)" %>
- <% end %>
+ <% if @product.is_owner?(@user) %>
+ <%= "(#{(a)product.pending_roles.size > 0 ? @product.pending_roles.size : 'No'} Role Requests)" %>
+ <% end %>
</td>
</tr>
diff --git a/db/migrate/021_add_mailing_list_to_product.rb b/db/migrate/021_add_mailing_list_to_product.rb
new file mode 100644
index 0000000..a0c1ffa
--- /dev/null
+++ b/db/migrate/021_add_mailing_list_to_product.rb
@@ -0,0 +1,26 @@
+# 021_add_mailing_list_to_product.rb
+# Copyright (C) 2008, 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 AddMailingListToProduct < ActiveRecord::Migration
+ def self.up
+ add_column :products, :mailing_list, :string, :limit => 128, :null => true
+ end
+
+ def self.down
+ remove_column :products, :mailing_list
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 6215516..ce24b1e 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -9,7 +9,7 @@
#
# It's strongly recommended to check this file into your version control system.
-ActiveRecord::Schema.define(:version => 20) do
+ActiveRecord::Schema.define(:version => 21) do
create_table "backlog_items", :force => true do |t|
t.integer "sprint_id", :null => false
@@ -23,7 +23,6 @@ ActiveRecord::Schema.define(:version => 20) do
end
add_index "backlog_items", ["sprint_id", "user_story_id"], :name => "index_backlog_items_on_sprint_id_and_user_story_id", :unique => true
- add_index "backlog_items", ["user_story_id"], :name => "fk_backlog_items_user_story"
create_table "notifications", :force => true do |t|
t.integer "user_id"
@@ -43,21 +42,19 @@ ActiveRecord::Schema.define(:version => 20) do
t.boolean "is_approved", :default => false
end
- add_index "product_roles", ["user_id", "product_id"], :name => "index_product_roles_on_user_id_and_product_id", :unique => true
- add_index "product_roles", ["product_id"], :name => "fk_user_role_product"
- add_index "product_roles", ["role_id"], :name => "fk_user_role_role"
+ add_index "product_roles", ["product_id", "user_id"], :name => "index_product_roles_on_user_id_and_product_id", :unique => true
create_table "products", :force => true do |t|
t.integer "project_id"
t.integer "owner_id"
- t.string "name", :limit => 128
+ t.string "name", :limit => 128
t.text "description"
t.datetime "created_at"
t.datetime "updated_at"
+ t.string "mailing_list", :limit => 128
end
add_index "products", ["name"], :name => "index_products_on_name", :unique => true
- add_index "products", ["project_id"], :name => "fk_products_project"
create_table "projects", :force => true do |t|
t.integer "owner_id", :null => false
@@ -69,7 +66,6 @@ ActiveRecord::Schema.define(:version => 20) do
end
add_index "projects", ["name"], :name => "index_projects_on_name", :unique => true
- add_index "projects", ["owner_id"], :name => "fk_product_owner"
create_table "remaining_hours_estimates", :force => true do |t|
t.integer "backlog_item_id", :null => false
@@ -80,9 +76,6 @@ ActiveRecord::Schema.define(:version => 20) do
t.datetime "estimated_on", :null => false
end
- add_index "remaining_hours_estimates", ["backlog_item_id"], :name => "fk_remaining_hours_item"
- add_index "remaining_hours_estimates", ["user_id"], :name => "fk_remaining_hours_user"
-
create_table "roles", :force => true do |t|
t.string "name", :null => false
t.boolean "can_manage_backlog_items", :default => false, :null => false
@@ -114,8 +107,6 @@ ActiveRecord::Schema.define(:version => 20) do
t.integer "status", :default => 0, :null => false
end
- add_index "sprints", ["product_id"], :name => "fk_sprint_product"
-
create_table "tasks", :force => true do |t|
t.integer "backlog_item_id", :null => false
t.integer "primary_id", :null => false
@@ -127,10 +118,6 @@ ActiveRecord::Schema.define(:version => 20) do
t.datetime "updated_at"
end
- add_index "tasks", ["backlog_item_id"], :name => "fk_task_backlog_item"
- add_index "tasks", ["primary_id"], :name => "fk_task_primary"
- add_index "tasks", ["backup_id"], :name => "fk_task_backup"
-
create_table "user_privileges", :force => true do |t|
t.integer "user_id", :null => false
t.boolean "admin_projects", :default => false, :null => false
@@ -139,8 +126,6 @@ ActiveRecord::Schema.define(:version => 20) do
t.datetime "updated_at"
end
- add_index "user_privileges", ["user_id"], :name => "fk_privilege_user"
-
create_table "user_stories", :force => true do |t|
t.integer "product_id"
t.integer "priority"
@@ -151,8 +136,6 @@ ActiveRecord::Schema.define(:version => 20) do
t.datetime "updated_at"
end
- add_index "user_stories", ["product_id"], :name => "fk_user_story_product"
-
create_table "user_verifications", :force => true do |t|
t.integer "user_id", :null => false
t.string "token", :limit => 16, :null => false
diff --git a/public/stylesheets/details.css b/public/stylesheets/details.css
index 07ab512..82bacdf 100644
--- a/public/stylesheets/details.css
+++ b/public/stylesheets/details.css
@@ -1,3 +1,11 @@
+table.detail col.label {
+ min-width: 25%;
+}
+
+table.detail col.value {
+ width: 90%;
+}
+
table.detail {
width: 100%;
padding: 5px 15px 5px 15px;
diff --git a/public/stylesheets/forms.css b/public/stylesheets/forms.css
index 0452925..a1d1b7d 100644
--- a/public/stylesheets/forms.css
+++ b/public/stylesheets/forms.css
@@ -3,6 +3,14 @@
font-weight: bold;
}
+table.edit col.label {
+ min-width: 25%;
+}
+
+table.edit col.value {
+ width: 90%;
+}
+
table.edit, table.edit td.label, table.edit td.label-required {
background: #f7f7f7;
}
diff --git a/test/unit/product_test.rb b/test/unit/product_test.rb
index c994a15..e016e5d 100644
--- a/test/unit/product_test.rb
+++ b/test/unit/product_test.rb
@@ -26,9 +26,10 @@ class ProductTest < ActiveSupport::TestCase
@project = projects(:projxp)
@new_product = Product.new(
- :project_id => @project.id,
- :owner_id => @project.owner.id,
- :name => "Test Product")
+ :project_id => @project.id,
+ :owner_id => @project.owner.id,
+ :name => "Test Product",
+ :mailing_list => 'test(a)projxp.org')
@existing_product = products(:projxp_web)
@owner = @existing_product.owner
@@ -50,6 +51,20 @@ class ProductTest < ActiveSupport::TestCase
flunk "Products must have an owner." if @new_product.valid?
end
+ # Ensures that a mailing list must be well formed if present.
+ def test_valid_fails_with_bad_mailing_list
+ @new_product.mailing_list = 'farkle'
+
+ flunk "Products must have a well-formatted mailing list." if @new_product.valid?
+ end
+
+ # Ensures that a mailing list is not required.
+ def test_valid_without_mailing_list
+ @new_product.mailing_list = ""
+
+ flunk "A mailing list is not required." unless @new_product.valid?
+ end
+
# Ensures that a well-formed product is valid.
def test_valid
flunk "There is a general validation problem." unless @new_product.valid?
--
1.6.0.3
15 years, 4 months
[PROJXP [PATCH] Show a subset of product details when viewing product roles. #68
by Darryl L. Pierce
Signed-off-by: Darryl L. Pierce <mcpierce(a)gmail.com>
---
app/views/roles/_list.html.erb | 4 ----
app/views/roles/index.html.erb | 2 ++
2 files changed, 2 insertions(+), 4 deletions(-)
diff --git a/app/views/roles/_list.html.erb b/app/views/roles/_list.html.erb
index 8692122..d94e1c3 100644
--- a/app/views/roles/_list.html.erb
+++ b/app/views/roles/_list.html.erb
@@ -1,9 +1,5 @@
<% columns = 4 - (@this_user ? 1 : 0) + (@product ? 0 : 1) + (@product && @product.can_approve_roles?(@user) ? 1 : 0) %>
-<% if @product %>
- <%= link_to @product.name, product_path(@product) %>
-<% end %>
-
<table class="list">
<colgroup>
<col class="row_id" />
diff --git a/app/views/roles/index.html.erb b/app/views/roles/index.html.erb
index c32fe36..ce6b86a 100644
--- a/app/views/roles/index.html.erb
+++ b/app/views/roles/index.html.erb
@@ -1,2 +1,4 @@
+<%= render :partial => 'products/product', :object => @product %>
+
<%= will_paginate @product_roles %>
<%= render :partial => 'list' %>
--
1.6.0.3
15 years, 4 months
[PROJXP [PATCH] Show a subset of product details when viewing product roles. #68
by Darryl L. Pierce
Signed-off-by: Darryl L. Pierce <mcpierce(a)gmail.com>
---
app/views/roles/index.html.erb | 2 ++
1 files changed, 2 insertions(+), 0 deletions(-)
diff --git a/app/views/roles/index.html.erb b/app/views/roles/index.html.erb
index c32fe36..ce6b86a 100644
--- a/app/views/roles/index.html.erb
+++ b/app/views/roles/index.html.erb
@@ -1,2 +1,4 @@
+<%= render :partial => 'products/product', :object => @product %>
+
<%= will_paginate @product_roles %>
<%= render :partial => 'list' %>
--
1.6.0.3
15 years, 4 months
[PROJXP Use cases
by Darryl L. Pierce
I've started writing up the more formal use cases for current
functionality. Please take a look at what's finished so far and give
me any feedback, either for missing functionality or if there are
details not being captured.
https://fedorahosted.org/projxp/wiki/Use_Cases
--
Darryl L. Pierce <mcpierce(a)gmail.com>
Visit the Infobahn Offramp: <http://mcpierce.multiply.com>
"Bury me next to my wife. Nothing too fancy..." - Ulysses S. Grant
15 years, 4 months
[PROJXP [PATCH] Users can select to receive daily scrum emails. #18
by Darryl L. Pierce
This patch requires a migration as it adds a new table, notifications,
to the project.
Added a new action to the users controller, called notifications,
that takes posted data to update the user's notifications.
>From the dashboard the user can select to view their profile. On
the profile page are options for selecting notification types.
Side tasks:
Removed the "Owner" column from the user view since it's redundant
to show the owner's name when it's the owner that's being viewed.
Refactored the BacklogItem.closed? to be BacklogItem.completed? since
the item state is actually STATE_COMPLETED.
Fixed a ton of unit tests that were broken by a refactoring exercise.
Signed-off-by: Darryl L. Pierce <mcpierce(a)gmail.com>
---
app/controllers/sprints_controller.rb | 2 +
app/controllers/users_controller.rb | 31 +++++++++++-
app/models/backlog_item.rb | 2 +-
app/models/notifications.rb | 2 +
app/models/user.rb | 1 +
app/models/user_mailer.rb | 9 +++
app/views/home/dashboard.html.erb | 6 ++-
app/views/items/_list.html.erb | 17 +++++--
app/views/user_mailer/daily_updates.html.erb | 28 ++++++++++
app/views/users/_notifications.html.erb | 29 +++++++++++
app/views/users/show.html.erb | 7 ++-
config/initializers/schedules.rb | 69 +++++++++++++++++++++++---
config/routes.rb | 2 +-
config/schedules.yml.example | 11 +++--
db/migrate/020_create_notifications.rb | 19 +++++++
db/schema.rb | 30 ++++++++++-
public/stylesheets/lists.css | 2 +-
test/fixtures/notifications.yml | 6 ++
test/functional/tasks_controller_test.rb | 4 +-
test/functional/user_items_test.rb | 2 +-
test/functional/users_controller_test.rb | 40 ++++++++++++++-
test/unit/backlog_item_test.rb | 3 +-
test/unit/notifications_test.rb | 8 +++
23 files changed, 299 insertions(+), 31 deletions(-)
create mode 100644 app/models/notifications.rb
create mode 100644 app/views/user_mailer/daily_updates.html.erb
create mode 100644 app/views/users/_notifications.html.erb
create mode 100644 db/migrate/020_create_notifications.rb
create mode 100644 test/fixtures/notifications.yml
create mode 100644 test/unit/notifications_test.rb
diff --git a/app/controllers/sprints_controller.rb b/app/controllers/sprints_controller.rb
index 713d330..09ebe5b 100644
--- a/app/controllers/sprints_controller.rb
+++ b/app/controllers/sprints_controller.rb
@@ -129,6 +129,8 @@ class SprintsController < ApplicationController
if @sprint.can_populate?(@user)
@title = "Sprint #{(a)sprint.id} (Planning)"
@user_stories = UserStory.find_all_by_product_id((a)product.id)
+ @selected = []
+ @estimates = {}
format.html
else
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 1c58dbf..3ed21e4 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -16,7 +16,7 @@
# +UsersController+ allows for CRUD operations on instances of +User+.
class UsersController < ApplicationController
- before_filter :authenticated, :except => [:index, :show, :new, :create, :backlog, :roles]
+ before_filter :authenticated, :only => [:edit, :update, :verify, :notifications]
before_filter :load_this_user, :except => [:index, :new, :create]
# GET /users
@@ -31,6 +31,11 @@ class UsersController < ApplicationController
# GET /users/1
def show
@title = "#{(a)this_user.display_name}"
+ @backlog_items = BacklogItem.paginate(
+ :conditions => ['owner_id = ?', @this_user.id],
+ :page => params[:page],
+ :per_page => 10
+ )
end
# GET /users/new
@@ -59,6 +64,7 @@ class UsersController < ApplicationController
if (@user == nil) || (@user.create_users?)
@this_user = User.new(params[:user])
@this_user.privileges = UserPrivilege.new
+ @this_user.notifications = Notifications.new
@this_user.verification = UserVerification.new(
:sent => Date.today,
:token => UserVerification.create_token)
@@ -151,11 +157,32 @@ class UsersController < ApplicationController
:per_page => 10)
end
+ # POST /users/1/notifications
+ def notifications
+ respond_to do |format|
+ if @user.id == @this_user.id
+ Notifications.transaction do
+ @this_user.notifications.update_attributes(params[:notifications])
+
+ if @this_user.notifications.save
+ flash[:message] = "Notifications updated."
+ else
+ flash[:error] = "Unable to update notifications."
+ end
+
+ format.html { redirect_to user_path(@this_user) }
+ end
+ else
+ flash[:error] = "You cannot alter notifications for #{(a)this_user.display_name}."
+ format.html { redirect_to user_path(@this_user) }
+ end
+ end
+ end
+
private
def load_this_user
@this_user = User.find_by_id(params[:id])
- @backlog_items = @this_user.backlog if @this_user
unless @this_user
flash[:error] = "Missing or invalid user."
diff --git a/app/models/backlog_item.rb b/app/models/backlog_item.rb
index 5a88800..4fd4501 100644
--- a/app/models/backlog_item.rb
+++ b/app/models/backlog_item.rb
@@ -94,7 +94,7 @@ class BacklogItem < ActiveRecord::Base
end
# Reports if the task is closed.
- def closed?
+ def completed?
state == STATE_COMPLETED
end
diff --git a/app/models/notifications.rb b/app/models/notifications.rb
new file mode 100644
index 0000000..199a196
--- /dev/null
+++ b/app/models/notifications.rb
@@ -0,0 +1,2 @@
+class Notifications < ActiveRecord::Base
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index bdd1514..613b7ad 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -30,6 +30,7 @@ class User < ActiveRecord::Base
has_many :product_roles, :dependent => :destroy
has_one :verification, :class_name => "UserVerification", :dependent => :destroy
has_many :backlog, :class_name => "BacklogItem", :foreign_key => 'owner_id'
+ has_one :notifications, :dependent => :destroy
attr_accessor :password_confirmation
validates_confirmation_of :password,
diff --git a/app/models/user_mailer.rb b/app/models/user_mailer.rb
index 200edcb..e830463 100644
--- a/app/models/user_mailer.rb
+++ b/app/models/user_mailer.rb
@@ -57,6 +57,15 @@ class UserMailer < ActionMailer::Base
body :role => role
end
+ # Sends an email to a user letting him know his daily activities.
+ def daily_updates(user, open_items, completed_items, task_performed)
+ recipients user.email
+ from MAIL_CONFIG[:from]
+ subject "Daily updates for #{user.display_name}"
+ body :user => user, :open_items => open_items,
+ :completed_items => completed_items, :tasks_performed => task_performed
+ end
+
# Send an email to a user to notify him that no activity has been detected in
# his backlog.
def no_activity_recorded(user, backlog_items)
diff --git a/app/views/home/dashboard.html.erb b/app/views/home/dashboard.html.erb
index b41ccbf..32f1913 100644
--- a/app/views/home/dashboard.html.erb
+++ b/app/views/home/dashboard.html.erb
@@ -1,7 +1,9 @@
<div>
-
+
+ <p>View your <%= link_to "profile", user_path(@user) %>.</p>
+
<p>You have <%= @backlog.size %> tasks in
<%= link_to "your backlog", backlog_user_path(@user) %>.
</p>
-
+
</div>
diff --git a/app/views/items/_list.html.erb b/app/views/items/_list.html.erb
index 0379bb1..a07fa3e 100644
--- a/app/views/items/_list.html.erb
+++ b/app/views/items/_list.html.erb
@@ -2,7 +2,10 @@
<colgroup>
<col class="row_id" />
<col class="description" />
- <col class="user" />
+
+ <% unless @this_user %>
+ <col class="user" />
+ <% end %>
<% unless @sprint %>
<col class="number" />
@@ -15,12 +18,15 @@
<thead>
<tr>
- <th class="title" colspan="<%= @sprint ? 6 : 7 %>">Backlog Items</th>
+ <th class="title" colspan="<%= 7 - (@sprint ? 1 : 0) - (@this_user ? 1 : 0) %>">Backlog Items</th>
</tr>
<tr>
<th>#</th>
<th>Title</th>
- <th>Owner</th>
+
+ <% unless @this_user %>
+ <th>Owner</th>
+ <% end %>
<% unless @sprint %>
<th>Sprint</th>
@@ -49,7 +55,10 @@
product_sprint_item_path(product, sprint, item) %>
</td>
<td><%= item.user_story.title %></td>
- <td><%= link_to(item.owner.display_name, user_path(item.owner)) if item.owner %></td>
+
+ <% unless @this_user %>
+ <td><%= link_to(item.owner.display_name, user_path(item.owner)) if item.owner %></td>
+ <% end %>
<% unless @sprint %>
<td>
diff --git a/app/views/user_mailer/daily_updates.html.erb b/app/views/user_mailer/daily_updates.html.erb
new file mode 100644
index 0000000..7b6d5b3
--- /dev/null
+++ b/app/views/user_mailer/daily_updates.html.erb
@@ -0,0 +1,28 @@
+Here are your daily updates:
+
+Tasks performed:
+<% if !@tasks_performed || @tasks_performed.empty? %>
+ None.
+<% else %>
+ <% @tasks_performed.each_with_index do |task, index| %>
+ <%= "#{index + 1} (#{task.hours} Hours) #{task.description}" %>
+ <% end %>
+<% end %>
+
+Open backlog items:
+<% if !@open_items || @open_items.empty? %>
+ None.
+<% else %>
+ <% @open_items.each_with_index do |item, index| %>
+ <%= "#{index + 1} #{item.user_story.title}" %>
+ <% end %>
+<% end %>
+
+Closed backlog items:
+<% if !@completed_items || @completed_items.empty? %>
+ None.
+<% else %>
+ <% @completed_items.each_with_index do |item, index| %>
+ <%= "#{index + 1} #{item.user_story.title}" %>
+ <% end %>
+<% end %>
diff --git a/app/views/users/_notifications.html.erb b/app/views/users/_notifications.html.erb
new file mode 100644
index 0000000..06b34fb
--- /dev/null
+++ b/app/views/users/_notifications.html.erb
@@ -0,0 +1,29 @@
+<% form_for(:notifications, @this_user.notifications, :url => notifications_user_path(@this_user)) do |form| %>
+
+ <table class="edit">
+ <thead>
+ <tr>
+ <th class="title" colspan="2">Email Notifications</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ <tr>
+ <td class="label">Daily scrum email</td>
+ <td class="value"><%= form.check_box :daily_updates %></td>
+ </tr>
+
+ <tr>
+ <td class="label">Task reminders</td>
+ <td class="value"><%= form.check_box :task_reminders %>
+ </tr>
+
+ <tr>
+ <td class="buttons" colspan="2">
+ <%= submit_tag "Update" %>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+<% end %>
diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb
index 4fa4cf1..2c4af66 100644
--- a/app/views/users/show.html.erb
+++ b/app/views/users/show.html.erb
@@ -26,4 +26,9 @@
</tbody>
</table>
-<%= render(:partial => 'items/list') %>
+<% if @this_user.can_edit?(@user) %>
+ <%= render :partial => 'notifications' %>
+<% end %>
+
+<%= will_paginate @backlog_items %>
+<%= render :partial => 'items/list' %>
diff --git a/config/initializers/schedules.rb b/config/initializers/schedules.rb
index 9239cd9..f2bb039 100644
--- a/config/initializers/schedules.rb
+++ b/config/initializers/schedules.rb
@@ -8,9 +8,56 @@ raise "Schedule configuration file not found at #{schedules_file_path}" unless F
SCHEDULES_CONFIG = YAML.load(File.open(schedules_file_path))
# Initialize a threads array
-threads = []
+threads = {}
-threads << Thread.new do
+threads["daily updates"] = Thread.new do
+ scheduler = Scheduler.new
+ scheduler.start
+
+ hours = SCHEDULES_CONFIG[:daily_updates][:hours]
+ minutes = SCHEDULES_CONFIG[:daily_updates][:minutes]
+
+ scheduler.schedule("#{minutes} #{hours} * * *") do
+ open_items = {}
+ completed_items = {}
+ tasks_performed = {}
+
+ BacklogItem.find(:all).each do |item|
+ owner = item.owner
+
+ if item.completed? && item.updated_at.to_date == Date.yesterday
+ completed_items[owner] ||= []
+ completed_items[owner] << item
+ elsif item.owner
+ open_items[owner] ||= []
+ open_items[owner] << item
+ end
+ end
+
+ Task.find(:all).each do |task|
+ if task.when_entered.to_date == Date.yesterday
+ tasks_performed[task.primary] ||= []
+ tasks_performed[task.primary] << task
+
+ if task.backup
+ tasks_performed[task.backup] ||= []
+ tasks_performed[task.backup] << task
+ end
+ end
+ end
+
+ User.find(:all).each do |user|
+ if user.notifications.daily_updates
+ UserMailer.deliver_daily_updates(user, open_items[user],
+ completed_items[user], tasks_performed[user])
+ end
+ end
+ end
+
+ scheduler.join
+end
+
+threads["daily reminders"] = Thread.new do
scheduler = Scheduler.new
scheduler.start
@@ -45,11 +92,11 @@ threads << Thread.new do
# Send the e-mail
user_no_activity_on_backlog_items.each do |owner, backlog_items|
- UserMailer.deliver_no_activity_recorded(owner, backlog_items)
+ UserMailer.deliver_no_activity_recorded(owner, backlog_items) if owner.notifications.task_reminders
end
user_no_tasks_on_backlog_items.each do |owner, backlog_items|
- UserMailer.deliver_no_task_recorded(owner, backlog_items)
+ UserMailer.deliver_no_task_recorded(owner, backlog_items) if owner.notifications.task_reminders
end
end
@@ -57,7 +104,7 @@ threads << Thread.new do
scheduler.join
end
-threads << Thread.new do
+threads["product status"] = Thread.new do
scheduler = Scheduler.new
scheduler.start
@@ -81,7 +128,7 @@ threads << Thread.new do
scheduler.join
end
-threads << Thread.new do
+threads["user verification expiration"] = Thread.new do
scheduler = Scheduler.new
scheduler.start
@@ -114,4 +161,12 @@ threads << Thread.new do
end
# Run all threads
-threads.each { |thread| thread.run }
+threads.each_pair do |key, thread|
+ puts "Starting scheduler for \"#{key}\"..."
+
+ begin
+ thread.run
+ rescue Exception => error
+ puts "Error starting thread: #{error.message}"
+ end
+end
diff --git a/config/routes.rb b/config/routes.rb
index d4d2ec6..25bac9c 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -30,7 +30,7 @@ ActionController::Routing::Routes.draw do |map|
}
end
end
- map.resources :users, :member => {:backlog => :get, :roles => :get} do |user|
+ map.resources :users, :member => {:backlog => :get, :roles => :get, :notifications => :post} do |user|
user.resources :tasks
end
diff --git a/config/schedules.yml.example b/config/schedules.yml.example
index 75741b4..88d55a7 100644
--- a/config/schedules.yml.example
+++ b/config/schedules.yml.example
@@ -1,10 +1,13 @@
---
:no_activity_or_no_task_recorded_time:
- :hours: 5
+ :hours: 3
:minutes: 0
+ :daily_updates:
+ :hours: 3
+ :minutes: 15
:sprints_products_status_time:
- :hours: 5
- :minutes: 0
+ :hours: 3
+ :minutes: 30
:user_verification_expiration:
:hours: 3
- :minutes: 0
+ :minutes: 45
diff --git a/db/migrate/020_create_notifications.rb b/db/migrate/020_create_notifications.rb
new file mode 100644
index 0000000..11f9643
--- /dev/null
+++ b/db/migrate/020_create_notifications.rb
@@ -0,0 +1,19 @@
+class CreateNotifications < ActiveRecord::Migration
+ def self.up
+ create_table :notifications do |t|
+ t.integer :user_id
+ t.boolean :task_reminders
+ t.boolean :daily_updates
+
+ t.timestamps
+ end
+
+ User.find(:all).each do |user|
+ user .notifications = Notifications.new(:task_reminders => true, :daily_updates => true)
+ end
+ end
+
+ def self.down
+ drop_table :notifications
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index aec15d9..6215516 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -9,7 +9,7 @@
#
# It's strongly recommended to check this file into your version control system.
-ActiveRecord::Schema.define(:version => 19) do
+ActiveRecord::Schema.define(:version => 20) do
create_table "backlog_items", :force => true do |t|
t.integer "sprint_id", :null => false
@@ -23,6 +23,15 @@ ActiveRecord::Schema.define(:version => 19) do
end
add_index "backlog_items", ["sprint_id", "user_story_id"], :name => "index_backlog_items_on_sprint_id_and_user_story_id", :unique => true
+ add_index "backlog_items", ["user_story_id"], :name => "fk_backlog_items_user_story"
+
+ create_table "notifications", :force => true do |t|
+ t.integer "user_id"
+ t.boolean "task_reminders"
+ t.boolean "daily_updates"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
create_table "product_roles", :force => true do |t|
t.integer "user_id", :null => false
@@ -34,7 +43,9 @@ ActiveRecord::Schema.define(:version => 19) do
t.boolean "is_approved", :default => false
end
- add_index "product_roles", ["product_id", "user_id"], :name => "index_product_roles_on_user_id_and_product_id", :unique => true
+ add_index "product_roles", ["user_id", "product_id"], :name => "index_product_roles_on_user_id_and_product_id", :unique => true
+ add_index "product_roles", ["product_id"], :name => "fk_user_role_product"
+ add_index "product_roles", ["role_id"], :name => "fk_user_role_role"
create_table "products", :force => true do |t|
t.integer "project_id"
@@ -46,6 +57,7 @@ ActiveRecord::Schema.define(:version => 19) do
end
add_index "products", ["name"], :name => "index_products_on_name", :unique => true
+ add_index "products", ["project_id"], :name => "fk_products_project"
create_table "projects", :force => true do |t|
t.integer "owner_id", :null => false
@@ -57,6 +69,7 @@ ActiveRecord::Schema.define(:version => 19) do
end
add_index "projects", ["name"], :name => "index_projects_on_name", :unique => true
+ add_index "projects", ["owner_id"], :name => "fk_product_owner"
create_table "remaining_hours_estimates", :force => true do |t|
t.integer "backlog_item_id", :null => false
@@ -67,6 +80,9 @@ ActiveRecord::Schema.define(:version => 19) do
t.datetime "estimated_on", :null => false
end
+ add_index "remaining_hours_estimates", ["backlog_item_id"], :name => "fk_remaining_hours_item"
+ add_index "remaining_hours_estimates", ["user_id"], :name => "fk_remaining_hours_user"
+
create_table "roles", :force => true do |t|
t.string "name", :null => false
t.boolean "can_manage_backlog_items", :default => false, :null => false
@@ -98,6 +114,8 @@ ActiveRecord::Schema.define(:version => 19) do
t.integer "status", :default => 0, :null => false
end
+ add_index "sprints", ["product_id"], :name => "fk_sprint_product"
+
create_table "tasks", :force => true do |t|
t.integer "backlog_item_id", :null => false
t.integer "primary_id", :null => false
@@ -109,6 +127,10 @@ ActiveRecord::Schema.define(:version => 19) do
t.datetime "updated_at"
end
+ add_index "tasks", ["backlog_item_id"], :name => "fk_task_backlog_item"
+ add_index "tasks", ["primary_id"], :name => "fk_task_primary"
+ add_index "tasks", ["backup_id"], :name => "fk_task_backup"
+
create_table "user_privileges", :force => true do |t|
t.integer "user_id", :null => false
t.boolean "admin_projects", :default => false, :null => false
@@ -117,6 +139,8 @@ ActiveRecord::Schema.define(:version => 19) do
t.datetime "updated_at"
end
+ add_index "user_privileges", ["user_id"], :name => "fk_privilege_user"
+
create_table "user_stories", :force => true do |t|
t.integer "product_id"
t.integer "priority"
@@ -127,6 +151,8 @@ ActiveRecord::Schema.define(:version => 19) do
t.datetime "updated_at"
end
+ add_index "user_stories", ["product_id"], :name => "fk_user_story_product"
+
create_table "user_verifications", :force => true do |t|
t.integer "user_id", :null => false
t.string "token", :limit => 16, :null => false
diff --git a/public/stylesheets/lists.css b/public/stylesheets/lists.css
index 4db910b..c5942c9 100644
--- a/public/stylesheets/lists.css
+++ b/public/stylesheets/lists.css
@@ -28,7 +28,7 @@ col.name {
}
col.description {
-
+ width: 100%;
}
col.number {
diff --git a/test/fixtures/notifications.yml b/test/fixtures/notifications.yml
new file mode 100644
index 0000000..ac0dd71
--- /dev/null
+++ b/test/fixtures/notifications.yml
@@ -0,0 +1,6 @@
+<% User.find(:all).each do |user| %>
+ <%= "#{user.id}_notifications:" %>
+ user_id: <%= user.id %>
+ task_reminders: false
+ daily_updates: false
+ <% end %>
diff --git a/test/functional/tasks_controller_test.rb b/test/functional/tasks_controller_test.rb
index 666fa4e..e2ed4a5 100644
--- a/test/functional/tasks_controller_test.rb
+++ b/test/functional/tasks_controller_test.rb
@@ -30,13 +30,13 @@ class TasksControllerTest < ActionController::TestCase
raise "Task must be owned by the user!" unless @task.primary_id == @user.id
@item = @task.backlog_item
- raise "Item cannot be closed!" if @item.closed?
+ raise "Item cannot be closed!" if @item.completed?
@sprint = @item.sprint
@product = @sprint.product
@closed_item = backlog_items(:closed_backlog_item)
- raise "Item must be closed!" unless @closed_item.closed?
+ raise "Item must be closed!" unless @closed_item.completed?
@owner = @item.owner
@nonowner = users(:jdonuts)
diff --git a/test/functional/user_items_test.rb b/test/functional/user_items_test.rb
index 628cd35..ef4354d 100644
--- a/test/functional/user_items_test.rb
+++ b/test/functional/user_items_test.rb
@@ -61,7 +61,7 @@ class UserItemsTest < ActionController::TestCase
raise "Item does not belong to sprint!" unless @unowned_item.sprint_id == @sprint.id
@closed_item = backlog_items(:closed_backlog_item)
- raise "item must not be active!" unless @closed_item.closed?
+ raise "item must not be active!" unless @closed_item.completed?
raise "Item does not belong to sprint!" unless @closed_item.sprint_id == @sprint.id
@member = users(:mcpierce)
diff --git a/test/functional/users_controller_test.rb b/test/functional/users_controller_test.rb
index 7375369..cebc450 100644
--- a/test/functional/users_controller_test.rb
+++ b/test/functional/users_controller_test.rb
@@ -17,6 +17,7 @@
require File.dirname(__FILE__) + '/../test_helper'
class UsersControllerTest < ActionController::TestCase
+ fixtures :notifications
fixtures :user_verifications
fixtures :users
@@ -160,8 +161,11 @@ class UsersControllerTest < ActionController::TestCase
post :create, {:user => @new_user}, {:user_id => @admin.id}
assert_redirected_to user_path(assigns['this_user'])
- assert User.find_by_email(@new_user[:email]),
- "A new user should have been created."
+ result = User.find_by_email(@new_user[:email])
+
+ assert result, "A new user should have been created."
+ assert result.privileges, "No privileges were created."
+ assert result.notifications, "No notifications were created."
email = @emails.first
assert email, "No email was generated."
@@ -309,4 +313,36 @@ class UsersControllerTest < ActionController::TestCase
assert_response :success
assert assigns['product_roles'], "Failed to load the user's roles."
end
+
+ # Ensures that notifications updates require the user be logged in.
+ def test_notifications_as_anonymous
+ post :notifications
+
+ assert_redirected_to login_path
+ end
+
+ # Ensures that a valid user's required.
+ def test_notifications_with_invalid_user
+ post :notifications, {}, {:user_id => @user.id}
+
+ assert_redirected_to users_path
+ end
+
+ # Ensures that a user cannot edit someone else's notifications.
+ def test_notifications_for_other_user
+ post :notifications, {:id => @other_user.id}, {:user_id => @user.id}
+
+ assert_redirected_to user_path(@other_user)
+ end
+
+ # Ensures that notifications are updated.
+ def test_notifications
+ post :notifications,
+ {:id => @user.id, :notifications => {:task_reminders => true}},
+ {:user_id => @user.id}
+
+ assert_redirected_to user_path(@user)
+ assert Notifications.find_by_user_id((a)user.id).task_reminders,
+ "Notification update should've been saved."
+ end
end
diff --git a/test/unit/backlog_item_test.rb b/test/unit/backlog_item_test.rb
index afe4669..c80c54a 100644
--- a/test/unit/backlog_item_test.rb
+++ b/test/unit/backlog_item_test.rb
@@ -26,7 +26,8 @@ class BacklogItemTest < ActiveSupport::TestCase
def setup
@item = BacklogItem.new(
:sprint_id => sprints(:active_sprint).id,
- :user_story_id => user_stories(:create_login).id)
+ :user_story_id => user_stories(:create_login).id,
+ :estimated_hours => 5.0)
@owned_backlog_item = backlog_items(:owned_backlog_item)
raise "Owned item must have an owner!" unless @owned_backlog_item.owner
diff --git a/test/unit/notifications_test.rb b/test/unit/notifications_test.rb
new file mode 100644
index 0000000..d515277
--- /dev/null
+++ b/test/unit/notifications_test.rb
@@ -0,0 +1,8 @@
+require 'test_helper'
+
+class NotificationsTest < ActiveSupport::TestCase
+ # Replace this with your real tests.
+ def test_truth
+ assert true
+ end
+end
--
1.6.0.2
15 years, 4 months
[PROJXP [PATCH] Added a page for users to change their password.
by Darryl L. Pierce
Signed-off-by: Darryl L. Pierce <mcpierce(a)gmail.com>
---
app/controllers/users_controller.rb | 30 +++++++++++++++++
app/views/users/password.html.erb | 30 +++++++++++++++++
app/views/users/show.html.erb | 2 +
config/routes.rb | 8 ++++-
public/images/icons/password.png | Bin 0 -> 744 bytes
test/functional/users_controller_test.rb | 53 ++++++++++++++++++++++++++++++
6 files changed, 122 insertions(+), 1 deletions(-)
create mode 100644 app/views/users/password.html.erb
create mode 100755 public/images/icons/password.png
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 1c58dbf..6cf7a27 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -151,6 +151,36 @@ class UsersController < ApplicationController
:per_page => 10)
end
+ # POST /users/1/change_password
+ def change_password
+ respond_to do |format|
+ if @this_user.can_edit?(@user)
+ if User.authenticate((a)this_user.email, params[:old_password])
+ @this_user.update_attributes(params[:user])
+
+ if @this_user.valid?
+ if @this_user.save
+ flash[:message] = "Password update successfully."
+ format.html { redirect_to user_path(@this_user) }
+ else
+ @this_user.valid?
+ format.html { render :action => :password }
+ end
+ else
+ @this_user.valid?
+ format.html { render :action => :password }
+ end
+ else
+ flash[:error] = "Old password did not match."
+ format.html { redirect_to password_user_path(@this_user) }
+ end
+ else
+ flash[:error] = "You cannot change the password for #{(a)this_user.display_name}."
+ format.html { redirect_to user_path(@this_user) }
+ end
+ end
+ end
+
private
def load_this_user
diff --git a/app/views/users/password.html.erb b/app/views/users/password.html.erb
new file mode 100644
index 0000000..acdd794
--- /dev/null
+++ b/app/views/users/password.html.erb
@@ -0,0 +1,30 @@
+<% form_for(:user, @this_user, :url => change_password_user_path(@this_user)) do |form| %>
+ <table class="edit">
+ <tr>
+ <td class="label-required">Current password</td>
+ <td class="value"><%= password_field_tag :old_password %></td>
+ </tr>
+
+ <tr>
+ <td class="label-required">New password</td>
+ <td class="value">
+ <%= form.password_field :password %>
+ <%= error_message_on(@this_user, :password) %>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="label-required">Confirm password</td>
+ <td class="value">
+ <%= form.password_field :password_confirmation %>
+ <%= error_message_on(@this_user, :password_confirmation) %>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="buttons" colspan="2">
+ <%= submit_tag "Update" %>
+ </td>
+ </tr>
+ </table>
+<% end %>
diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb
index 4fa4cf1..e0de5b9 100644
--- a/app/views/users/show.html.erb
+++ b/app/views/users/show.html.erb
@@ -11,6 +11,8 @@
<%= link_to(image_tag("icons/back.png", :alt => "Back to users list..."), users_path) %>
<%= link_to(image_tag("icons/edit.png", :alt => "Edit"),
edit_user_path(@this_user)) if @this_user.can_edit?(@user) %>
+ <%= link_to(image_tag("icons/password.png", :alt => "Change password"),
+ password_user_path(@this_user)) if @this_user.can_edit?(@user) %>
</td>
</tr>
diff --git a/config/routes.rb b/config/routes.rb
index d4d2ec6..ff7cf95 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -30,7 +30,13 @@ ActionController::Routing::Routes.draw do |map|
}
end
end
- map.resources :users, :member => {:backlog => :get, :roles => :get} do |user|
+ map.resources :users, :member =>
+ {
+ :backlog => :get,
+ :roles => :get,
+ :password => :get,
+ :change_password => :post
+ } do |user|
user.resources :tasks
end
diff --git a/public/images/icons/password.png b/public/images/icons/password.png
new file mode 100755
index 0000000000000000000000000000000000000000..30b0dc316e52dba388d88112d4c1cc32672fffbb
GIT binary patch
literal 744
zcmV<E0vG*>P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004XF*Lt006JZ
zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!dPzh<R5;6x
zlU+!YVHk$LRU{on2NEG99t4gN<(k<vgSIj~$mk$~eksv@gmxl=Ow(;Fr^B3}b5Lr_
z2G&-MqzD~`qBeg)s5vYXON+MJpPSp<&)xfdS}mK_2lvVQa$nE=JkR@40I2o!$#_If
zgcYe*-~X369QeQ}9^~I<-#CKyR)jn~<T%PeX7qx)>jGlUfiHCke$)P}4*HwX@?mb`
zzgiE#M5fLDxtj=lZ6q=+Lkt2$!wyVqxC~@X03De&Gn$t$kdWJig()R`(|L#ldNHNi
zi?tK@-;xA1zab1r3XkO&T;m5wdrx2~XU8>fpl4v~7ZF1h+!NXGy*rQ6jw}?mrNKGI
zL&zzIrIL-VTRiLE`+jy5wt-SCISiy4pO}x6>V>$`&V{7&3{GoOF)87oTao_ek0H|L
zXxL5q?8f4p7$RLZL=Q4>?LH3$EqhS@^b{VAG@wL(0y*{DquI)BECvw!(t<WU7OeSj
z4slT2o&n>y8jr^s8DqzY3Mx|xw6AM%RhNVG>V(j48H=@2*+n87;k63kFsH&hc^Czx
zU)p@TON5%2#gM-!LRIG_NS|MUrcZ`*_YPuLB^9Iro+W2D85SRofmAHM&xik`9B1#a
z@o-oLow*L$!CJHqC<x>_n){?E(&Zwhf|^V!qqZ#X+&c)jMMwsg3*W31W<{FoWOGV1
zuOTTStWS(&DYr&0v}HowTZPN*IY_RcCU%rj3Cs*;4T3Shy&sFSmGFOV!%!{P*`>;C
zSiN43jAg&56(U(ojS}<bU;n~z%OUZEdjI^WYM*^X$^G8flvN$?agoUOo#Ks1ETcBX
ap8o(~AJmyDx~^sb0000<MNUMnLSTY)_fHN0
literal 0
HcmV?d00001
diff --git a/test/functional/users_controller_test.rb b/test/functional/users_controller_test.rb
index 7375369..838fac2 100644
--- a/test/functional/users_controller_test.rb
+++ b/test/functional/users_controller_test.rb
@@ -309,4 +309,57 @@ class UsersControllerTest < ActionController::TestCase
assert_response :success
assert assigns['product_roles'], "Failed to load the user's roles."
end
+
+ # Ensures anonymous users can't change passwords.
+ def test_change_password_as_anonymous
+ post :change_password
+
+ assert_redirected_to login_path
+ end
+
+ # Ensures that a valid user must be supplied.
+ def test_change_password_with_invalid_user
+ post :change_password, {}, {:user_id => @user.id}
+
+ assert_redirected_to users_path
+ end
+
+ # Ensures that only the user can change his password.
+ def test_change_password_for_other_user
+ post :change_password, {:id => @other_user.id}, {:user_id => @user.id}
+
+ assert_redirected_to user_path(@other_user)
+ end
+
+ # Ensures that the current password has to match.
+ def test_change_password_with_wrong_current_password
+ post :change_password,
+ {:id => @user.id, :old_password => "garbledina"},
+ {:user_id => @user.id}
+
+ assert_redirected_to password_user_path(@user)
+ end
+
+ # Ensures that the new passwords have to valid.
+ def test_change_password_with_incorrect_passwords
+ post :change_password,
+ {:id => @user.id, :old_password => "farkle",
+ :user => {:password => "a", :password_confirmation => "b" }},
+ {:user_id => @user.id}
+
+ assert_response :success
+ assert !User.authenticate((a)user.email, "a"), "Password should not have been updated."
+ end
+
+ # Ensures that a valid password update works as expected.
+ def test_change_password
+ post :change_password,
+ {:id => @user.id, :old_password => "farkle",
+ :user => {:password => "visible", :password_confirmation => "visible"}},
+ {:user_id => @user.id}
+
+ assert_redirected_to user_path(@user)
+ assert User.authenticate((a)user.email, "visible"),
+ "Password was not properly updated."
+ end
end
--
1.6.0.2
15 years, 4 months
[PROJXP [PATCH] Users can select to receive daily scrum emails. #18
by Darryl L. Pierce
This patch requires a migration as it adds a new table, notifications,
to the project.
Added a new action to the users controller, called notifications,
that takes posted data to update the user's notifications.
>From the dashboard the user can select to view their profile. On
the profile page are options for selecting notification types.
Side tasks:
Removed the "Owner" column from the user view since it's redundant
to show the owner's name when it's the owner that's being viewed.
Refactored the BacklogItem.closed? to be BacklogItem.completed? since
the item state is actually STATE_COMPLETED.
Fixed a ton of unit tests that were broken by a refactoring exercise.
Signed-off-by: Darryl L. Pierce <mcpierce(a)gmail.com>
---
app/controllers/sprints_controller.rb | 2 +
app/controllers/users_controller.rb | 31 +++++++++++-
app/models/backlog_item.rb | 2 +-
app/models/notifications.rb | 2 +
app/models/user.rb | 1 +
app/models/user_mailer.rb | 9 +++
app/views/home/dashboard.html.erb | 6 ++-
app/views/items/_list.html.erb | 17 +++++--
app/views/user_mailer/daily_updates.html.erb | 28 ++++++++++
app/views/users/_notifications.html.erb | 29 +++++++++++
app/views/users/show.html.erb | 7 ++-
config/initializers/schedules.rb | 69 +++++++++++++++++++++++---
config/routes.rb | 2 +-
config/schedules.yml.example | 11 +++--
db/migrate/020_create_notifications.rb | 19 +++++++
db/schema.rb | 30 ++++++++++-
public/stylesheets/lists.css | 2 +-
test/fixtures/notifications.yml | 6 ++
test/functional/tasks_controller_test.rb | 4 +-
test/functional/user_items_test.rb | 2 +-
test/functional/users_controller_test.rb | 40 ++++++++++++++-
test/unit/backlog_item_test.rb | 3 +-
test/unit/notifications_test.rb | 8 +++
23 files changed, 299 insertions(+), 31 deletions(-)
create mode 100644 app/models/notifications.rb
create mode 100644 app/views/user_mailer/daily_updates.html.erb
create mode 100644 app/views/users/_notifications.html.erb
create mode 100644 db/migrate/020_create_notifications.rb
create mode 100644 test/fixtures/notifications.yml
create mode 100644 test/unit/notifications_test.rb
diff --git a/app/controllers/sprints_controller.rb b/app/controllers/sprints_controller.rb
index 713d330..09ebe5b 100644
--- a/app/controllers/sprints_controller.rb
+++ b/app/controllers/sprints_controller.rb
@@ -129,6 +129,8 @@ class SprintsController < ApplicationController
if @sprint.can_populate?(@user)
@title = "Sprint #{(a)sprint.id} (Planning)"
@user_stories = UserStory.find_all_by_product_id((a)product.id)
+ @selected = []
+ @estimates = {}
format.html
else
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 1c58dbf..3ed21e4 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -16,7 +16,7 @@
# +UsersController+ allows for CRUD operations on instances of +User+.
class UsersController < ApplicationController
- before_filter :authenticated, :except => [:index, :show, :new, :create, :backlog, :roles]
+ before_filter :authenticated, :only => [:edit, :update, :verify, :notifications]
before_filter :load_this_user, :except => [:index, :new, :create]
# GET /users
@@ -31,6 +31,11 @@ class UsersController < ApplicationController
# GET /users/1
def show
@title = "#{(a)this_user.display_name}"
+ @backlog_items = BacklogItem.paginate(
+ :conditions => ['owner_id = ?', @this_user.id],
+ :page => params[:page],
+ :per_page => 10
+ )
end
# GET /users/new
@@ -59,6 +64,7 @@ class UsersController < ApplicationController
if (@user == nil) || (@user.create_users?)
@this_user = User.new(params[:user])
@this_user.privileges = UserPrivilege.new
+ @this_user.notifications = Notifications.new
@this_user.verification = UserVerification.new(
:sent => Date.today,
:token => UserVerification.create_token)
@@ -151,11 +157,32 @@ class UsersController < ApplicationController
:per_page => 10)
end
+ # POST /users/1/notifications
+ def notifications
+ respond_to do |format|
+ if @user.id == @this_user.id
+ Notifications.transaction do
+ @this_user.notifications.update_attributes(params[:notifications])
+
+ if @this_user.notifications.save
+ flash[:message] = "Notifications updated."
+ else
+ flash[:error] = "Unable to update notifications."
+ end
+
+ format.html { redirect_to user_path(@this_user) }
+ end
+ else
+ flash[:error] = "You cannot alter notifications for #{(a)this_user.display_name}."
+ format.html { redirect_to user_path(@this_user) }
+ end
+ end
+ end
+
private
def load_this_user
@this_user = User.find_by_id(params[:id])
- @backlog_items = @this_user.backlog if @this_user
unless @this_user
flash[:error] = "Missing or invalid user."
diff --git a/app/models/backlog_item.rb b/app/models/backlog_item.rb
index 5a88800..4fd4501 100644
--- a/app/models/backlog_item.rb
+++ b/app/models/backlog_item.rb
@@ -94,7 +94,7 @@ class BacklogItem < ActiveRecord::Base
end
# Reports if the task is closed.
- def closed?
+ def completed?
state == STATE_COMPLETED
end
diff --git a/app/models/notifications.rb b/app/models/notifications.rb
new file mode 100644
index 0000000..199a196
--- /dev/null
+++ b/app/models/notifications.rb
@@ -0,0 +1,2 @@
+class Notifications < ActiveRecord::Base
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index bdd1514..613b7ad 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -30,6 +30,7 @@ class User < ActiveRecord::Base
has_many :product_roles, :dependent => :destroy
has_one :verification, :class_name => "UserVerification", :dependent => :destroy
has_many :backlog, :class_name => "BacklogItem", :foreign_key => 'owner_id'
+ has_one :notifications, :dependent => :destroy
attr_accessor :password_confirmation
validates_confirmation_of :password,
diff --git a/app/models/user_mailer.rb b/app/models/user_mailer.rb
index 200edcb..e830463 100644
--- a/app/models/user_mailer.rb
+++ b/app/models/user_mailer.rb
@@ -57,6 +57,15 @@ class UserMailer < ActionMailer::Base
body :role => role
end
+ # Sends an email to a user letting him know his daily activities.
+ def daily_updates(user, open_items, completed_items, task_performed)
+ recipients user.email
+ from MAIL_CONFIG[:from]
+ subject "Daily updates for #{user.display_name}"
+ body :user => user, :open_items => open_items,
+ :completed_items => completed_items, :tasks_performed => task_performed
+ end
+
# Send an email to a user to notify him that no activity has been detected in
# his backlog.
def no_activity_recorded(user, backlog_items)
diff --git a/app/views/home/dashboard.html.erb b/app/views/home/dashboard.html.erb
index b41ccbf..32f1913 100644
--- a/app/views/home/dashboard.html.erb
+++ b/app/views/home/dashboard.html.erb
@@ -1,7 +1,9 @@
<div>
-
+
+ <p>View your <%= link_to "profile", user_path(@user) %>.</p>
+
<p>You have <%= @backlog.size %> tasks in
<%= link_to "your backlog", backlog_user_path(@user) %>.
</p>
-
+
</div>
diff --git a/app/views/items/_list.html.erb b/app/views/items/_list.html.erb
index 0379bb1..a07fa3e 100644
--- a/app/views/items/_list.html.erb
+++ b/app/views/items/_list.html.erb
@@ -2,7 +2,10 @@
<colgroup>
<col class="row_id" />
<col class="description" />
- <col class="user" />
+
+ <% unless @this_user %>
+ <col class="user" />
+ <% end %>
<% unless @sprint %>
<col class="number" />
@@ -15,12 +18,15 @@
<thead>
<tr>
- <th class="title" colspan="<%= @sprint ? 6 : 7 %>">Backlog Items</th>
+ <th class="title" colspan="<%= 7 - (@sprint ? 1 : 0) - (@this_user ? 1 : 0) %>">Backlog Items</th>
</tr>
<tr>
<th>#</th>
<th>Title</th>
- <th>Owner</th>
+
+ <% unless @this_user %>
+ <th>Owner</th>
+ <% end %>
<% unless @sprint %>
<th>Sprint</th>
@@ -49,7 +55,10 @@
product_sprint_item_path(product, sprint, item) %>
</td>
<td><%= item.user_story.title %></td>
- <td><%= link_to(item.owner.display_name, user_path(item.owner)) if item.owner %></td>
+
+ <% unless @this_user %>
+ <td><%= link_to(item.owner.display_name, user_path(item.owner)) if item.owner %></td>
+ <% end %>
<% unless @sprint %>
<td>
diff --git a/app/views/user_mailer/daily_updates.html.erb b/app/views/user_mailer/daily_updates.html.erb
new file mode 100644
index 0000000..7b6d5b3
--- /dev/null
+++ b/app/views/user_mailer/daily_updates.html.erb
@@ -0,0 +1,28 @@
+Here are your daily updates:
+
+Tasks performed:
+<% if !@tasks_performed || @tasks_performed.empty? %>
+ None.
+<% else %>
+ <% @tasks_performed.each_with_index do |task, index| %>
+ <%= "#{index + 1} (#{task.hours} Hours) #{task.description}" %>
+ <% end %>
+<% end %>
+
+Open backlog items:
+<% if !@open_items || @open_items.empty? %>
+ None.
+<% else %>
+ <% @open_items.each_with_index do |item, index| %>
+ <%= "#{index + 1} #{item.user_story.title}" %>
+ <% end %>
+<% end %>
+
+Closed backlog items:
+<% if !@completed_items || @completed_items.empty? %>
+ None.
+<% else %>
+ <% @completed_items.each_with_index do |item, index| %>
+ <%= "#{index + 1} #{item.user_story.title}" %>
+ <% end %>
+<% end %>
diff --git a/app/views/users/_notifications.html.erb b/app/views/users/_notifications.html.erb
new file mode 100644
index 0000000..06b34fb
--- /dev/null
+++ b/app/views/users/_notifications.html.erb
@@ -0,0 +1,29 @@
+<% form_for(:notifications, @this_user.notifications, :url => notifications_user_path(@this_user)) do |form| %>
+
+ <table class="edit">
+ <thead>
+ <tr>
+ <th class="title" colspan="2">Email Notifications</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ <tr>
+ <td class="label">Daily scrum email</td>
+ <td class="value"><%= form.check_box :daily_updates %></td>
+ </tr>
+
+ <tr>
+ <td class="label">Task reminders</td>
+ <td class="value"><%= form.check_box :task_reminders %>
+ </tr>
+
+ <tr>
+ <td class="buttons" colspan="2">
+ <%= submit_tag "Update" %>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+<% end %>
diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb
index 4fa4cf1..2c4af66 100644
--- a/app/views/users/show.html.erb
+++ b/app/views/users/show.html.erb
@@ -26,4 +26,9 @@
</tbody>
</table>
-<%= render(:partial => 'items/list') %>
+<% if @this_user.can_edit?(@user) %>
+ <%= render :partial => 'notifications' %>
+<% end %>
+
+<%= will_paginate @backlog_items %>
+<%= render :partial => 'items/list' %>
diff --git a/config/initializers/schedules.rb b/config/initializers/schedules.rb
index 9239cd9..f2bb039 100644
--- a/config/initializers/schedules.rb
+++ b/config/initializers/schedules.rb
@@ -8,9 +8,56 @@ raise "Schedule configuration file not found at #{schedules_file_path}" unless F
SCHEDULES_CONFIG = YAML.load(File.open(schedules_file_path))
# Initialize a threads array
-threads = []
+threads = {}
-threads << Thread.new do
+threads["daily updates"] = Thread.new do
+ scheduler = Scheduler.new
+ scheduler.start
+
+ hours = SCHEDULES_CONFIG[:daily_updates][:hours]
+ minutes = SCHEDULES_CONFIG[:daily_updates][:minutes]
+
+ scheduler.schedule("#{minutes} #{hours} * * *") do
+ open_items = {}
+ completed_items = {}
+ tasks_performed = {}
+
+ BacklogItem.find(:all).each do |item|
+ owner = item.owner
+
+ if item.completed? && item.updated_at.to_date == Date.yesterday
+ completed_items[owner] ||= []
+ completed_items[owner] << item
+ elsif item.owner
+ open_items[owner] ||= []
+ open_items[owner] << item
+ end
+ end
+
+ Task.find(:all).each do |task|
+ if task.when_entered.to_date == Date.yesterday
+ tasks_performed[task.primary] ||= []
+ tasks_performed[task.primary] << task
+
+ if task.backup
+ tasks_performed[task.backup] ||= []
+ tasks_performed[task.backup] << task
+ end
+ end
+ end
+
+ User.find(:all).each do |user|
+ if user.notifications.daily_updates
+ UserMailer.deliver_daily_updates(user, open_items[user],
+ completed_items[user], tasks_performed[user])
+ end
+ end
+ end
+
+ scheduler.join
+end
+
+threads["daily reminders"] = Thread.new do
scheduler = Scheduler.new
scheduler.start
@@ -45,11 +92,11 @@ threads << Thread.new do
# Send the e-mail
user_no_activity_on_backlog_items.each do |owner, backlog_items|
- UserMailer.deliver_no_activity_recorded(owner, backlog_items)
+ UserMailer.deliver_no_activity_recorded(owner, backlog_items) if owner.notifications.task_reminders
end
user_no_tasks_on_backlog_items.each do |owner, backlog_items|
- UserMailer.deliver_no_task_recorded(owner, backlog_items)
+ UserMailer.deliver_no_task_recorded(owner, backlog_items) if owner.notifications.task_reminders
end
end
@@ -57,7 +104,7 @@ threads << Thread.new do
scheduler.join
end
-threads << Thread.new do
+threads["product status"] = Thread.new do
scheduler = Scheduler.new
scheduler.start
@@ -81,7 +128,7 @@ threads << Thread.new do
scheduler.join
end
-threads << Thread.new do
+threads["user verification expiration"] = Thread.new do
scheduler = Scheduler.new
scheduler.start
@@ -114,4 +161,12 @@ threads << Thread.new do
end
# Run all threads
-threads.each { |thread| thread.run }
+threads.each_pair do |key, thread|
+ puts "Starting scheduler for \"#{key}\"..."
+
+ begin
+ thread.run
+ rescue Exception => error
+ puts "Error starting thread: #{error.message}"
+ end
+end
diff --git a/config/routes.rb b/config/routes.rb
index d4d2ec6..25bac9c 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -30,7 +30,7 @@ ActionController::Routing::Routes.draw do |map|
}
end
end
- map.resources :users, :member => {:backlog => :get, :roles => :get} do |user|
+ map.resources :users, :member => {:backlog => :get, :roles => :get, :notifications => :post} do |user|
user.resources :tasks
end
diff --git a/config/schedules.yml.example b/config/schedules.yml.example
index 75741b4..88d55a7 100644
--- a/config/schedules.yml.example
+++ b/config/schedules.yml.example
@@ -1,10 +1,13 @@
---
:no_activity_or_no_task_recorded_time:
- :hours: 5
+ :hours: 3
:minutes: 0
+ :daily_updates:
+ :hours: 3
+ :minutes: 15
:sprints_products_status_time:
- :hours: 5
- :minutes: 0
+ :hours: 3
+ :minutes: 30
:user_verification_expiration:
:hours: 3
- :minutes: 0
+ :minutes: 45
diff --git a/db/migrate/020_create_notifications.rb b/db/migrate/020_create_notifications.rb
new file mode 100644
index 0000000..11f9643
--- /dev/null
+++ b/db/migrate/020_create_notifications.rb
@@ -0,0 +1,19 @@
+class CreateNotifications < ActiveRecord::Migration
+ def self.up
+ create_table :notifications do |t|
+ t.integer :user_id
+ t.boolean :task_reminders
+ t.boolean :daily_updates
+
+ t.timestamps
+ end
+
+ User.find(:all).each do |user|
+ user .notifications = Notifications.new(:task_reminders => true, :daily_updates => true)
+ end
+ end
+
+ def self.down
+ drop_table :notifications
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index aec15d9..6215516 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -9,7 +9,7 @@
#
# It's strongly recommended to check this file into your version control system.
-ActiveRecord::Schema.define(:version => 19) do
+ActiveRecord::Schema.define(:version => 20) do
create_table "backlog_items", :force => true do |t|
t.integer "sprint_id", :null => false
@@ -23,6 +23,15 @@ ActiveRecord::Schema.define(:version => 19) do
end
add_index "backlog_items", ["sprint_id", "user_story_id"], :name => "index_backlog_items_on_sprint_id_and_user_story_id", :unique => true
+ add_index "backlog_items", ["user_story_id"], :name => "fk_backlog_items_user_story"
+
+ create_table "notifications", :force => true do |t|
+ t.integer "user_id"
+ t.boolean "task_reminders"
+ t.boolean "daily_updates"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
create_table "product_roles", :force => true do |t|
t.integer "user_id", :null => false
@@ -34,7 +43,9 @@ ActiveRecord::Schema.define(:version => 19) do
t.boolean "is_approved", :default => false
end
- add_index "product_roles", ["product_id", "user_id"], :name => "index_product_roles_on_user_id_and_product_id", :unique => true
+ add_index "product_roles", ["user_id", "product_id"], :name => "index_product_roles_on_user_id_and_product_id", :unique => true
+ add_index "product_roles", ["product_id"], :name => "fk_user_role_product"
+ add_index "product_roles", ["role_id"], :name => "fk_user_role_role"
create_table "products", :force => true do |t|
t.integer "project_id"
@@ -46,6 +57,7 @@ ActiveRecord::Schema.define(:version => 19) do
end
add_index "products", ["name"], :name => "index_products_on_name", :unique => true
+ add_index "products", ["project_id"], :name => "fk_products_project"
create_table "projects", :force => true do |t|
t.integer "owner_id", :null => false
@@ -57,6 +69,7 @@ ActiveRecord::Schema.define(:version => 19) do
end
add_index "projects", ["name"], :name => "index_projects_on_name", :unique => true
+ add_index "projects", ["owner_id"], :name => "fk_product_owner"
create_table "remaining_hours_estimates", :force => true do |t|
t.integer "backlog_item_id", :null => false
@@ -67,6 +80,9 @@ ActiveRecord::Schema.define(:version => 19) do
t.datetime "estimated_on", :null => false
end
+ add_index "remaining_hours_estimates", ["backlog_item_id"], :name => "fk_remaining_hours_item"
+ add_index "remaining_hours_estimates", ["user_id"], :name => "fk_remaining_hours_user"
+
create_table "roles", :force => true do |t|
t.string "name", :null => false
t.boolean "can_manage_backlog_items", :default => false, :null => false
@@ -98,6 +114,8 @@ ActiveRecord::Schema.define(:version => 19) do
t.integer "status", :default => 0, :null => false
end
+ add_index "sprints", ["product_id"], :name => "fk_sprint_product"
+
create_table "tasks", :force => true do |t|
t.integer "backlog_item_id", :null => false
t.integer "primary_id", :null => false
@@ -109,6 +127,10 @@ ActiveRecord::Schema.define(:version => 19) do
t.datetime "updated_at"
end
+ add_index "tasks", ["backlog_item_id"], :name => "fk_task_backlog_item"
+ add_index "tasks", ["primary_id"], :name => "fk_task_primary"
+ add_index "tasks", ["backup_id"], :name => "fk_task_backup"
+
create_table "user_privileges", :force => true do |t|
t.integer "user_id", :null => false
t.boolean "admin_projects", :default => false, :null => false
@@ -117,6 +139,8 @@ ActiveRecord::Schema.define(:version => 19) do
t.datetime "updated_at"
end
+ add_index "user_privileges", ["user_id"], :name => "fk_privilege_user"
+
create_table "user_stories", :force => true do |t|
t.integer "product_id"
t.integer "priority"
@@ -127,6 +151,8 @@ ActiveRecord::Schema.define(:version => 19) do
t.datetime "updated_at"
end
+ add_index "user_stories", ["product_id"], :name => "fk_user_story_product"
+
create_table "user_verifications", :force => true do |t|
t.integer "user_id", :null => false
t.string "token", :limit => 16, :null => false
diff --git a/public/stylesheets/lists.css b/public/stylesheets/lists.css
index 4db910b..c5942c9 100644
--- a/public/stylesheets/lists.css
+++ b/public/stylesheets/lists.css
@@ -28,7 +28,7 @@ col.name {
}
col.description {
-
+ width: 100%;
}
col.number {
diff --git a/test/fixtures/notifications.yml b/test/fixtures/notifications.yml
new file mode 100644
index 0000000..ac0dd71
--- /dev/null
+++ b/test/fixtures/notifications.yml
@@ -0,0 +1,6 @@
+<% User.find(:all).each do |user| %>
+ <%= "#{user.id}_notifications:" %>
+ user_id: <%= user.id %>
+ task_reminders: false
+ daily_updates: false
+ <% end %>
diff --git a/test/functional/tasks_controller_test.rb b/test/functional/tasks_controller_test.rb
index 666fa4e..e2ed4a5 100644
--- a/test/functional/tasks_controller_test.rb
+++ b/test/functional/tasks_controller_test.rb
@@ -30,13 +30,13 @@ class TasksControllerTest < ActionController::TestCase
raise "Task must be owned by the user!" unless @task.primary_id == @user.id
@item = @task.backlog_item
- raise "Item cannot be closed!" if @item.closed?
+ raise "Item cannot be closed!" if @item.completed?
@sprint = @item.sprint
@product = @sprint.product
@closed_item = backlog_items(:closed_backlog_item)
- raise "Item must be closed!" unless @closed_item.closed?
+ raise "Item must be closed!" unless @closed_item.completed?
@owner = @item.owner
@nonowner = users(:jdonuts)
diff --git a/test/functional/user_items_test.rb b/test/functional/user_items_test.rb
index 628cd35..ef4354d 100644
--- a/test/functional/user_items_test.rb
+++ b/test/functional/user_items_test.rb
@@ -61,7 +61,7 @@ class UserItemsTest < ActionController::TestCase
raise "Item does not belong to sprint!" unless @unowned_item.sprint_id == @sprint.id
@closed_item = backlog_items(:closed_backlog_item)
- raise "item must not be active!" unless @closed_item.closed?
+ raise "item must not be active!" unless @closed_item.completed?
raise "Item does not belong to sprint!" unless @closed_item.sprint_id == @sprint.id
@member = users(:mcpierce)
diff --git a/test/functional/users_controller_test.rb b/test/functional/users_controller_test.rb
index 7375369..cebc450 100644
--- a/test/functional/users_controller_test.rb
+++ b/test/functional/users_controller_test.rb
@@ -17,6 +17,7 @@
require File.dirname(__FILE__) + '/../test_helper'
class UsersControllerTest < ActionController::TestCase
+ fixtures :notifications
fixtures :user_verifications
fixtures :users
@@ -160,8 +161,11 @@ class UsersControllerTest < ActionController::TestCase
post :create, {:user => @new_user}, {:user_id => @admin.id}
assert_redirected_to user_path(assigns['this_user'])
- assert User.find_by_email(@new_user[:email]),
- "A new user should have been created."
+ result = User.find_by_email(@new_user[:email])
+
+ assert result, "A new user should have been created."
+ assert result.privileges, "No privileges were created."
+ assert result.notifications, "No notifications were created."
email = @emails.first
assert email, "No email was generated."
@@ -309,4 +313,36 @@ class UsersControllerTest < ActionController::TestCase
assert_response :success
assert assigns['product_roles'], "Failed to load the user's roles."
end
+
+ # Ensures that notifications updates require the user be logged in.
+ def test_notifications_as_anonymous
+ post :notifications
+
+ assert_redirected_to login_path
+ end
+
+ # Ensures that a valid user's required.
+ def test_notifications_with_invalid_user
+ post :notifications, {}, {:user_id => @user.id}
+
+ assert_redirected_to users_path
+ end
+
+ # Ensures that a user cannot edit someone else's notifications.
+ def test_notifications_for_other_user
+ post :notifications, {:id => @other_user.id}, {:user_id => @user.id}
+
+ assert_redirected_to user_path(@other_user)
+ end
+
+ # Ensures that notifications are updated.
+ def test_notifications
+ post :notifications,
+ {:id => @user.id, :notifications => {:task_reminders => true}},
+ {:user_id => @user.id}
+
+ assert_redirected_to user_path(@user)
+ assert Notifications.find_by_user_id((a)user.id).task_reminders,
+ "Notification update should've been saved."
+ end
end
diff --git a/test/unit/backlog_item_test.rb b/test/unit/backlog_item_test.rb
index afe4669..c80c54a 100644
--- a/test/unit/backlog_item_test.rb
+++ b/test/unit/backlog_item_test.rb
@@ -26,7 +26,8 @@ class BacklogItemTest < ActiveSupport::TestCase
def setup
@item = BacklogItem.new(
:sprint_id => sprints(:active_sprint).id,
- :user_story_id => user_stories(:create_login).id)
+ :user_story_id => user_stories(:create_login).id,
+ :estimated_hours => 5.0)
@owned_backlog_item = backlog_items(:owned_backlog_item)
raise "Owned item must have an owner!" unless @owned_backlog_item.owner
diff --git a/test/unit/notifications_test.rb b/test/unit/notifications_test.rb
new file mode 100644
index 0000000..d515277
--- /dev/null
+++ b/test/unit/notifications_test.rb
@@ -0,0 +1,8 @@
+require 'test_helper'
+
+class NotificationsTest < ActiveSupport::TestCase
+ # Replace this with your real tests.
+ def test_truth
+ assert true
+ end
+end
--
1.6.0.2
15 years, 4 months
[PROJXP [PATCH] Restored the "Save, Add Another" button to the user story edit page. #55
by Darryl L. Pierce
When the product owner is creating a new user story, they can select this button
to create a save the current user story and go directly to creating a new one.
Signed-off-by: Darryl L. Pierce <mcpierce(a)gmail.com>
---
app/controllers/stories_controller.rb | 12 +++++++++++-
app/views/stories/_edit.html.erb | 5 ++++-
test/functional/stories_controller_test.rb | 14 ++++++++++++++
3 files changed, 29 insertions(+), 2 deletions(-)
diff --git a/app/controllers/stories_controller.rb b/app/controllers/stories_controller.rb
index 45a66d3..baa58b8 100644
--- a/app/controllers/stories_controller.rb
+++ b/app/controllers/stories_controller.rb
@@ -76,7 +76,17 @@ class StoriesController < ApplicationController
if @user_story.save
flash[:message] = "The user story was successfully created."
- format.html { redirect_to params[:url] ? params[:url] : product_stories_path(@product) }
+
+ puts "params['add_another']=#{params['add_another']}"
+
+ if params[:add_another]
+ format.html {
+ redirect_to params[:url] ?
+ new_product_story_path(@product, :url => params[:url]) :
+ new_product_story_path(@product) }
+ else
+ format.html { redirect_to params[:url] ? params[:url] : product_stories_path(@product) }
+ end
else
@title = "User Story (New)"
@user_story.valid?
diff --git a/app/views/stories/_edit.html.erb b/app/views/stories/_edit.html.erb
index 078505a..7a30e1b 100644
--- a/app/views/stories/_edit.html.erb
+++ b/app/views/stories/_edit.html.erb
@@ -45,7 +45,10 @@
<tr>
<td class="buttons" colspan="2">
- <%= submit_tag "Save" %>
+ <%= submit_tag "Save", :name => :save %>
+ <% if @user_story.new_record? %>
+ <%= submit_tag "Save, Create Another", :name => "add_another" %>
+ <% end %>
</td>
</tr>
</table>
diff --git a/test/functional/stories_controller_test.rb b/test/functional/stories_controller_test.rb
index 23b001f..977453d 100644
--- a/test/functional/stories_controller_test.rb
+++ b/test/functional/stories_controller_test.rb
@@ -227,6 +227,20 @@ class StoriesControllerTest < ActionController::TestCase
"User story was not saved."
end
+ # Ensures that specifying to add another the user is returned to the
+ # new action
+ def test_create_and_add_another
+ post :create,
+ {:product_id => @product.id,
+ :user_story => @new_story,
+ 'add_another' => 'yup'},
+ {:user_id => @owner.id}
+
+ assert_redirected_to new_product_story_path(@product)
+ assert UserStory.find_by_title(@new_story[:title]),
+ "User story was not saved."
+ end
+
# Ensures that anonymous users can't update stories.
def test_update_as_anonymous
put :update
--
1.5.6.5
15 years, 4 months
[PROJXP [PATCH] When populating a sprint, a backlog item cannot have an estimate of 0. #57
by LAN-SUN-LUK Benjamin
Now, no data are lost when an error occured.
Signed-off-by: Benjamin LAN-SUN-LUK <benjamin.lan-sun-luk(a)supinfo.com>
---
app/controllers/sprints_controller.rb | 18 ++++++++----------
app/models/backlog_item.rb | 4 ++++
app/views/sprints/plan.html.erb | 6 +++---
3 files changed, 15 insertions(+), 13 deletions(-)
diff --git a/app/controllers/sprints_controller.rb
b/app/controllers/sprints_controller.rb
index df336ad..713d330 100644
--- a/app/controllers/sprints_controller.rb
+++ b/app/controllers/sprints_controller.rb
@@ -130,14 +130,6 @@ class SprintsController < ApplicationController
@title = "Sprint #{(a)sprint.id} (Planning)"
@user_stories = UserStory.find_all_by_product_id((a)product.id)
- @estimates = Hash.new
- @selected = Array.new
-
- BacklogItem.find_all_by_sprint_id((a)sprint.id).each do |item|
- @estimates["#{item.user_story_id}"] = item.estimated_hours
- @selected << item.user_story_id
- end
-
format.html
else
flash[:error] = "You are not allowed to plan this sprint."
@@ -167,7 +159,7 @@ class SprintsController < ApplicationController
@sprint.backlog_items << BacklogItem.new(
:sprint_id => @sprint.id,
:user_story_id => selected,
- :estimated_hours => estimate.to_f)
+ :estimated_hours => estimate)
end
end
@@ -177,8 +169,14 @@ class SprintsController < ApplicationController
else
@title = "Sprint #{(a)sprint.id} (Planning)"
flash[:error] = "There was an error updating the sprint
backlog."
+ @sprint.backlog_items.each do |backlog_item|
+ if backlog_item.errors['estimated_hours']
+ flash[:error] += ' ' +
backlog_item.errors['estimated_hours']
+ break
+ end
+ end
@user_stories = UserStory.find_all_by_product_id((a)product.id)
- format.html { render :action => :plan }
+ format.html { render :action => :plan, :selected =>
params[:selected] }
end
else
flash[:error] = "You are not allowed to plan this sprint."
diff --git a/app/models/backlog_item.rb b/app/models/backlog_item.rb
index d149172..5a88800 100644
--- a/app/models/backlog_item.rb
+++ b/app/models/backlog_item.rb
@@ -24,6 +24,10 @@ class BacklogItem < ActiveRecord::Base
validates_presence_of :user_story_id,
:message => 'You need to specify a user story.'
+ validates_numericality_of :estimated_hours,
+ :greater_than => 0,
+ :message => 'Estimated hours must be numeric and greater than 0.'
+
belongs_to :sprint
belongs_to :user_story
belongs_to :owner, :class_name => 'User', :foreign_key => 'owner_id'
diff --git a/app/views/sprints/plan.html.erb
b/app/views/sprints/plan.html.erb
index ba6bb95..7f2ecde 100644
--- a/app/views/sprints/plan.html.erb
+++ b/app/views/sprints/plan.html.erb
@@ -22,13 +22,13 @@
<tbody>
<% @user_stories.each_with_index do |story, index| %>
<% row_class = index%2 == 0 ? 'even' : 'odd' %>
-
+ <% backlog_item =
@sprint.backlog_items.find_by_user_story_id(story.id) %>
<tr class="<%= row_class %>">
<td><%= story.id %></td>
<td><%= story.priority %></td>
<td>
- <%= check_box_tag "selected[]", story.id,
@selected.include?(story.id) %>
- <%= text_field_tag "estimates['#{story.id}']",
@estimates["#{story.id}"],
+ <%= check_box_tag "selected[]", story.id, (backlog_item ||
(params[:selected].include?(story.id.to_s) if params[:selected])) %>
+ <%= text_field_tag "estimates['#{story.id}']",
(params[:estimates]["'#{story.id.to_s}'"] if params[:estimates]) ||
(backlog_item.estimated_hours if backlog_item),
:size => 4, :maxlength => 4%>
</td>
<td><%= get_first_sentence story.description %></td>
--
1.6.0.2
15 years, 4 months