[PROJXP [PATCH] Double render error during sprint population. #26
by Benjamin LAN-SUN-LUK
Signed-off-by: Benjamin LAN-SUN-LUK <benjamin.lan-sun-luk(a)supinfo.com>
---
app/controllers/sprint_controller.rb | 4 +++-
1 files changed, 3 insertions(+), 1 deletions(-)
diff --git a/app/controllers/sprint_controller.rb b/app/controllers/sprint_controller.rb
index 772efc4..8cf6897 100644
--- a/app/controllers/sprint_controller.rb
+++ b/app/controllers/sprint_controller.rb
@@ -181,13 +181,15 @@ class SprintController < ApplicationController
def validate_estimates
if request.post?
if params['user_story_id']
+ must_redirect = false
params['user_story_id'].each do |id|
estimate = params["estimate_for_#{id}"]
unless estimate && (estimate.to_f > 0.0)
flash[:message] = 'All backlog items must have a valid estimate.'
- redirect_to :action => :populate
+ must_redirect = true
end
end
+ redirect_to :action => :populate if must_redirect
end
end
end
--
1.6.0.2
15 years, 5 months
[PROJXP [PATCH] Product owners are sent a daily status email. #11
by Benjamin LAN-SUN-LUK
Now, the time of schedules are configurable.
Signed-off-by: Benjamin LAN-SUN-LUK <benjamin.lan-sun-luk(a)supinfo.com>
---
app/models/product.rb | 1 +
app/models/user_mailer.rb | 10 ++++++
.../user_mailer/sprints_products_status.html.erb | 9 +++++
config/initializers/schedules.rb | 34 ++++++++++++++++++-
config/schedules.yml | 7 ++++
5 files changed, 59 insertions(+), 2 deletions(-)
create mode 100644 app/views/user_mailer/sprints_products_status.html.erb
create mode 100644 config/schedules.yml
diff --git a/app/models/product.rb b/app/models/product.rb
index 1cd3035..78cff45 100644
--- a/app/models/product.rb
+++ b/app/models/product.rb
@@ -30,6 +30,7 @@ class Product < ActiveRecord::Base
has_many :backlog, :class_name => 'UserStory', :order => 'priority ASC'
has_many :product_roles
has_many :users, :through => :product_roles
+ has_many :sprints
# Returns whether the user can create sprints for this product.
#
diff --git a/app/models/user_mailer.rb b/app/models/user_mailer.rb
index 896a688..9c70473 100644
--- a/app/models/user_mailer.rb
+++ b/app/models/user_mailer.rb
@@ -36,4 +36,14 @@ class UserMailer < ActionMailer::Base
subject "No task has been detected"
body :user => user, :backlog_items => backlog_items
end
+
+ # Send an email to a user to notify him the status of each sprint of his
+ # products.
+ #
+ def sprints_products_status(user, products)
+ recipients user.email
+ from MAIL_CONFIG[:from]
+ subject "Status of sprints"
+ body :user => user, :products => products
+ end
end
diff --git a/app/views/user_mailer/sprints_products_status.html.erb b/app/views/user_mailer/sprints_products_status.html.erb
new file mode 100644
index 0000000..9ae07c9
--- /dev/null
+++ b/app/views/user_mailer/sprints_products_status.html.erb
@@ -0,0 +1,9 @@
+Dear <%= @user.display_name %>,
+
+Here the sprint's status of every product.
+<% @products.each do |product| %>
+- <%= product.name %>
+<% product.sprints.each do |sprint| %>
+ - <%= sprint.title %> => <%= Sprint::STATUS_TEXT.collect { |s| s.first if (s.last == sprint.status) }.compact %>
+<% end %>
+<% end %>
diff --git a/config/initializers/schedules.rb b/config/initializers/schedules.rb
index fb58101..aa8a515 100644
--- a/config/initializers/schedules.rb
+++ b/config/initializers/schedules.rb
@@ -2,6 +2,10 @@ require 'fastthread'
require 'openwfe/util/scheduler'
include OpenWFE
+# Load the schedules configuration
+schedules_file_path = "#{RAILS_ROOT}/config/schedules.yml"
+SCHEDULES_CONFIG = YAML.load(File.open(schedules_file_path))
+
# Initialize a threads array
threads = []
@@ -9,8 +13,10 @@ threads << Thread.new do
scheduler = Scheduler.new
scheduler.start
- # Do this everyday at 05:00 am
- scheduler.schedule('0 5 * * *') do # TODO Time must be configurable
+ hours = SCHEDULES_CONFIG[:no_activity_or_no_task_recorded_time][:hours]
+ minutes = SCHEDULES_CONFIG[:no_activity_or_no_task_recorded_time][:minutes]
+
+ scheduler.schedule("#{minutes} #{hours} * * *") do
user_no_activity_on_backlog_items = {}
user_no_tasks_on_backlog_items = {}
@@ -50,5 +56,29 @@ threads << Thread.new do
scheduler.join
end
+threads << Thread.new do
+ scheduler = Scheduler.new
+ scheduler.start
+
+ hours = SCHEDULES_CONFIG[:sprints_products_status_time][:hours]
+ minutes = SCHEDULES_CONFIG[:sprints_products_status_time][:minutes]
+
+ scheduler.schedule("#{minutes} #{hours} * * *") do
+ user_with_products = {}
+
+ Product.find(:all).each do |product|
+ product_owner = product.owner
+ user_with_products[product_owner] ||= []
+ user_with_products[product_owner] << product
+ end
+
+ user_with_products.each do |owner, products|
+ UserMailer.deliver_sprints_products_status(owner, products)
+ end
+ end
+
+ scheduler.join
+end
+
# Run all threads
threads.each { |thread| thread.run }
\ No newline at end of file
diff --git a/config/schedules.yml b/config/schedules.yml
new file mode 100644
index 0000000..7883c15
--- /dev/null
+++ b/config/schedules.yml
@@ -0,0 +1,7 @@
+---
+ :no_activity_or_no_task_recorded_time:
+ :hours: 5
+ :minutes: 0
+ :sprints_products_status_time:
+ :hours: 5
+ :minutes: 0
\ No newline at end of file
--
1.6.0.2
15 years, 5 months
[PROJXP [PATCH] Product owners are sent a daily status email. #11
by LAN-SUN-LUK Benjamin
Now, the time of schedules are configurable.
Signed-off-by: Benjamin LAN-SUN-LUK <benjamin.lan-sun-luk(a)supinfo.com>
---
app/models/product.rb | 1 +
app/models/user_mailer.rb | 10 +++++
.../user_mailer/sprints_products_status.html.erb | 9 ++++
config/initializers/schedules.rb | 34 ++++++++++++++++-
config/schedules.yml | 7 +++
db/schema.rb | 40 ++++++++++++-------
6 files changed, 84 insertions(+), 17 deletions(-)
create mode 100644 app/views/user_mailer/sprints_products_status.html.erb
create mode 100644 config/schedules.yml
diff --git a/app/models/product.rb b/app/models/product.rb
index 1cd3035..78cff45 100644
--- a/app/models/product.rb
+++ b/app/models/product.rb
@@ -30,6 +30,7 @@ class Product < ActiveRecord::Base
has_many :backlog, :class_name => 'UserStory', :order => 'priority ASC'
has_many :product_roles
has_many :users, :through => :product_roles
+ has_many :sprints
# Returns whether the user can create sprints for this product.
#
diff --git a/app/models/user_mailer.rb b/app/models/user_mailer.rb
index 896a688..9c70473 100644
--- a/app/models/user_mailer.rb
+++ b/app/models/user_mailer.rb
@@ -36,4 +36,14 @@ class UserMailer < ActionMailer::Base
subject "No task has been detected"
body :user => user, :backlog_items => backlog_items
end
+
+ # Send an email to a user to notify him the status of each sprint of his
+ # products.
+ #
+ def sprints_products_status(user, products)
+ recipients user.email
+ from MAIL_CONFIG[:from]
+ subject "Status of sprints"
+ body :user => user, :products => products
+ end
end
diff --git a/app/views/user_mailer/sprints_products_status.html.erb b/app/views/user_mailer/sprints_products_status.html.erb
new file mode 100644
index 0000000..9ae07c9
--- /dev/null
+++ b/app/views/user_mailer/sprints_products_status.html.erb
@@ -0,0 +1,9 @@
+Dear <%= @user.display_name %>,
+
+Here the sprint's status of every product.
+<% @products.each do |product| %>
+- <%= product.name %>
+<% product.sprints.each do |sprint| %>
+ - <%= sprint.title %> => <%= Sprint::STATUS_TEXT.collect { |s| s.first if (s.last == sprint.status) }.compact %>
+<% end %>
+<% end %>
diff --git a/config/initializers/schedules.rb b/config/initializers/schedules.rb
index fb58101..aa8a515 100644
--- a/config/initializers/schedules.rb
+++ b/config/initializers/schedules.rb
@@ -2,6 +2,10 @@ require 'fastthread'
require 'openwfe/util/scheduler'
include OpenWFE
+# Load the schedules configuration
+schedules_file_path = "#{RAILS_ROOT}/config/schedules.yml"
+SCHEDULES_CONFIG = YAML.load(File.open(schedules_file_path))
+
# Initialize a threads array
threads = []
@@ -9,8 +13,10 @@ threads << Thread.new do
scheduler = Scheduler.new
scheduler.start
- # Do this everyday at 05:00 am
- scheduler.schedule('0 5 * * *') do # TODO Time must be configurable
+ hours = SCHEDULES_CONFIG[:no_activity_or_no_task_recorded_time][:hours]
+ minutes = SCHEDULES_CONFIG[:no_activity_or_no_task_recorded_time][:minutes]
+
+ scheduler.schedule("#{minutes} #{hours} * * *") do
user_no_activity_on_backlog_items = {}
user_no_tasks_on_backlog_items = {}
@@ -50,5 +56,29 @@ threads << Thread.new do
scheduler.join
end
+threads << Thread.new do
+ scheduler = Scheduler.new
+ scheduler.start
+
+ hours = SCHEDULES_CONFIG[:sprints_products_status_time][:hours]
+ minutes = SCHEDULES_CONFIG[:sprints_products_status_time][:minutes]
+
+ scheduler.schedule("#{minutes} #{hours} * * *") do
+ user_with_products = {}
+
+ Product.find(:all).each do |product|
+ product_owner = product.owner
+ user_with_products[product_owner] ||= []
+ user_with_products[product_owner] << product
+ end
+
+ user_with_products.each do |owner, products|
+ UserMailer.deliver_sprints_products_status(owner, products)
+ end
+ end
+
+ scheduler.join
+end
+
# Run all threads
threads.each { |thread| thread.run }
\ No newline at end of file
diff --git a/config/schedules.yml b/config/schedules.yml
new file mode 100644
index 0000000..7883c15
--- /dev/null
+++ b/config/schedules.yml
@@ -0,0 +1,7 @@
+---
+ :no_activity_or_no_task_recorded_time:
+ :hours: 5
+ :minutes: 0
+ :sprints_products_status_time:
+ :hours: 5
+ :minutes: 0
\ No newline at end of file
diff --git a/db/schema.rb b/db/schema.rb
index 331a1e5..6802559 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 => 15) do
+ActiveRecord::Schema.define(:version => 16) do
create_table "backlog_items", :force => true do |t|
t.integer "sprint_id", :null => false
@@ -50,8 +50,8 @@ ActiveRecord::Schema.define(:version => 15) do
add_index "products", ["project_id"], :name => "fk_products_project"
create_table "projects", :force => true do |t|
- t.integer "owner_id", :null => false
- t.string "name", :limit => 50, :null => false
+ t.integer "owner_id", :null => false
+ t.string "name", :limit => 50, :default => "", :null => false
t.string "url"
t.text "description"
t.datetime "created_at"
@@ -74,7 +74,7 @@ ActiveRecord::Schema.define(:version => 15) do
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.string "name", :default => "", :null => false
t.boolean "can_manage_backlog_items", :default => false, :null => false
t.boolean "can_close_user_story", :default => false, :null => false
t.datetime "created_at"
@@ -84,7 +84,7 @@ ActiveRecord::Schema.define(:version => 15) do
add_index "roles", ["name"], :name => "index_roles_on_name", :unique => true
create_table "sessions", :force => true do |t|
- t.string "session_id", :null => false
+ t.string "session_id", :default => "", :null => false
t.text "data"
t.datetime "created_at"
t.datetime "updated_at"
@@ -94,14 +94,14 @@ ActiveRecord::Schema.define(:version => 15) do
add_index "sessions", ["updated_at"], :name => "index_sessions_on_updated_at"
create_table "sprints", :force => true do |t|
- t.integer "product_id", :null => false
- t.string "title", :limit => 100, :null => false
- t.date "start", :null => false
- t.integer "duration", :null => false
- t.string "goals", :limit => 1000, :null => false
+ t.integer "product_id", :null => false
+ t.string "title", :limit => 100, :default => "", :null => false
+ t.date "start", :null => false
+ t.integer "duration", :null => false
+ t.string "goals", :limit => 1000, :default => "", :null => false
t.datetime "created_at"
t.datetime "updated_at"
- t.integer "status", :default => 0, :null => false
+ t.integer "status", :default => 0, :null => false
end
add_index "sprints", ["product_id"], :name => "fk_sprint_product"
@@ -143,11 +143,21 @@ ActiveRecord::Schema.define(:version => 15) do
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, :default => "", :null => false
+ t.datetime "sent", :null => false
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+ add_index "user_verifications", ["user_id"], :name => "index_user_verifications_on_user_id", :unique => true
+
create_table "users", :force => true do |t|
- t.string "email", :limit => 128, :null => false
- t.string "display_name", :limit => 128, :null => false
- t.string "hashed_password", :null => false
- t.string "salt", :null => false
+ t.string "email", :limit => 128, :default => "", :null => false
+ t.string "display_name", :limit => 128, :default => "", :null => false
+ t.string "hashed_password", :default => "", :null => false
+ t.string "salt", :default => "", :null => false
t.text "introduction"
t.datetime "created_at"
t.datetime "updated_at"
--
1.6.0.2
15 years, 5 months
[PROJXP [PATCH] Product owners are sent a daily status email. #11
by Benjamin LAN-SUN-LUK
Now, the time of schedules are configurable.
Signed-off-by: Benjamin LAN-SUN-LUK <benjamin.lan-sun-luk(a)supinfo.com>
---
app/models/product.rb | 1 +
app/models/user_mailer.rb | 10 +++++
.../user_mailer/sprints_products_status.html.erb | 9 ++++
config/initializers/schedules.rb | 34 ++++++++++++++++-
config/schedules.yml | 7 +++
db/schema.rb | 40 ++++++++++++-------
6 files changed, 84 insertions(+), 17 deletions(-)
create mode 100644 app/views/user_mailer/sprints_products_status.html.erb
create mode 100644 config/schedules.yml
diff --git a/app/models/product.rb b/app/models/product.rb
index 1cd3035..78cff45 100644
--- a/app/models/product.rb
+++ b/app/models/product.rb
@@ -30,6 +30,7 @@ class Product < ActiveRecord::Base
has_many :backlog, :class_name => 'UserStory', :order => 'priority ASC'
has_many :product_roles
has_many :users, :through => :product_roles
+ has_many :sprints
# Returns whether the user can create sprints for this product.
#
diff --git a/app/models/user_mailer.rb b/app/models/user_mailer.rb
index 896a688..9c70473 100644
--- a/app/models/user_mailer.rb
+++ b/app/models/user_mailer.rb
@@ -36,4 +36,14 @@ class UserMailer < ActionMailer::Base
subject "No task has been detected"
body :user => user, :backlog_items => backlog_items
end
+
+ # Send an email to a user to notify him the status of each sprint of his
+ # products.
+ #
+ def sprints_products_status(user, products)
+ recipients user.email
+ from MAIL_CONFIG[:from]
+ subject "Status of sprints"
+ body :user => user, :products => products
+ end
end
diff --git a/app/views/user_mailer/sprints_products_status.html.erb b/app/views/user_mailer/sprints_products_status.html.erb
new file mode 100644
index 0000000..9ae07c9
--- /dev/null
+++ b/app/views/user_mailer/sprints_products_status.html.erb
@@ -0,0 +1,9 @@
+Dear <%= @user.display_name %>,
+
+Here the sprint's status of every product.
+<% @products.each do |product| %>
+- <%= product.name %>
+<% product.sprints.each do |sprint| %>
+ - <%= sprint.title %> => <%= Sprint::STATUS_TEXT.collect { |s| s.first if (s.last == sprint.status) }.compact %>
+<% end %>
+<% end %>
diff --git a/config/initializers/schedules.rb b/config/initializers/schedules.rb
index fb58101..aa8a515 100644
--- a/config/initializers/schedules.rb
+++ b/config/initializers/schedules.rb
@@ -2,6 +2,10 @@ require 'fastthread'
require 'openwfe/util/scheduler'
include OpenWFE
+# Load the schedules configuration
+schedules_file_path = "#{RAILS_ROOT}/config/schedules.yml"
+SCHEDULES_CONFIG = YAML.load(File.open(schedules_file_path))
+
# Initialize a threads array
threads = []
@@ -9,8 +13,10 @@ threads << Thread.new do
scheduler = Scheduler.new
scheduler.start
- # Do this everyday at 05:00 am
- scheduler.schedule('0 5 * * *') do # TODO Time must be configurable
+ hours = SCHEDULES_CONFIG[:no_activity_or_no_task_recorded_time][:hours]
+ minutes = SCHEDULES_CONFIG[:no_activity_or_no_task_recorded_time][:minutes]
+
+ scheduler.schedule("#{minutes} #{hours} * * *") do
user_no_activity_on_backlog_items = {}
user_no_tasks_on_backlog_items = {}
@@ -50,5 +56,29 @@ threads << Thread.new do
scheduler.join
end
+threads << Thread.new do
+ scheduler = Scheduler.new
+ scheduler.start
+
+ hours = SCHEDULES_CONFIG[:sprints_products_status_time][:hours]
+ minutes = SCHEDULES_CONFIG[:sprints_products_status_time][:minutes]
+
+ scheduler.schedule("#{minutes} #{hours} * * *") do
+ user_with_products = {}
+
+ Product.find(:all).each do |product|
+ product_owner = product.owner
+ user_with_products[product_owner] ||= []
+ user_with_products[product_owner] << product
+ end
+
+ user_with_products.each do |owner, products|
+ UserMailer.deliver_sprints_products_status(owner, products)
+ end
+ end
+
+ scheduler.join
+end
+
# Run all threads
threads.each { |thread| thread.run }
\ No newline at end of file
diff --git a/config/schedules.yml b/config/schedules.yml
new file mode 100644
index 0000000..7883c15
--- /dev/null
+++ b/config/schedules.yml
@@ -0,0 +1,7 @@
+---
+ :no_activity_or_no_task_recorded_time:
+ :hours: 5
+ :minutes: 0
+ :sprints_products_status_time:
+ :hours: 5
+ :minutes: 0
\ No newline at end of file
diff --git a/db/schema.rb b/db/schema.rb
index 331a1e5..6802559 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 => 15) do
+ActiveRecord::Schema.define(:version => 16) do
create_table "backlog_items", :force => true do |t|
t.integer "sprint_id", :null => false
@@ -50,8 +50,8 @@ ActiveRecord::Schema.define(:version => 15) do
add_index "products", ["project_id"], :name => "fk_products_project"
create_table "projects", :force => true do |t|
- t.integer "owner_id", :null => false
- t.string "name", :limit => 50, :null => false
+ t.integer "owner_id", :null => false
+ t.string "name", :limit => 50, :default => "", :null => false
t.string "url"
t.text "description"
t.datetime "created_at"
@@ -74,7 +74,7 @@ ActiveRecord::Schema.define(:version => 15) do
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.string "name", :default => "", :null => false
t.boolean "can_manage_backlog_items", :default => false, :null => false
t.boolean "can_close_user_story", :default => false, :null => false
t.datetime "created_at"
@@ -84,7 +84,7 @@ ActiveRecord::Schema.define(:version => 15) do
add_index "roles", ["name"], :name => "index_roles_on_name", :unique => true
create_table "sessions", :force => true do |t|
- t.string "session_id", :null => false
+ t.string "session_id", :default => "", :null => false
t.text "data"
t.datetime "created_at"
t.datetime "updated_at"
@@ -94,14 +94,14 @@ ActiveRecord::Schema.define(:version => 15) do
add_index "sessions", ["updated_at"], :name => "index_sessions_on_updated_at"
create_table "sprints", :force => true do |t|
- t.integer "product_id", :null => false
- t.string "title", :limit => 100, :null => false
- t.date "start", :null => false
- t.integer "duration", :null => false
- t.string "goals", :limit => 1000, :null => false
+ t.integer "product_id", :null => false
+ t.string "title", :limit => 100, :default => "", :null => false
+ t.date "start", :null => false
+ t.integer "duration", :null => false
+ t.string "goals", :limit => 1000, :default => "", :null => false
t.datetime "created_at"
t.datetime "updated_at"
- t.integer "status", :default => 0, :null => false
+ t.integer "status", :default => 0, :null => false
end
add_index "sprints", ["product_id"], :name => "fk_sprint_product"
@@ -143,11 +143,21 @@ ActiveRecord::Schema.define(:version => 15) do
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, :default => "", :null => false
+ t.datetime "sent", :null => false
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+ add_index "user_verifications", ["user_id"], :name => "index_user_verifications_on_user_id", :unique => true
+
create_table "users", :force => true do |t|
- t.string "email", :limit => 128, :null => false
- t.string "display_name", :limit => 128, :null => false
- t.string "hashed_password", :null => false
- t.string "salt", :null => false
+ t.string "email", :limit => 128, :default => "", :null => false
+ t.string "display_name", :limit => 128, :default => "", :null => false
+ t.string "hashed_password", :default => "", :null => false
+ t.string "salt", :default => "", :null => false
t.text "introduction"
t.datetime "created_at"
t.datetime "updated_at"
--
1.6.0.2
15 years, 5 months
[PROJXP [PATCH] Users are sent a verification email on registration. #17
by Darryl L. Pierce
It also includes a new controller which handles verifying the user
after they follow the link in the email.
You will need to run a migration with this patch.
To set the hostname for where the user goes to verify themselves, update
the config/environments/{development, test, production}.rb file and enter
the correct public hostname.
It introduces a new model object, UserVerification. When a user
registers an account, an instance is created. As long as that object
exists, the user cannot do anything with the system.
Signed-off-by: Darryl L. Pierce <mcpierce(a)gmail.com>
---
app/controllers/home_controller.rb | 25 ----
app/controllers/user_controller.rb | 34 +++++
app/models/user.rb | 16 +++
app/models/user_mailer.rb | 17 ++-
app/models/user_verification.rb | 45 +++++++
app/views/user_mailer/email_verification.html.erb | 14 ++
config/environments/development.rb | 27 ++--
config/environments/production.rb | 25 ++--
config/environments/test.rb | 25 ++--
config/routes.rb | 4 +
db/migrate/016_create_user_verifications.rb | 20 +++
db/schema.rb | 20 +++-
test/fixtures/user_verifications.yml | 6 +
test/fixtures/users.yml | 8 +-
test/functional/home_controller_test.rb | 33 +----
test/functional/user_controller_test.rb | 147 +++++++++++++++------
test/unit/user_test.rb | 26 ++++-
test/unit/user_verification_test.rb | 77 +++++++++++
18 files changed, 431 insertions(+), 138 deletions(-)
create mode 100644 app/models/user_verification.rb
create mode 100644 app/views/user/verify.html.erb
create mode 100644 app/views/user_mailer/email_verification.html.erb
create mode 100644 db/migrate/016_create_user_verifications.rb
create mode 100644 test/fixtures/user_verifications.yml
create mode 100644 test/unit/user_verification_test.rb
diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb
index 35dd883..6b8030c 100644
--- a/app/controllers/home_controller.rb
+++ b/app/controllers/home_controller.rb
@@ -72,31 +72,6 @@ class HomeController < ApplicationController
@backlog = BacklogItem.find_all_by_state_and_owner_id(BacklogItem::STATE_ASSIGNED, session[:user_id])
end
- # Allows the user to register a new account. Users may only register if they
- # are not currently logged into an account. TODO We need to authenticate the
- # email address entered by a user. TODO Accounts that aren't verified afew a
- # set time should be deleted.
- #
- def register
- if @user
- flash[:message] = 'You are already registered.'
- redirect_to :action => :dashboard
- else if request.post?
- user = User.new(:email => params[:email],
- :display_name => params[:display_name],
- :introduction => params[:introduction])
-
- user.privileges = UserPrivilege.new
-
- user.password = params[:password]
- user.save!
-
- flash[:message] = 'Thank you. Please log in with your new account.'
- redirect_to :action => :login
- end
- end
- end
-
# Allows the user to email him a new password.
#
def generate_new_password
diff --git a/app/controllers/user_controller.rb b/app/controllers/user_controller.rb
index 6d13641..ebd6de6 100644
--- a/app/controllers/user_controller.rb
+++ b/app/controllers/user_controller.rb
@@ -26,6 +26,35 @@ class UserController < ApplicationController
end
+ def verify
+ if !(a)this_user.verified?
+ if params[:token]
+ if @this_user.verify(params[:token])
+ begin
+ User.transaction do
+ @this_user.verification.destroy
+ @this_user.verification = nil
+ @this_user.save!
+ end
+
+ flash[:message] = "#{(a)this_user.email} is now verified. Please login."
+ redirect_to :controller => :home, :action => :login
+ rescue Exception => error
+ flash[:error] = "ERROR: #{error.message}"
+ redirect_to error_url
+ end
+ else
+ flash[:error] = 'Verification failed.'
+ end
+ else
+ flash[:error] = 'No token was provided.'
+ end
+ else
+ flash[:error] = "#{(a)this_user.display_name} is already verified."
+ redirect_to error_url
+ end
+ end
+
def backlog
end
@@ -36,8 +65,13 @@ class UserController < ApplicationController
User.transaction do
@this_user = User.new(params[:user])
@this_user.privileges = UserPrivilege.new
+ @this_user.verification = UserVerification.new(
+ :sent => Date.today,
+ :token => UserVerification.create_token)
@this_user.save!
+
+ UserMailer.deliver_email_verification(@this_user,(a)this_user.verification.token)
end
redirect_to :controller => :home, :action => :login
diff --git a/app/models/user.rb b/app/models/user.rb
index 6554bf4..7d26194 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -25,6 +25,7 @@ class User < ActiveRecord::Base
has_one :privileges, :class_name => "UserPrivilege", :dependent => :destroy
has_many :product_roles
+ has_one :verification, :class_name => "UserVerification", :dependent => :destroy
attr_accessor :password_confirmation
validates_confirmation_of :password
@@ -77,6 +78,21 @@ class User < ActiveRecord::Base
result
end
+ # Attempts to verify the user by checking the supplied token against the
+ # existing verification token.
+ #
+ def verify(token)
+ raise Exception.new("User has no pending verifications!") if verification == nil
+
+ verification.token == token
+ end
+
+ # Returns whether the user has been verified.
+ #
+ def verified?
+ (verification == nil)
+ end
+
# Returns whether the user is allowed to create projects.
#
def create_projects?
diff --git a/app/models/user_mailer.rb b/app/models/user_mailer.rb
index bd42800..896a688 100644
--- a/app/models/user_mailer.rb
+++ b/app/models/user_mailer.rb
@@ -1,4 +1,13 @@
class UserMailer < ActionMailer::Base
+ # Sends an email verification to the specified user.
+ #
+ def email_verification(user, token)
+ recipients user.email
+ from MAIL_CONFIG[:from]
+ subject "Email verification"
+ body :user => user, :token => token
+ end
+
# Send an e-mail to an user and notify him his new password.
#
def new_generated_password(user, new_password)
@@ -8,8 +17,8 @@ class UserMailer < ActionMailer::Base
body :user => user, :new_password => new_password
end
- # Send an email to a user to notify him that no activity has been detected
- # in his backlog.
+ # 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)
recipients user.email
@@ -18,8 +27,8 @@ class UserMailer < ActionMailer::Base
body :user => user, :backlog_items => backlog_items
end
- # Send an email to a user to notify him that no task has been detected
- # in his backlog.
+ # Send an email to a user to notify him that no task has been detected in his
+ # backlog.
#
def no_task_recorded(user, backlog_items)
recipients user.email
diff --git a/app/models/user_verification.rb b/app/models/user_verification.rb
new file mode 100644
index 0000000..0431fe0
--- /dev/null
+++ b/app/models/user_verification.rb
@@ -0,0 +1,45 @@
+# user_verification.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/>.
+#
+
+# A +UserVerification+ object only exists when a user account needs to be
+# verified.
+#
+# A verification contains a +token+ which must be received from the user in
+# order to complete the validation. The token is 16 bytes long and consists only
+# of alphanumeric characters.
+#
+class UserVerification < ActiveRecord::Base
+ TOKEN_CHARACTERS = ('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a
+ TOKEN_SIZE = 16
+ TOKEN_PATTERN = "^[[:alnum:]]{#{TOKEN_SIZE}}$"
+
+ validates_presence_of :user_id
+
+ validates_presence_of :token
+ validates_format_of :token,
+ :with => /#{TOKEN_PATTERN}/
+
+ validates_presence_of :sent
+
+ belongs_to :user
+
+ # Generates a verification token.
+ #
+ def self.create_token
+ (0...TOKEN_SIZE).collect { TOKEN_CHARACTERS[Kernel.rand(TOKEN_CHARACTERS.length)] }.join
+ end
+end
diff --git a/app/views/user/verify.html.erb b/app/views/user/verify.html.erb
new file mode 100644
index 0000000..e69de29
diff --git a/app/views/user_mailer/email_verification.html.erb b/app/views/user_mailer/email_verification.html.erb
new file mode 100644
index 0000000..3190ac4
--- /dev/null
+++ b/app/views/user_mailer/email_verification.html.erb
@@ -0,0 +1,14 @@
+Dear <%= @user.display_name %>,
+
+This email is sent to you to verify that the address you entered:
+
+<%= @user.email %>
+
+is valid. To confirm this email address, please go to:
+
+<%= email_verification_url :controller => "user", :action => "verify", :id => @user.id, :token => @token %>
+
+If this link fails, please go to the verification page and enter the token:
+
+<%= @token %>
+
diff --git a/config/environments/development.rb b/config/environments/development.rb
index 9708140..420825d 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -1,18 +1,18 @@
-# developerment.rb
-# Copyright (C) 2008, Darryl L. Pierce <mcpierce(a)gmail.com>
+# developerment.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 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.
+# 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/>.
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Settings specified here will take precedence over those in config/environment.rb
@@ -32,4 +32,5 @@ config.action_controller.perform_caching = false
config.action_view.cache_template_extensions = false
# Don't care if the mailer can't send
-config.action_mailer.raise_delivery_errors = false
+config.action_mailer.raise_delivery_errors = true
+config.action_mailer.default_url_options = { :host => 'localhost:3000' }
diff --git a/config/environments/production.rb b/config/environments/production.rb
index eb7195a..ada1fd1 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -1,18 +1,18 @@
-# production.rb
-# Copyright (C) 2008, Darryl L. Pierce <mcpierce(a)gmail.com>
+# production.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 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.
+# 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/>.
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Settings specified here will take precedence over those in config/environment.rb
@@ -34,3 +34,4 @@ config.action_view.cache_template_loading = true
# Disable delivery errors, bad email addresses will be ignored
# config.action_mailer.raise_delivery_errors = false
+config.action_mailer.default_url_options = { :host => 'www.example.com' }
diff --git a/config/environments/test.rb b/config/environments/test.rb
index 00f6c34..e844d9b 100644
--- a/config/environments/test.rb
+++ b/config/environments/test.rb
@@ -1,18 +1,18 @@
-# test.rb
-# Copyright (C) 2008, Darryl L. Pierce <mcpierce(a)gmail.com>
+# test.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 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.
+# 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/>.
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Settings specified here will take precedence over those in config/environment.rb
@@ -37,3 +37,4 @@ config.action_controller.allow_forgery_protection = false
# The :test delivery method accumulates sent emails in the
# ActionMailer::Base.deliveries array.
config.action_mailer.delivery_method = :test
+config.action_mailer.default_url_options = { :host => 'localhost' }
diff --git a/config/routes.rb b/config/routes.rb
index 1108059..0f9ef23 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -23,6 +23,10 @@ ActionController::Routing::Routes.draw do |map|
# Error handling
map.error "/error", :controller => 'home', :action => 'error'
+ map.email_verification "verify/:id/:token",
+ :controller => 'user',
+ :action => 'verify'
+
# Install the default routes as the lowest priority.
map.connect ':controller/:action/:id'
map.connect ':controller/:action/:id.:format'
diff --git a/db/migrate/016_create_user_verifications.rb b/db/migrate/016_create_user_verifications.rb
new file mode 100644
index 0000000..7f947dc
--- /dev/null
+++ b/db/migrate/016_create_user_verifications.rb
@@ -0,0 +1,20 @@
+class CreateUserVerifications < ActiveRecord::Migration
+ def self.up
+ create_table :user_verifications do |t|
+ t.integer :user_id, :null => false
+ t.string :token, :null => false, :limit => 16
+ t.datetime :sent, :null => false
+
+ t.timestamps
+ end
+
+ add_index :user_verifications, :user_id, :unique => true
+
+ execute "alter table user_verifications add constraint fk_user_verification_user
+ foreign key (user_id) references users(id)"
+ end
+
+ def self.down
+ drop_table :user_verifications
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 4a20449..331a1e5 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -23,6 +23,7 @@ ActiveRecord::Schema.define(:version => 15) 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 "product_roles", :force => true do |t|
t.integer "user_id", :null => false
@@ -32,7 +33,9 @@ ActiveRecord::Schema.define(:version => 15) do
t.datetime "updated_at"
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"
@@ -44,6 +47,7 @@ ActiveRecord::Schema.define(:version => 15) 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
@@ -55,6 +59,7 @@ ActiveRecord::Schema.define(:version => 15) 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
@@ -65,6 +70,9 @@ ActiveRecord::Schema.define(:version => 15) 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
@@ -96,6 +104,8 @@ ActiveRecord::Schema.define(:version => 15) 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
@@ -107,6 +117,10 @@ ActiveRecord::Schema.define(:version => 15) 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
@@ -115,6 +129,8 @@ ActiveRecord::Schema.define(:version => 15) 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"
@@ -125,6 +141,8 @@ ActiveRecord::Schema.define(:version => 15) do
t.datetime "updated_at"
end
+ add_index "user_stories", ["product_id"], :name => "fk_user_story_product"
+
create_table "users", :force => true do |t|
t.string "email", :limit => 128, :null => false
t.string "display_name", :limit => 128, :null => false
diff --git a/test/fixtures/user_verifications.yml b/test/fixtures/user_verifications.yml
new file mode 100644
index 0000000..20a7c19
--- /dev/null
+++ b/test/fixtures/user_verifications.yml
@@ -0,0 +1,6 @@
+# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
+
+unverified_user_verification:
+ user_id: <%= Fixtures.identify(:unverified_user) %>
+ token: <%= UserVerification.create_token %>
+ sent: <%= Date.today %>
diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml
index 6c096e5..590d61b 100644
--- a/test/fixtures/users.yml
+++ b/test/fixtures/users.yml
@@ -33,4 +33,10 @@ user_admin:
email: user_admin(a)product.com
display_name: User admin
salt: <%= SALT %>
- hashed_password: <%= User.encrypted_password('fungi', SALT) %>
\ No newline at end of file
+ hashed_password: <%= User.encrypted_password('fungi', SALT) %>
+
+unverified_user:
+ email: unverified(a)user.com
+ display_name: Unverified user
+ salt: <%= SALT %>
+ hashed_password: <%= User.encrypted_password('unverified', SALT) %>
diff --git a/test/functional/home_controller_test.rb b/test/functional/home_controller_test.rb
index b8aaf02..ab8739f 100644
--- a/test/functional/home_controller_test.rb
+++ b/test/functional/home_controller_test.rb
@@ -24,6 +24,11 @@ class HomeControllerTest < ActionController::TestCase
@controller = HomeController.new
@authenticated_user = users(:mcpierce)
@backlog = BacklogItem.find_all_by_state_and_owner_id(BacklogItem::STATE_ASSIGNED,@authenticated_user.id)
+
+ # setup for testing mail
+ ActionMailer::Base.delivery_method = :test
+ ActionMailer::Base.perform_deliveries = true
+ ActionMailer::Base.deliveries = []
end
# Ensures that anonymous users are sent to the login page when they try to
@@ -78,34 +83,6 @@ class HomeControllerTest < ActionController::TestCase
assert_nil session[:user_id], 'User id was not deleted.'
end
- # Ensures that a registration attempt rejects a logged in user.
- #
- def test_register_for_authenticated_user
- get :register, nil, {:user_id => @authenticated_user.id}
-
- assert_redirected_to :action => :dashboard
- end
-
- # Ensures that registering a new account works as expected.
- #
- def test_register_create_account
- post :register, {:email => 'test(a)user.com',
- :display_name => 'Test User',
- :password => 'farkle',
- :password_confirmation => 'farkle' }
-
- assert_redirected_to :action => :login
- assert User.find_by_email('test(a)user.com'), 'User not created'
- end
-
- # Ensure that the registration page works as expected.
- #
- def test_register
- get :register
-
- assert_response :success
- end
-
# Ensures that the main page doesn't require the user be logged.
#
def test_index
diff --git a/test/functional/user_controller_test.rb b/test/functional/user_controller_test.rb
index 584d52b..9812f04 100644
--- a/test/functional/user_controller_test.rb
+++ b/test/functional/user_controller_test.rb
@@ -1,137 +1,202 @@
# user_controller_test.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/>.
-#
+#
require File.dirname(__FILE__) + '/../test_helper'
class UserControllerTest < ActionController::TestCase
fixtures :backlog_items
+ fixtures :user_verifications
fixtures :users
-
+
def setup
@user = users(:mcpierce)
@other_user = users(:jdonuts)
@admin = users(:user_admin)
- @backlog_items = BacklogItem.find_all_by_owner_id((a)user.id)
+ @backlog_items = BacklogItem.find_all_by_owner_id((a)user.id)
+
+ @verified_user = users(:mcpierce)
+ raise "User should be verified!" unless @verified_user.verified?
+
+ @unverified_user = users(:unverified_user)
end
-
+
# Ensures that viewing fails when the user id is missing or invalid.
- #
+ #
def test_view_with_invalid_id
get :view
-
+
assert_redirected_to error_path
-
+
get :view, {:id => 9999}
-
+
assert_redirected_to error_path
end
-
+
# Ensures that viewing a user works as expected.
- #
+ #
def test_view
get :view, {:id => users(:mcpierce).id}
-
+
assert_response :success
assert assigns['this_user'], 'Failed to load the user.'
assert assigns['my_backlog'], 'Failed to load the user\'s backlog.'
- assert_equal @backlog_items.size, assigns['my_backlog'].size,
+ assert_equal @backlog_items.size, assigns['my_backlog'].size,
'Failed to load the right set of items.'
end
-
+
# Ensures that viewing a user backlog requires a user id.
- #
+ #
def test_backlog_without_id
get :backlog
-
+
assert_redirected_to error_path
end
-
+
# Ensures that the right backlog is loaded.
- #
+ #
def test_backlog
get :view, {:id => @user.id}
-
+
assert_response :success
assert_equal @backlog_items.size, assigns['my_backlog'].size,
'Did not load the right backlog items.'
end
-
+
# Ensures that editing requires a valid user id.
- #
+ #
def test_modify_with_invalid_id
get :modify,
{:id => 9999}
-
+
assert_redirected_to error_path
end
-
+
# Ensures that anonymous users cannot modify a user.
- #
+ #
def test_modify_as_anonymous
get :modify,
{:id => @user.id}
-
+
assert_redirected_to :controller => :home, :action => :login
end
-
+
# Ensures that other users cannot modify someone's account.
- #
+ #
def test_modify_as_someone_else
get :modify,
{:id => @user.id},
{:user_id => @other_user.id}
-
+
assert_redirected_to error_path
end
-
+
# Ensures that an admin can edit a user's account.
- #
+ #
def test_modify_as_admin
get :modify,
{:id => @user.id},
{:user_id => @admin.id}
-
+
assert_response :success
end
-
+
# Ensures that a user can edit himself.
- #
+ #
def test_modify
get :modify,
{:id => @user.id},
{:user_id => @user.id}
-
+
assert_response :success
end
-
+
# Ensures that saving the updated user details works.
#
def test_modify
email = 'farkle(a)projxp.com'
-
+
post :modify,
{:id => @user.id,
:user => {:email => email}},
{:user_id => @user.id}
-
+
assert_redirected_to :action => :view, :id => @user.id
-
+
result = User.find_by_email(email)
-
+
assert result, 'User was not updated.'
end
+
+ # Ensures that a user id must be present when verifying a user.
+ #
+ def test_verify_without_user_id
+ get :verify
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that the user id must be valid.
+ #
+ def test_verify_with_invalid_user_id
+ get :verify, {:id => 9999}
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that a user who has already been verified sees an error message.
+ #
+ def test_verify_for_verified_user
+ get :verify,
+ {:id => @verified_user.id, :token => UserVerification.create_token}
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that a token is required.
+ #
+ def test_verify_without_token
+ get :verify, {:id => @unverified_user.id}
+
+ assert_template "verify"
+ end
+
+ # Ensures that an invalid token causes verification to fail.
+ #
+ def test_verify_with_invalid_token
+ get :verify,
+ {
+ :id => @unverified_user.id,
+ :token => UserVerification.create_token
+ }
+
+ assert_template "verify"
+ end
+
+ # Ensures that verification works as expected.
+ #
+ def test_verify
+ get :verify,
+ {
+ :id => @unverified_user.id,
+ :token => @unverified_user.verification.token
+ }
+
+ assert_redirected_to :controller => :home, :action => :login
+ result = User.find_by_id((a)unverified_user.id)
+ assert result.verified?, 'Verification failed to mark the user as verified.'
+ end
end
diff --git a/test/unit/user_test.rb b/test/unit/user_test.rb
index 2b12f48..9955734 100644
--- a/test/unit/user_test.rb
+++ b/test/unit/user_test.rb
@@ -18,15 +18,39 @@
require File.dirname(__FILE__) + '/../test_helper'
class UserTest < ActiveSupport::TestCase
+ fixtures :users
def setup
@mcpierce = users(:mcpierce)
+
+ @verified_user = @mcpierce
+ raise "User should be verified!" unless @verified_user.verified?
+ @unverified_user = users(:unverified_user)
end
# Ensures that authenticating a user using the wrong password fails.
#
- def authenticate_with_wrong_password
+ def test_authenticate_with_wrong_password
result = User.authenticate((a)mcpierce.email, 'klefar')
assert_nil result
end
+
+ # Ensures that trying to verify a token for a user with no pending verification
+ # raises an exception.
+ #
+ def test_verify_for_verified_user
+ assert_raise(Exception) { @verified_user.verify(UserVerification.create_token) }
+ end
+
+ # Ensures that giving the wrong token does not verified the user.
+ #
+ def test_verify_with_wrong_token
+ flunk "User should not have verified the token." if @unverified_user.verify(UserVerification.create_token)
+ end
+
+ # Ensures that an unverified user is marke as unverified.
+ #
+ def test_verified_with_unverified_user
+ flunk "User is not verified." if @unverified_user.verified?
+ end
end
diff --git a/test/unit/user_verification_test.rb b/test/unit/user_verification_test.rb
new file mode 100644
index 0000000..0171729
--- /dev/null
+++ b/test/unit/user_verification_test.rb
@@ -0,0 +1,77 @@
+# user_verification_test.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/>.
+#
+
+require File.dirname(__FILE__) + '/../test_helper'
+
+class UserVerificationTest < ActiveSupport::TestCase
+
+ def setup
+ @verification = UserVerification.new(
+ :user_id => 1,
+ :token => UserVerification.create_token,
+ :sent => Date.today)
+ end
+
+ # Ensures that a verification requires an associated user.
+ #
+ def test_valid_fails_without_user_id
+ @verification.user_id = nil
+
+ flunk "A user must be specified." if @verification.valid?
+ end
+
+ # Ensures that a verification token is required.
+ #
+ def test_valid_fails_without_token
+ @verification.token = nil
+
+ flunk "A token must be specified." if @verification.valid?
+ end
+
+ # Ensures that a verification token must be 16 alpha-numeric characters.
+ #
+ def test_valid_fails_with_invalid_token
+ @verification.token = "this is invalid!"
+
+ flunk "Tokens must be 16 alpha-numeric characters only." if @verification.valid?
+ end
+
+ # Ensures that a sent date has to be set.
+ #
+ def test_valid_fails_without_sent_date
+ @verification.sent = nil
+
+ flunk "Verification must have a sent date." if @verification.valid?
+ end
+
+ # Ensures taht a well-formed verification is considered valid.
+ #
+ def test_valid
+ flunk "There is a problem in validation." unless @verification.valid?
+ end
+
+ # Ensures that the token generator always creates a 16-character alphanumeric
+ # token.
+ #
+ def test_create_token
+ (1..100).each do |num|
+ token = UserVerification.create_token
+
+ flunk "The token generator is broken." unless token =~ /#{UserVerification::TOKEN_PATTERN}/
+ end
+ end
+end
--
1.6.0.2
15 years, 5 months
[PROJXP Still can't get emails to go...
by Darryl L. Pierce
I have the following as my config/mailer.yml file and I'm still having
problems sending from my development environment. In diagnosing it, I
get the exception "wrong number of arguments (3 of 2)" from
ActionMailer.
---
:require_smtp_tls: true
:account:
:address: smtp.google.com
:port: 587
:user_name: mcpierce(a)gmail.com
:password: xxxxx
:authentication: :login
:from: mcpierce(a)gmail.com
--
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, 5 months
[PROJXP [PATCH] Users can view a burndown chart for a sprint. #7
by Darryl L. Pierce
Added a mapping for burndown charts. URLs will be in the format:
/report/burndown/[id]
where id is the sprint's id.
The day argument is an optional parameter. If it is not included
then the current status of the sprint is reported.
This patch requires you to run a migration, and the gruff gem to be
installed.
Signed-off-by: Darryl L. Pierce <mcpierce(a)gmail.com>
---
app/controllers/backlog_controller.rb | 7 ++-
app/controllers/report_controller.rb | 56 ++++++++++++++++++++++
app/controllers/task_controller.rb | 1 -
app/controllers/user_story_controller.rb | 1 -
app/helpers/report_helper.rb | 2 +
app/models/backlog_item.rb | 18 ++++++-
app/models/sprint.rb | 22 +++++++++
app/views/report/burndown.html.erb | 3 +
app/views/sprint/view.html.erb | 33 +++++++------
config/environment.rb | 54 +++------------------
config/routes.rb | 1 -
db/migrate/015_add_explicit_date_to_estimates.rb | 16 ++++++
db/schema.rb | 3 +-
test/fixtures/remaining_hours_estimates.yml | 2 +
test/functional/backlog_controller_test.rb | 15 +++++-
test/functional/report_controller_test.rb | 52 ++++++++++++++++++++
test/unit/backlog_item_test.rb | 2 +-
test/unit/sprint_test.rb | 4 +-
18 files changed, 217 insertions(+), 75 deletions(-)
create mode 100644 app/controllers/report_controller.rb
create mode 100644 app/helpers/report_helper.rb
create mode 100644 app/views/report/burndown.html.erb
create mode 100644 db/migrate/015_add_explicit_date_to_estimates.rb
create mode 100644 test/functional/report_controller_test.rb
diff --git a/app/controllers/backlog_controller.rb b/app/controllers/backlog_controller.rb
index 9e3ea1d..e033149 100644
--- a/app/controllers/backlog_controller.rb
+++ b/app/controllers/backlog_controller.rb
@@ -67,7 +67,8 @@ class BacklogController < ApplicationController
@backlog_item.remaining_hours_estimates << RemainingHoursEstimate.create(
:backlog_item_id => @backlog_item.id,
:user_id => @user.id,
- :hours => params[:hours])
+ :hours => params[:hours],
+ :estimated_on => Date.today)
end
rescue Exception => error
@@ -147,6 +148,7 @@ class BacklogController < ApplicationController
begin
BacklogItem.transaction do
@backlog_item.state = BacklogItem::STATE_COMPLETED
+ @backlog_item.update_remaining_hours(0.0, @user)
@backlog_item.save
end
@@ -170,6 +172,7 @@ class BacklogController < ApplicationController
BacklogItem.transaction do
@backlog_item.state = BacklogItem::STATE_CANCELED
@backlog_item.owner = nil
+ @backlog_item.update_remaining_hours(0.0, @user)
@backlog_item.save!
end
@@ -188,7 +191,7 @@ class BacklogController < ApplicationController
def reopen
if @backlog_item.user_can_reopen?(@user)
BacklogItem.transaction do
- @backlog_item.state = @backlog_item.owner ? BacklogItem::STATE_ASSIGNED : BacklogItem::PENDING
+ @backlog_item.state = @backlog_item.owner ? BacklogItem::STATE_ASSIGNED : BacklogItem::STATE_PENDING
@backlog_item.save!
redirect_to :action => :view, :id => @backlog_item.id
diff --git a/app/controllers/report_controller.rb b/app/controllers/report_controller.rb
new file mode 100644
index 0000000..c3ca4a1
--- /dev/null
+++ b/app/controllers/report_controller.rb
@@ -0,0 +1,56 @@
+# report_controller.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/>.
+#
+
+# +ReportController+ allows users to view various reports.
+#
+class ReportController < ApplicationController
+ before_filter :load_sprint
+
+ # Generates a burndown report.
+ #
+ def burndown_graphic
+ if @sprint.can_view_burndown?
+ @data = @sprint.burndown_data
+
+ g = Gruff::Line.new('800x600')
+ g.title = "Burndown Chart For '#{(a)sprint.title}'"
+
+ g.font = File.expand_path('Courier.ttf', RAILS_ROOT)
+ g.data("Remaining hours", @data)
+ g.labels= {
+ 0 => @sprint.start.to_s(:date),
+ (@sprint.duration / 2) => (@sprint.start + (@sprint.duration / 2)).to_s(:date),
+ (@sprint.duration - 1) => (@sprint.start + (@sprint.duration - 1)).to_s(:date)}
+
+ send_data(g.to_blob, :disposition => 'inline', :type => 'image/png', :filename => "site-stats.png")
+ else
+ flash[:error] = 'Cannot view a burndown chart for this sprint.'
+ redirect_to :controller => :sprint, :action => :view, :id => @sprint.id
+ end
+ end
+
+ private
+
+ def load_sprint
+ @sprint = Sprint.find_by_id(params[:id])
+
+ unless @sprint
+ flash[:error] = 'Missing or invalid sprint id.'
+ redirect_to error_url
+ end
+ end
+end
diff --git a/app/controllers/task_controller.rb b/app/controllers/task_controller.rb
index 0692e6f..6c6e42b 100644
--- a/app/controllers/task_controller.rb
+++ b/app/controllers/task_controller.rb
@@ -42,7 +42,6 @@ class TaskController < ApplicationController
rescue ActiveRecord::RecordInvalid => error
flash[:message] = error.message
- puts ":::#{error.message}"
@task.valid?
render :action => :create
end
diff --git a/app/controllers/user_story_controller.rb b/app/controllers/user_story_controller.rb
index 9f5633d..83e567c 100644
--- a/app/controllers/user_story_controller.rb
+++ b/app/controllers/user_story_controller.rb
@@ -69,7 +69,6 @@ class UserStoryController < ApplicationController
end
rescue ActiveRecord::RecordInvalid => error
- puts ":::ERROR:#{error.message}"
@user_story.valid?
render :modify
end
diff --git a/app/helpers/report_helper.rb b/app/helpers/report_helper.rb
new file mode 100644
index 0000000..0847d3b
--- /dev/null
+++ b/app/helpers/report_helper.rb
@@ -0,0 +1,2 @@
+module ReportHelper
+end
diff --git a/app/models/backlog_item.rb b/app/models/backlog_item.rb
index 0eea0b3..55024c4 100644
--- a/app/models/backlog_item.rb
+++ b/app/models/backlog_item.rb
@@ -66,8 +66,22 @@ class BacklogItem < ActiveRecord::Base
remaining_hours_estimates.last.hours
end
- def remaining_hours=(hours)
- remaining_hours_estimates << RemainingHoursEstimate.new(:hours => hours)
+ def remaining_hours_for_date(date)
+ result = estimated_hours
+
+ remaining_hours_estimates.each do |estimate|
+ result = estimate.hours.to_f if estimate.estimated_on <= date
+ end
+
+ return result
+ end
+
+ def update_remaining_hours(hours, user)
+ remaining_hours_estimates <<
+ RemainingHoursEstimate.new(
+ :hours => hours,
+ :user_id => user.id,
+ :estimated_on => Date.today)
end
# Returns whether the given user can modify this backlog item.
diff --git a/app/models/sprint.rb b/app/models/sprint.rb
index 157396c..bf4a4ee 100644
--- a/app/models/sprint.rb
+++ b/app/models/sprint.rb
@@ -100,6 +100,12 @@ class Sprint < ActiveRecord::Base
backlog_items.inject(0) { |sum, item| sum + item.remaining_hours}
end
+ # Returns the number of hours remaining for the specified day.
+ #
+ def remaining_hours_for_day(day)
+ backlog_items.inject(0) { |sum, item| sum + item.remaining_hours_for_date(start + day) }
+ end
+
# Returns whether the specified user can modify this sprint.
#
def user_can_modify(user)
@@ -118,6 +124,22 @@ class Sprint < ActiveRecord::Base
status == STATUS_ACTIVE
end
+ # Returns whether a burndown chart can be viewed for this sprint.
+ #
+ def can_view_burndown?
+ status != STATUS_PLANNED
+ end
+
+ # Returns the data as of the given day into the sprint.
+ #
+ def burndown_data
+ data = []
+
+ (0..duration).each { |day| data[day] = remaining_hours_for_day(day) }
+
+ return data
+ end
+
# Returns whether the sprint is in a healthy state or not.
#
def healthy?
diff --git a/app/views/report/burndown.html.erb b/app/views/report/burndown.html.erb
new file mode 100644
index 0000000..b5fa23f
--- /dev/null
+++ b/app/views/report/burndown.html.erb
@@ -0,0 +1,3 @@
+<img src="<%= url_for :action => :burndown_graphic, :id => @sprint.id %>"
+ alt="Burndown"
+ align="center" />
\ No newline at end of file
diff --git a/app/views/sprint/view.html.erb b/app/views/sprint/view.html.erb
index cfa0a58..c6a1659 100644
--- a/app/views/sprint/view.html.erb
+++ b/app/views/sprint/view.html.erb
@@ -1,10 +1,13 @@
<div style="width: 100%; overflow: auto">
<div style="float: left; width: 33%;">
<div class="toolbar">
-
+
<%= link_to "Edit", :action => :modify, :id => @sprint %>
<%= link_to "Populate", :action => :populate, :id => @sprint %>
-
+ <% if @sprint.can_view_burndown? %>
+ <%= link_to "Burndown", :controller => :report, :action => :burndown, :id => @sprint.id %>
+ <% end %>
+
</div>
<table class="details">
<thead>
@@ -12,14 +15,14 @@
<th class="title" colspan="2"><%= @sprint.title %></th>
</tr>
</thead>
-
+
<tbody>
<tr>
<td class="label">Status:</td>
- <td class="value">
+ <td class="value">
<% if @sprint.user_can_modify(@user) %>
<% form_tag(:action => :status, :id => @sprint.id) do %>
- <%= select_tag "status",
+ <%= select_tag "status",
options_for_select(Sprint::STATUS_TEXT, @sprint.status) %>
<%= submit_tag "Apply" %>
<% end %>
@@ -28,36 +31,36 @@
<% end %>
</td>
</tr>
-
+
<tr>
<td class="label">Starts:</td>
<td class="value"><%= @sprint.start.to_s(:date) %></td>
</tr>
-
+
<tr>
<td class="label">Ends:</td>
<td class="value"><%= @sprint.end_date.to_s(:date) %>
</tr>
-
+
<tr>
<td class="label">Hours E/A/R:</td>
<td class="value"><%= "#{@sprint.estimated_hours}/#{@sprint.actual_hours}/#{(a)sprint.remaining_hours}" %></td>
</tr>
-
+
<tr>
<td class="text" colspan="2"><%= simple_format @sprint.goals %></td>
</tr>
</tbody>
-
+
</table>
</div>
-
+
<div style="width: 66%; float:left;">
-
+
<%= will_paginate @backlog_items %>
-
-
- <%= render :partial => 'backlog/list',
+
+
+ <%= render :partial => 'backlog/list',
:object => @backlog_items.sort {|item1, item2| item1.user_story.priority <=> item2.user_story.priority},
:locals => {:title => 'Sprint Backlog'}%>
</div>
diff --git a/config/environment.rb b/config/environment.rb
index e6685d5..230170b 100644
--- a/config/environment.rb
+++ b/config/environment.rb
@@ -1,25 +1,25 @@
# environment.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/>.
-#
+#
# Be sure to restart your server when you modify this file
# Uncomment below to force Rails into production mode when you don't control
-# web/app server and can't set it the proper way ENV['RAILS_ENV'] ||=
-# 'production'
+# web/app server and can't set it the proper way
+# ENV['RAILS_ENV'] ||= 'production'
# Specifies gem version of Rails to use when vendor/rails is not present
# #RAILS_GEM_VERSION = '2.1.0' unless defined? RAILS_GEM_VERSION
@@ -28,53 +28,13 @@
require File.join(File.dirname(__FILE__), 'boot')
Rails::Initializer.run do |config|
- # Settings in config/environments/* take precedence over those specified here.
- # Application configuration should go into files in config/initializers -- all
- # .rb files in that directory are automatically loaded. See
- # Rails::Configuration for more options.
-
- # Skip frameworks you're not going to use (only works if using vendor/rails).
- # To use Rails without a database, you must remove the Active Record framework
- # config.frameworks -= [ :active_record, :active_resource, :action_mailer ]
-
- # Only load the plugins named here, in the order given. By default, all
- # plugins in vendor/plugins are loaded in alphabetical order. :all can be used
- # as a placeholder for all plugins not explicitly named config.plugins = [
- # :exception_notification, :ssl_requirement, :all ]
-
- # Add additional load paths for your own custom dirs config.load_paths += %W(
- # #{RAILS_ROOT}/extras )
-
- # Force all environments to use the same logger level (by default production
- # uses :info, the others :debug) config.log_level = :debug
-
- # Your secret key for verifying cookie session data integrity. If you change
- # this key, all old sessions will become invalid! Make sure the secret is at
- # least 30 characters and all random, no regular words or you'll be exposed to
- # dictionary attacks.
config.action_controller.session = {
:session_key => '_chiroxp_session',
:secret => 'bb5e22a8065951948272bccbeff6c1e1fa1eff29e85dabcefe7577b68795d9e42cfc9a18426f84aee6722bb4859bf8b144a98fa58891e6c1f215dc3b6f38d2b3'
}
- # Use the database for sessions instead of the cookie-based default, which
- # shouldn't be used to store highly confidential information (create the
- # session table with 'rake db:sessions:create')
- # config.action_controller.session_store = :active_record_store
-
- # Use SQL instead of Active Record's schema dumper when creating the test
- # database. This is necessary if your schema can't be completely dumped by the
- # schema dumper, like if you have constraints or database-specific column
- # types config.active_record.schema_format = :sql
-
- # Activate observers that should always be running
- # config.active_record.observers = :cacher, :garbage_collector
-
- # Make Active Record use UTC-base instead of local time
- # config.active_record.default_timezone = :utc
-
- # Gems
config.gem 'openwferu-scheduler', :lib => 'openwfe/util/scheduler'
+ config.gem "gruff"
end
ActiveSupport::CoreExtensions::Time::Conversions::DATE_FORMATS.merge!(
diff --git a/config/routes.rb b/config/routes.rb
index e1f81cc..1108059 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -23,7 +23,6 @@ ActionController::Routing::Routes.draw do |map|
# Error handling
map.error "/error", :controller => 'home', :action => 'error'
-
# Install the default routes as the lowest priority.
map.connect ':controller/:action/:id'
map.connect ':controller/:action/:id.:format'
diff --git a/db/migrate/015_add_explicit_date_to_estimates.rb b/db/migrate/015_add_explicit_date_to_estimates.rb
new file mode 100644
index 0000000..f300931
--- /dev/null
+++ b/db/migrate/015_add_explicit_date_to_estimates.rb
@@ -0,0 +1,16 @@
+class AddExplicitDateToEstimates < ActiveRecord::Migration
+ def self.up
+ add_column :remaining_hours_estimates, :estimated_on, :datetime
+
+ RemainingHoursEstimate.find(:all).each do |estimate|
+ estimate.estimated_on = estimate.created_at
+ estimate.save!
+ end
+
+ change_column :remaining_hours_estimates, :estimated_on, :datetime, :null => false
+ end
+
+ def self.down
+ remove_column :remaining_hours_estimates, :estimated_on
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index c773644..4a20449 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 => 14) do
+ActiveRecord::Schema.define(:version => 15) do
create_table "backlog_items", :force => true do |t|
t.integer "sprint_id", :null => false
@@ -62,6 +62,7 @@ ActiveRecord::Schema.define(:version => 14) do
t.decimal "hours", :precision => 5, :scale => 2, :null => false
t.datetime "created_at"
t.datetime "updated_at"
+ t.datetime "estimated_on", :null => false
end
create_table "roles", :force => true do |t|
diff --git a/test/fixtures/remaining_hours_estimates.yml b/test/fixtures/remaining_hours_estimates.yml
index 6e2e09b..b5ecd13 100644
--- a/test/fixtures/remaining_hours_estimates.yml
+++ b/test/fixtures/remaining_hours_estimates.yml
@@ -2,8 +2,10 @@ owned_backlog_item_remaining_one:
backlog_item_id: <%= Fixtures.identify(:owned_backlog_item) %>
user_id: <%= Fixtures.identify(:mcpierce) %>
hours: 2.5
+ estimated_on: <%= Date.today.to_s(:db) %>
owned_backlog_item_remaining_two:
backlog_item_id: <%= Fixtures.identify(:owned_backlog_item) %>
user_id: <%= Fixtures.identify(:mcpierce) %>
hours: 4.5
+ estimated_on: <%= Date.today.next.to_s(:db) %>
diff --git a/test/functional/backlog_controller_test.rb b/test/functional/backlog_controller_test.rb
index 4657b2d..cb2b041 100644
--- a/test/functional/backlog_controller_test.rb
+++ b/test/functional/backlog_controller_test.rb
@@ -38,8 +38,10 @@ class BacklogControllerTest < ActionController::TestCase
@completed_backlog_item = backlog_items(:closed_backlog_item)
flunk "Closed item should have a closed state!" unless @completed_backlog_item.state == BacklogItem::STATE_COMPLETED
+
@open_backlog_item = @owned_backlog_item
flunk "Open item should have an open state!" unless @open_backlog_item.state == BacklogItem::STATE_ASSIGNED
+
@task = tasks(:created_login_controller)
@sprint = @owned_backlog_item.sprint
end
@@ -282,6 +284,7 @@ class BacklogControllerTest < ActionController::TestCase
# Ensures that remaining hours must be a non-zero value.
#
def test_remaining_with_nonnumeric_hours
+ old_hours = @owned_backlog_item.remaining_hours.to_f
post :remaining,
{
:id => @owned_backlog_item.id,
@@ -290,7 +293,9 @@ class BacklogControllerTest < ActionController::TestCase
{:user_id => @owned_backlog_item.owner_id}
assert_redirected_to :action => :view, :id => @owned_backlog_item.id
- assert flash.has_key?(:message)
+ result = BacklogItem.find_by_id((a)owned_backlog_item.id)
+ assert_equal old_hours, result.remaining_hours.to_f,
+ "Remaining hours should not have changed."
end
# Ensures that a negative remaining hours estimate is rejected.
@@ -310,6 +315,8 @@ class BacklogControllerTest < ActionController::TestCase
# Ensures that a zero hours remaining estimate is rejected.
#
def test_remaining_with_zero_hours
+ old_hours = @owned_backlog_item.remaining_hours
+
post :remaining,
{
:id => @owned_backlog_item.id,
@@ -318,7 +325,9 @@ class BacklogControllerTest < ActionController::TestCase
{:user_id => @owned_backlog_item.owner_id}
assert_redirected_to :action => :view, :id => @owned_backlog_item.id
- assert flash.has_key?(:message)
+ result = BacklogItem.find_by_id((a)owned_backlog_item.id)
+ assert_equal old_hours.to_f, result.remaining_hours.to_f,
+ 'Remaining hours should not have changed.'
end
# Ensures that someone who is not the backlog item owner can't update the
@@ -354,7 +363,7 @@ class BacklogControllerTest < ActionController::TestCase
assert_redirected_to :action => :view, :id => @owned_backlog_item.id
result = BacklogItem.find_by_id((a)owned_backlog_item.id)
- assert_equal 2.5, result.remaining_hours, 'Remaining hours not updated correctly.'
+ assert_equal 2.5, result.remaining_hours.to_f, 'Remaining hours not updated correctly.'
end
# Ensures that anonymous users cannot reopen a closed task.
diff --git a/test/functional/report_controller_test.rb b/test/functional/report_controller_test.rb
new file mode 100644
index 0000000..6957fe5
--- /dev/null
+++ b/test/functional/report_controller_test.rb
@@ -0,0 +1,52 @@
+# report_controller_test.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/>.
+#
+
+require File.dirname(__FILE__) + '/../test_helper'
+
+class ReportControllerTest < ActionController::TestCase
+ fixtures :sprints
+
+ def setup
+ @pending_sprint = sprints(:inactive_sprint)
+ @sprint = sprints(:initial_development_sprint)
+ end
+
+ # Ensures that an error occurs when no sprint id was provided.
+ #
+ def test_burndown_graphic_without_sprint_id
+ get :burndown_graphics
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that trying to print a burndown report for a pending sprint fails.
+ #
+ def test_burndown_graphic_fails_for_pending_sprint
+ get :burndown_graphic, {:id => @pending_sprint.id}
+
+ assert_redirected_to :controller => :sprint, :action => :view, :id => @pending_sprint.id
+ end
+
+ # Ensures that a burndown chart works as expected.
+ #
+ def test_burndown_graphic
+ get :burndown_graphic, {:id => @sprint.id}
+
+ assert_response :success
+ assert assigns['data'], "Didn't generate the data needed for a report."
+ end
+end
diff --git a/test/unit/backlog_item_test.rb b/test/unit/backlog_item_test.rb
index fd24efa..c3cbc1b 100644
--- a/test/unit/backlog_item_test.rb
+++ b/test/unit/backlog_item_test.rb
@@ -67,7 +67,7 @@ class BacklogItemTest < ActiveSupport::TestCase
#
def test_remaining_hours_when_updated
@item.estimated_hours = 5.0
- @item.remaining_hours_estimates << RemainingHoursEstimate.new(:hours => 2.5)
+ @item.remaining_hours_estimates << RemainingHoursEstimate.new(:hours => 2.5, :estimated_on => Date.today.next)
assert_equal 2.5,
@item.remaining_hours,
diff --git a/test/unit/sprint_test.rb b/test/unit/sprint_test.rb
index 2e1c0c0..9e0d827 100644
--- a/test/unit/sprint_test.rb
+++ b/test/unit/sprint_test.rb
@@ -20,6 +20,7 @@ require File.dirname(__FILE__) + '/../test_helper'
class SprintTest < ActiveSupport::TestCase
fixtures :sprints
fixtures :products
+ fixtures :users
def setup
@sprint = Sprint.new(
@@ -28,10 +29,11 @@ class SprintTest < ActiveSupport::TestCase
:start => Date.today - 7,
:duration => 14,
:goals => "Things I'd like to accomplish.")
+ @developer = users(:mcpierce)
backlog_item = BacklogItem.new(:estimated_hours => 100)
backlog_item.tasks << Task.new(:hours => 50)
- backlog_item.remaining_hours = 50.0
+ backlog_item.update_remaining_hours(50.0,@developer)
@healthy_backlog = [ backlog_item ]
backlog_item = BacklogItem.new(:estimated_hours => 100)
--
1.6.0.2
15 years, 5 months
[PROJXP [PATCH] Emails are sent when no activity is recorded. #10
by Benjamin LAN-SUN-LUK
From: Benjamin LAN-SUN-LUK <benji(a)Wloups.lan>
This patch supercedes the last one.
Required:
- Install gems, use: "rake gems:install"
- Configure mail, create a file at "config/mailer.yml". Use example at "config/mailer.yml.example"
If you want to use sendmail for the server. You have just to set "ActionMailer::Base.delivery_method = :sendmail" in "config/initializers/mailer.rb".
Signed-off-by: Benjamin LAN-SUN-LUK <benjamin.lan-sun-luk(a)supinfo.com>
---
app/models/user_mailer.rb | 22 ++++++++-
.../user_mailer/no_activity_recorded.html.erb | 6 ++
app/views/user_mailer/no_task_recorded.html.erb | 6 ++
config/environment.rb | 12 +---
config/initializers/mailer.rb | 20 +++++++
config/initializers/schedules.rb | 54 ++++++++++++++++++++
config/mailer.yml | 6 --
config/mailer.yml.example | 9 +++
test/unit/user_mailer_test.rb | 21 +++++++-
9 files changed, 137 insertions(+), 19 deletions(-)
create mode 100644 app/views/user_mailer/no_activity_recorded.html.erb
create mode 100644 app/views/user_mailer/no_task_recorded.html.erb
create mode 100644 config/initializers/mailer.rb
create mode 100644 config/initializers/schedules.rb
delete mode 100644 config/mailer.yml
create mode 100644 config/mailer.yml.example
diff --git a/app/models/user_mailer.rb b/app/models/user_mailer.rb
index 73308af..bd42800 100644
--- a/app/models/user_mailer.rb
+++ b/app/models/user_mailer.rb
@@ -3,8 +3,28 @@ class UserMailer < ActionMailer::Base
#
def new_generated_password(user, new_password)
recipients user.email
- from "no_reply(a)projxp.org" # TODO Email must be edit
+ from MAIL_CONFIG[:from]
subject "Your password"
body :user => user, :new_password => new_password
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)
+ recipients user.email
+ from MAIL_CONFIG[:from]
+ subject "No activity has been detected"
+ body :user => user, :backlog_items => backlog_items
+ end
+
+ # Send an email to a user to notify him that no task has been detected
+ # in his backlog.
+ #
+ def no_task_recorded(user, backlog_items)
+ recipients user.email
+ from MAIL_CONFIG[:from]
+ subject "No task has been detected"
+ body :user => user, :backlog_items => backlog_items
+ end
end
diff --git a/app/views/user_mailer/no_activity_recorded.html.erb b/app/views/user_mailer/no_activity_recorded.html.erb
new file mode 100644
index 0000000..8f7a005
--- /dev/null
+++ b/app/views/user_mailer/no_activity_recorded.html.erb
@@ -0,0 +1,6 @@
+Dear <%= @user.display_name %>,
+
+The following backlog items are active, but no tasks were entered against them. Please take a moment to update them.
+<% @backlog_items.each do |backlog_item|%>
+<%= backlog_item.user_story.title %>
+<% end %>
diff --git a/app/views/user_mailer/no_task_recorded.html.erb b/app/views/user_mailer/no_task_recorded.html.erb
new file mode 100644
index 0000000..a912ec1
--- /dev/null
+++ b/app/views/user_mailer/no_task_recorded.html.erb
@@ -0,0 +1,6 @@
+Dear <%= @user.display_name %>,
+
+The following backlog items are active, but tasks are empty. Please take a moment to update them.
+<% @backlog_items.each do |backlog_item|%>
+<%= backlog_item.user_story.title %>
+<% end %>
diff --git a/config/environment.rb b/config/environment.rb
index af8a593..e6685d5 100644
--- a/config/environment.rb
+++ b/config/environment.rb
@@ -72,6 +72,9 @@ Rails::Initializer.run do |config|
# Make Active Record use UTC-base instead of local time
# config.active_record.default_timezone = :utc
+
+ # Gems
+ config.gem 'openwferu-scheduler', :lib => 'openwfe/util/scheduler'
end
ActiveSupport::CoreExtensions::Time::Conversions::DATE_FORMATS.merge!(
@@ -79,12 +82,3 @@ ActiveSupport::CoreExtensions::Time::Conversions::DATE_FORMATS.merge!(
:date_time12 => "%m/%d/%Y %I:%M%p",
:date_time24 => "%m/%d/%Y %H:%M"
)
-
-# Mail configuration.
-# Please complete the config file are locate at /config/mailer.yml
-#
-require "smtp_tls"
-
-mailer_config = File.open("#{RAILS_ROOT}/config/mailer.yml")
-mailer_options = YAML.load(mailer_config)
-ActionMailer::Base.smtp_settings = mailer_options
diff --git a/config/initializers/mailer.rb b/config/initializers/mailer.rb
new file mode 100644
index 0000000..f47c5cb
--- /dev/null
+++ b/config/initializers/mailer.rb
@@ -0,0 +1,20 @@
+# Mail configuration.
+# Please complete the config file that are locate at /config/mailer.yml
+#
+mailer_file_path = "#{RAILS_ROOT}/config/mailer.yml"
+
+# Defines a delivery method. Possible values are :smtp (default), :sendmail,
+# and :test.
+ActionMailer::Base.delivery_method = :smtp
+
+if File.exist?(mailer_file_path)
+
+ MAIL_CONFIG = YAML.load(File.open(mailer_file_path))
+ require "smtp_tls" if MAIL_CONFIG[:require_smtp_tls]
+ ActionMailer::Base.smtp_settings = MAIL_CONFIG[:account]
+
+else
+ raise "Mail configuration file not found at #{mailer_file_path}"
+end
+
+MAIL_CONFIG ||= {}
diff --git a/config/initializers/schedules.rb b/config/initializers/schedules.rb
new file mode 100644
index 0000000..fb58101
--- /dev/null
+++ b/config/initializers/schedules.rb
@@ -0,0 +1,54 @@
+require 'fastthread'
+require 'openwfe/util/scheduler'
+include OpenWFE
+
+# Initialize a threads array
+threads = []
+
+threads << Thread.new do
+ scheduler = Scheduler.new
+ scheduler.start
+
+ # Do this everyday at 05:00 am
+ scheduler.schedule('0 5 * * *') do # TODO Time must be configurable
+
+ user_no_activity_on_backlog_items = {}
+ user_no_tasks_on_backlog_items = {}
+
+ BacklogItem.find_all_by_state(BacklogItem::STATE_ASSIGNED).each do |backlog_item|
+ if backlog_item.tasks.empty?
+
+ backlog_item_owner = backlog_item.owner
+ user_no_tasks_on_backlog_items[backlog_item_owner] ||= []
+ user_no_tasks_on_backlog_items[backlog_item_owner] << backlog_item
+
+ else
+
+ backlog_item.tasks.each do |task|
+ if task.created_at.to_date < Date.yesterday
+ backlog_item_owner = backlog_item.owner
+ user_no_activity_on_backlog_items[backlog_item_owner] ||= []
+ user_no_activity_on_backlog_items[backlog_item_owner] << backlog_item
+ end
+ end
+
+ end
+ end
+
+ # Send the e-mail
+
+ user_no_activity_on_backlog_items.each do |owner, backlog_items|
+ UserMailer.deliver_no_activity_recorded(owner, backlog_items)
+ end
+
+ user_no_tasks_on_backlog_items.each do |owner, backlog_items|
+ UserMailer.deliver_no_task_recorded(owner, backlog_items)
+ end
+
+ end
+
+ scheduler.join
+end
+
+# Run all threads
+threads.each { |thread| thread.run }
\ No newline at end of file
diff --git a/config/mailer.yml b/config/mailer.yml
deleted file mode 100644
index f62d51e..0000000
--- a/config/mailer.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
- :address: smtp.gmail.com
- :port: 587
- :user_name: username(a)gmail.com
- :password: password
- :authentication: :plain
diff --git a/config/mailer.yml.example b/config/mailer.yml.example
new file mode 100644
index 0000000..d6a6f18
--- /dev/null
+++ b/config/mailer.yml.example
@@ -0,0 +1,9 @@
+---
+ :require_smtp_tls: false
+ :account:
+ :address: smtp.localhost.localdomain
+ :port: 25
+ :user_name: username
+ :password: password
+ :authentication: :login
+ :from: no-reply(a)projxp.org
\ No newline at end of file
diff --git a/test/unit/user_mailer_test.rb b/test/unit/user_mailer_test.rb
index d68a249..7d5fc15 100644
--- a/test/unit/user_mailer_test.rb
+++ b/test/unit/user_mailer_test.rb
@@ -2,8 +2,23 @@ require 'test_helper'
class UserMailerTest < ActionMailer::TestCase
tests UserMailer
- # TODO Test user_mailer
- def test_truth
- assert true
+
+ def setup
+ @user = User.new(:email => 'test(a)projxp.org', :display_name => 'Test')
+ backlog_item = BacklogItem.new
+ backlog_item.user_story = UserStory.new(:title => 'Test')
+ @backlog_items = [backlog_item]
+ end
+
+ def test_new_generated_password
+ assert UserMailer.deliver_new_generated_password(@user, 'password')
+ end
+
+ def test_no_activity_recorded
+ assert UserMailer.deliver_no_activity_recorded(@user, @backlog_items)
+ end
+
+ def test_no_task_recorded
+ assert UserMailer.deliver_no_task_recorded(@user, @backlog_items)
end
end
--
1.5.2.4
15 years, 5 months
Re: [PROJXP [PATCH] Users can view a burndown chart for a sprint. #7
by Darryl L. Pierce
On Wed, Oct 22, 2008 at 3:27 PM, LAN-SUN-LUK Benjamin
<Benjamin.LAN-SUN-LUK(a)supinfo.com> wrote:
> I have a probleme, can you help me ?
> Look at this:
> benji@Wloups ~/Desktop/projxp % git apply 7.patch
> error: patch failed: app/controllers/task_controller.rb:42
> error: app/controllers/task_controller.rb: patch does not apply
> error: patch failed: app/controllers/user_story_controller.rb:69
> error: app/controllers/user_story_controller.rb: patch does not apply
> error: patch failed: app/views/sprint/view.html.erb:1
> error: app/views/sprint/view.html.erb: patch does not apply
> error: patch failed: config/environment.rb:1
> error: config/environment.rb: patch does not apply
> error: patch failed: test/unit/backlog_item_test.rb:67
> error: test/unit/backlog_item_test.rb: patch does not apply
What are you doing to apply the patch? And, how are you extracting the
patch from email?
The way I do apply patches for testing is:
1. git checkout master
2. git pull (to make sure I'm at the latest)
3. git checkout -b test (to create a new temporary testing branch)
4. git am [the patch file]
This assumes that your email client's not munging the text of the
patch. Be sure you get the patch without any thing like changes in
line breaks or word wrapping or anything.
--
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, 5 months