New layout idea...
by Darryl L. Pierce
This patch is a RFC for a new layout idea I've had for the project.
Apply this patch and take a look at the project list, project detail and
project edit pages. I've tried a different approach to putting a little
bit more data into the list page. And I dialed back the amount of data
in the details page.
If you have any additions or suggestions, please provide a patch.
15 years
Changing the layout...
by Darryl L. Pierce
This patch is a proposed change in how lists, details and forms are done
in the project. I've found the lists to be generally more and more
unwieldy and adding epics to projects really brought it out. So I sat
down and played around with the styling and came up with this.
It only affects the project list, project details and project edit
pages. But it's how I'd like to evolve the interface. Please give me
some feedback (and especially patches or fixes).
15 years
[PATCH] If a user doesn't have avatar, then a default avatar is used. #114
by LAN-SUN-LUK Benjamin
---
app/models/user.rb | 18 ++++++------------
app/views/users/_edit.html.erb | 2 --
app/views/users/show.html.erb | 2 --
public/images/icons/default_avatar.gif | Bin 0 -> 10150 bytes
test/unit/user_test.rb | 2 +-
5 files changed, 7 insertions(+), 17 deletions(-)
create mode 100644 public/images/icons/default_avatar.gif
diff --git a/app/models/user.rb b/app/models/user.rb
index 862cbbf..8532072 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -116,20 +116,14 @@ class User < ActiveRecord::Base
(id == project.owner.id) || privileges.admin_projects
end
- # Returns whether the user has an avatar or not.
- def avatar?
- use_gravatar || (!url_for_avatar.nil? && !url_for_avatar.empty?)
- end
-
- # Returns the user's avatar url, or +nil+ if none was defined.
+ # Returns the user's avatar url, or the default avatar url if none was defined.
def avatar_url
- result = nil
-
- if !use_gravatar && !url_for_avatar.nil? && !url_for_avatar.empty?
+ if use_gravatar
+ result = gravatar_url(!url_for_avatar.blank? ? url_for_avatar : email)
+ elsif !use_gravatar && !url_for_avatar.blank?
result = url_for_avatar
- elsif use_gravatar
- result = (!url_for_avatar.nil? && !url_for_avatar.empty?) ? url_for_avatar : email
- result = gravatar_url(result)
+ else
+ result = ConfigProperty.fetch('users.default_avatar_url', '/images/icons/default_avatar.gif')
end
return result
diff --git a/app/views/users/_edit.html.erb b/app/views/users/_edit.html.erb
index 14ab203..5ad9989 100644
--- a/app/views/users/_edit.html.erb
+++ b/app/views/users/_edit.html.erb
@@ -61,11 +61,9 @@
<%= form.text_field :url_for_avatar %>
</td>
</tr>
- <% if @this_user.avatar? %>
<tr>
<td /><td id="current_avatar"><%= image_tag @this_user.avatar_url %></td>
</tr>
- <% end %>
<tr>
<td class="label">Introduction</td>
diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb
index e32ce69..a30d853 100644
--- a/app/views/users/show.html.erb
+++ b/app/views/users/show.html.erb
@@ -26,11 +26,9 @@
<td colspan="2" class="user_details">
<dl>
<dt><%= "#{(a)this_user.display_name}" %></dt>
- <% if @this_user.avatar? %>
<dd>
<%= image_tag @this_user.avatar_url %>
</dd>
- <% end %>
<dd>
<%= RedCloth.new((a)this_user.introduction).to_html %>
</dd>
diff --git a/public/images/icons/default_avatar.gif b/public/images/icons/default_avatar.gif
new file mode 100644
index 0000000000000000000000000000000000000000..28379b4b553bcc3d18eb97744c68f7307f21e4a4
GIT binary patch
literal 10150
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
literal 0
HcmV?d00001
diff --git a/test/unit/user_test.rb b/test/unit/user_test.rb
index 063517c..63dc478 100644
--- a/test/unit/user_test.rb
+++ b/test/unit/user_test.rb
@@ -63,7 +63,7 @@ class UserTest < ActiveSupport::TestCase
# Ensures that a user with no avatar returns the right value when queries.
def test_avatar_reports_no
- flunk "User should not say he has an avatar." if @user_with_no_avatar.avatar?
+ flunk "User should not say he has an avatar." unless @user_with_no_avatar.avatar_url == ConfigProperty.fetch('users.default_avatar_url')
end
# Ensures that a user with an avatar URL returns the right URL.
--
1.6.1
15 years
Re: [PROJXP] [ProjXP] #115: Users can upload an avatar image for their account.
by Darryl L. Pierce
On Tue, Apr 7, 2009 at 6:37 AM, ProjXP <trac(a)fedorahosted.org> wrote:
> #115: Users can upload an avatar image for their account.
I think this one we shouldn't do: with using gravatar, I think we
don't need to bother at this point with doing our own picture
uploading. The user can put their headshot on a separate site and
reference it via the url_for_avatar field of User, or they can use
Gravatar. I wouldn't bother with this task.
--
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
[PATCH] Projects can have epic user stories. #103
by Darryl L. Pierce
THIS PATCH REQUIRES A MIGRATION.
Introduces the new Epic class with unit tests.
Signed-off-by: Darryl L. Pierce <mcpierce(a)gmail.com>
---
app/models/epic.rb | 34 +++++++++
db/migrate/029_create_epics.rb | 35 +++++++++
.../030_user_stories_reference_project_epics.rb | 29 ++++++++
doc/ChangeLog | 2 +
test/fixtures/epics.yml | 4 +
test/unit/epic_test.rb | 74 ++++++++++++++++++++
6 files changed, 178 insertions(+), 0 deletions(-)
create mode 100644 app/models/epic.rb
create mode 100644 db/migrate/029_create_epics.rb
create mode 100644 db/migrate/030_user_stories_reference_project_epics.rb
create mode 100644 test/fixtures/epics.yml
create mode 100644 test/unit/epic_test.rb
diff --git a/app/models/epic.rb b/app/models/epic.rb
new file mode 100644
index 0000000..6763cae
--- /dev/null
+++ b/app/models/epic.rb
@@ -0,0 +1,34 @@
+# epic.rb
+# Copyright (C) 2009, Darryl L. Pierce <mcpierce(a)gmail.com>
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+# An +Epic+ represents a project-level user story, one that is an overarching feature.
+# An epic is a feature that is implemented by many individual user stories or
+# in more than one product.
+class Epic < ActiveRecord::Base
+ belongs_to :project
+ validates_presence_of :project_id,
+ :message => "A project must be specified."
+
+ validates_presence_of :priority,
+ :message => "A priority must be provided."
+ validates_numericality_of :priority,
+ :message => "Priority must be a positive integer value.",
+ :only_integer => true,
+ :greater_than => 0
+
+ validates_presence_of :title
+end
diff --git a/db/migrate/029_create_epics.rb b/db/migrate/029_create_epics.rb
new file mode 100644
index 0000000..1d04545
--- /dev/null
+++ b/db/migrate/029_create_epics.rb
@@ -0,0 +1,35 @@
+# 029_create_epics.rb
+# Copyright (C) 2009, Darryl L. Pierce <mcpierce(a)gmail.com>
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+class CreateEpics < ActiveRecord::Migration
+ def self.up
+ create_table :epics do |t|
+ t.integer :project_id, :null => false
+ t.integer :priority, :null => false
+ t.boolean :closed, :null => false, :default => false
+ t.string :title, :null => false, :limit => 100
+ t.text :description
+ end
+
+ execute 'alter table epics add constraint fk_epic_project
+ foreign key (project_id) references projects(id)'
+ end
+
+ def self.down
+ drop_table :epics
+ end
+end
diff --git a/db/migrate/030_user_stories_reference_project_epics.rb b/db/migrate/030_user_stories_reference_project_epics.rb
new file mode 100644
index 0000000..6aadc55
--- /dev/null
+++ b/db/migrate/030_user_stories_reference_project_epics.rb
@@ -0,0 +1,29 @@
+# 030_user_stories_reference_project_epics.rb
+# Copyright (C) 2009, Darryl L. Pierce <mcpierce(a)gmail.com>
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+class UserStoriesReferenceProjectEpics < ActiveRecord::Migration
+ def self.up
+ add_column :user_stories, :epic_id, :integer, :null => true
+
+ execute 'alter table user_stories add constraint fk_user_story_epic
+ foreign key (epic_id) references epics(id)'
+ end
+
+ def self.down
+ remove_column :user_stories, :epic_id
+ end
+end
diff --git a/doc/ChangeLog b/doc/ChangeLog
index 35af777..4515634 100644
--- a/doc/ChangeLog
+++ b/doc/ChangeLog
@@ -3,6 +3,8 @@ Change Log (0.2.0):
* #98 - Admins can approve projects.
* #99 - Sprints can have team leads.
* #100 - Team leads can reopen items.
+ * #103 - Projects can have epic stories.
+ * #104 - User stories can be associated with project epics.
* #105 - Users can select another sprint from the sprint details page.
* #106 - Task descriptions can have wiki markup.
* #107 - User story descriptions can have wiki markup.
diff --git a/test/fixtures/epics.yml b/test/fixtures/epics.yml
new file mode 100644
index 0000000..492bf61
--- /dev/null
+++ b/test/fixtures/epics.yml
@@ -0,0 +1,4 @@
+user_stories:
+ project_id: <%= Fixtures.identify(:projxp) %>
+ priority: 1
+ title: User stories capture individual features.
diff --git a/test/unit/epic_test.rb b/test/unit/epic_test.rb
new file mode 100644
index 0000000..f79edc4
--- /dev/null
+++ b/test/unit/epic_test.rb
@@ -0,0 +1,74 @@
+# user_story_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 EpicTest < ActiveSupport::TestCase
+ fixtures :projects
+
+ def setup
+ @project = projects(:projxp)
+ @new_epic = Epic.new(:project => @project, :priority => 1, :title => "This is an epic")
+ end
+
+ # Ensures the project_id field is required.
+ def test_valid_fails_without_project_id
+ @new_epic.project = nil
+ fail "Project must be required for an epic." if @new_epic.valid?
+ end
+
+ # Ensures that a priority is required.
+ def test_valid_fails_without_priority
+ @new_epic.priority = nil
+ fail "Priority must be required for an epic." if @new_epic.valid?
+ end
+
+ # Ensures that priority is a number.
+ def test_valid_fails_with_nonnumeric_priority
+ @new_epic.priority = 'a'
+ fail "Priority must be a number." if @new_epic.valid?
+ end
+
+ # Ensures that a priority must be a positive integer.
+ def test_valid_fails_with_negative_integer_priority
+ @new_epic.priority = -1
+ fail "Priority cannot be negative." if @new_epic.valid?
+ end
+
+ # Ensures that a priority cannot be 0.
+ def test_valid_fails_with_zero_priority
+ @new_epic.priority = 0
+ fail "Priority cannot be zero." if @new_epic.valid?
+ end
+
+ # Ensures that a title is required.
+ def test_valid_fails_without_title
+ @new_epic.title = nil
+ fail "A title must be provided." if @new_epic.valid?
+ end
+
+ # Ensures that a title cannot be an empty string.
+ def test_valid_fails_with_empty_title
+ @new_epic.title = ""
+ fail "A title cannot be 0 bytes long." if @new_epic.valid?
+ end
+
+ # Ensures that a well-formed epic is valid.
+ def test_valid
+ fail "There is a general problect with validation." unless @new_epic.valid?
+ end
+end
--
1.6.0.6
15 years
[PATCH] Users can have avatars. #113
by Darryl L. Pierce
Users can host a headshot image anywhere on the web and then assign that
to their account. Also supported are Gravatar images.
THIS PATCH REQUIRES A MIGRATION
Added a method, User.avatar?, to check if the user has defined an
avatar. Another method, User.avatar_url, returns the user's avatar URL
or else nil if no avatar was defined.
On the user's profile page, the avatar is shown in a div separate from
the user's details.
Signed-off-by: Darryl L. Pierce <mcpierce(a)gmail.com>
---
app/helpers/application_helper.rb | 2 -
app/models/user.rb | 21 +++++++++++++
app/views/users/_edit.html.erb | 19 +++++++++++
app/views/users/_user.html.erb | 6 ----
app/views/users/show.html.erb | 19 +++++++++--
db/migrate/028_add_avatar_url_to_users.rb | 28 +++++++++++++++++
lib/avatar.rb | 47 +++++++++++++++++++++++++++++
public/stylesheets/details.css | 31 ++++++++++++++++++-
test/fixtures/users.yml | 4 ++
test/unit/user_test.rb | 40 ++++++++++++++++++++++++
10 files changed, 204 insertions(+), 13 deletions(-)
create mode 100644 db/migrate/028_add_avatar_url_to_users.rb
create mode 100644 lib/avatar.rb
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 9e81b48..02b10ab 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -45,7 +45,6 @@ module ApplicationHelper
end
# Creates a link to a backlog item.
- #
def link_to_backlog_item(backlog_item, options = {})
@template.link_to(options[:text] ? options[:text] : backlog_item.title,
:controller => :backlog,
@@ -54,7 +53,6 @@ module ApplicationHelper
end
# Shows the hours for a given object as estimated, actual and remaining.
- #
def show_hours_as_ear(estimated)
format("%0.1f/%0.1f/%0.1f",estimated.estimated_hours, estimated.actual_hours, estimated.remaining_hours)
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 613b7ad..862cbbf 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -15,6 +15,8 @@
# this program. If not, see <http://www.gnu.org/licenses/>.
#
+require 'avatar'
+
# +User+ represents a single user in the system.
#
class User < ActiveRecord::Base
@@ -114,6 +116,25 @@ class User < ActiveRecord::Base
(id == project.owner.id) || privileges.admin_projects
end
+ # Returns whether the user has an avatar or not.
+ def avatar?
+ use_gravatar || (!url_for_avatar.nil? && !url_for_avatar.empty?)
+ end
+
+ # Returns the user's avatar url, or +nil+ if none was defined.
+ def avatar_url
+ result = nil
+
+ if !use_gravatar && !url_for_avatar.nil? && !url_for_avatar.empty?
+ result = url_for_avatar
+ elsif use_gravatar
+ result = (!url_for_avatar.nil? && !url_for_avatar.empty?) ? url_for_avatar : email
+ result = gravatar_url(result)
+ end
+
+ return result
+ end
+
private
def self.encrypted_password(password, salt)
diff --git a/app/views/users/_edit.html.erb b/app/views/users/_edit.html.erb
index bfe36fd..14ab203 100644
--- a/app/views/users/_edit.html.erb
+++ b/app/views/users/_edit.html.erb
@@ -48,6 +48,25 @@
<% end %>
+ <tr>
+ <td class="label"></td>
+ <td class="value">
+ <%= form.check_box :use_gravatar %>
+ <%= form.label :use_gravatar, "Use a Gravatar-hosted image?" %>
+ </td>
+ </tr>
+ <tr>
+ <td class="label">Avatar URL/Gravatar email</td>
+ <td class="value">
+ <%= form.text_field :url_for_avatar %>
+ </td>
+ </tr>
+ <% if @this_user.avatar? %>
+ <tr>
+ <td /><td id="current_avatar"><%= image_tag @this_user.avatar_url %></td>
+ </tr>
+ <% end %>
+
<tr>
<td class="label">Introduction</td>
<td class="value">
diff --git a/app/views/users/_user.html.erb b/app/views/users/_user.html.erb
index c1950fa..974ba3e 100644
--- a/app/views/users/_user.html.erb
+++ b/app/views/users/_user.html.erb
@@ -12,12 +12,6 @@
<tbody>
<tr>
- <td class="toolbar" colspan="2">
- <%= link_to image_tag("icons/user.png", :title => "View user's page..."), user_path(user) %>
- </td>
- </tr>
-
- <tr>
<td class="text" colspan="2"><%= RedCloth.new(user.introduction).to_html %></td>
</tr>
</tbody>
diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb
index 4414c78..e32ce69 100644
--- a/app/views/users/show.html.erb
+++ b/app/views/users/show.html.erb
@@ -16,15 +16,26 @@
<%= link_to(image_tag("icons/back.png", :title => "Back..."), users_path) %>
<%= link_to(image_tag("icons/roles.png", :title => "View this user's roles..."), roles_user_path(@user)) %>
<%= link_to(image_tag("icons/edit.png", :title => "Edit this user..."),
- edit_user_path(@this_user)) if @this_user.can_edit?(@user) %>
+ edit_user_path(@this_user)) if @this_user.can_edit?(@user) %>
<%= link_to(image_tag("icons/password.png", :title => "Change the account password..."),
- password_user_path(@this_user)) if @this_user.can_edit?(@user) %>
+ password_user_path(@this_user)) if @this_user.can_edit?(@user) %>
</td>
</tr>
<tr>
- <td class="text" colspan="2">
- <%= RedCloth.new((a)this_user.introduction).to_html %></td>
+ <td colspan="2" class="user_details">
+ <dl>
+ <dt><%= "#{(a)this_user.display_name}" %></dt>
+ <% if @this_user.avatar? %>
+ <dd>
+ <%= image_tag @this_user.avatar_url %>
+ </dd>
+ <% end %>
+ <dd>
+ <%= RedCloth.new((a)this_user.introduction).to_html %>
+ </dd>
+ </dl>
+ </td>
</tr>
</tbody>
</table>
diff --git a/db/migrate/028_add_avatar_url_to_users.rb b/db/migrate/028_add_avatar_url_to_users.rb
new file mode 100644
index 0000000..7becc37
--- /dev/null
+++ b/db/migrate/028_add_avatar_url_to_users.rb
@@ -0,0 +1,28 @@
+# 027_create_sprint_members.rb
+# Copyright (C) 2009, Darryl L. Pierce <mcpierce(a)gmail.com>
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+class AddAvatarUrlToUsers < ActiveRecord::Migration
+ def self.up
+ add_column :users, :url_for_avatar, :string, :null => true, :limit => 128
+ add_column :users, :use_gravatar, :boolean, :null => false, :default => false
+ end
+
+ def self.down
+ remove_column :users, :url_for_avatar
+ remove_column :users, :use_gravatar
+ end
+end
diff --git a/lib/avatar.rb b/lib/avatar.rb
new file mode 100644
index 0000000..1b6416b
--- /dev/null
+++ b/lib/avatar.rb
@@ -0,0 +1,47 @@
+# avatar.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/>.
+#
+
+# Generates a URL for Gravatar-hosted images.
+def gravatar_url(email, size="80", rating=nil, default=nil)
+ # The supported ratings from gravatar
+ allowed_ratings = ['g', 'pg', 'r', 'x']
+
+ # query will be our extra param string
+ query = "s=" + size + "&"
+ # if we have a rating ...
+ if rating != nil
+ # ... and it is allowed
+ if allowed_ratings.index(rating) != nil
+ query += "r=" + rating + "&"
+ else
+ # if it is not allowed, raise an exception
+ raise "No such gravatar rating: '" + rating + "'"
+ end
+ end
+ # if we have a default then use it
+ if default != nil
+ query += "d=" + URI.parse(URI.encode(default)).to_s() + "&"
+ end
+ # put it all together in an HTTP object
+ url = URI::HTTP.build({
+ :host => 'www.gravatar.com',
+ :path => '/avatar/' + Digest::MD5.hexdigest(email),
+ :query => query[0, query.size()-1]
+ })
+ # and return the built string
+ return url.to_s()
+end
diff --git a/public/stylesheets/details.css b/public/stylesheets/details.css
index 82bacdf..515b06e 100644
--- a/public/stylesheets/details.css
+++ b/public/stylesheets/details.css
@@ -13,7 +13,7 @@ table.detail {
table.detail th.title, table.detail td.title {
font-weight: bold;
- text-align: center;
+ text-align: center;
border-bottom: 1px solid #000000;
background-color: #f3f3f3;
}
@@ -34,3 +34,32 @@ table.detail td.text {
font-size: 90%;
padding: 5px 10px 1px 10px;
}
+
+table.detail td.user_details {
+ width: 300px;
+ padding: 10px 0;
+ border: 2px solid #ccdd2;
+}
+
+table.detail td.user_details dl {
+ margin: 10px 20px;
+ padding: 0;
+}
+
+table.detail td.user_details dt {
+ margin: 0;
+ padding: 0;
+ font-size: 130%;
+ letter-spacing: 1px;
+}
+
+table.detail td.user_details dd {
+ margin: 0;
+ padding: 0;
+ font-size: 85%;
+ line-height: 1.5em;
+}
+
+table.detail td.user_details dd img {
+ float:left;
+}
diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml
index 2738441..46dcc78 100644
--- a/test/fixtures/users.yml
+++ b/test/fixtures/users.yml
@@ -41,6 +41,7 @@ mcpierce:
introduction: How are you?
salt: <%= SALT %>
hashed_password: <%= User.encrypted_password('farkle', SALT) %>
+ use_gravatar: true
jdonuts:
email: jdonuts(a)gmail.com
@@ -48,6 +49,8 @@ jdonuts:
introduction: How's it going, eh?
salt: <%= SALT %>
hashed_password: <%= User.encrypted_password('jelly', SALT) %>
+ use_gravatar: true
+ url_for_avatar: jdonuts(a)projxp.org
celliot:
email: celliot(a)gmail.com
@@ -55,6 +58,7 @@ celliot:
introduction: Get a life.
salt: <%= SALT %>
hashed_password: <%= User.encrypted_password('loser', SALT) %>
+ url_for_avatar: http://www.born-today.com/btpix/elliot_chris.jpg
unverified_user:
email: unverified(a)newuser.com
diff --git a/test/unit/user_test.rb b/test/unit/user_test.rb
index 7d3cc9b..063517c 100644
--- a/test/unit/user_test.rb
+++ b/test/unit/user_test.rb
@@ -16,6 +16,7 @@
#
require File.dirname(__FILE__) + '/../test_helper'
+require 'avatar'
class UserTest < ActiveSupport::TestCase
fixtures :users
@@ -25,6 +26,16 @@ class UserTest < ActiveSupport::TestCase
@verified_user = @mcpierce
raise "User should be verified!" unless @verified_user.verified?
@unverified_user = users(:unverified_user)
+
+ @user_with_no_avatar = users(:admin)
+ raise "User should not have an avatar!" unless !(a)user_with_no_avatar.use_gravatar && @user_with_no_avatar.url_for_avatar.nil?
+ @user_with_avatar = users(:celliot)
+ raise "User should have an avatar!" if @user_with_avatar.url_for_avatar.nil?
+ @user_with_gravatar = @mcpierce
+ raise "User does not use gravatar!" unless @user_with_gravatar.use_gravatar
+ @user_with_special_gravatar = users(:jdonuts)
+ raise "User is not using gravatar!" unless @user_with_special_gravatar.use_gravatar
+ raise "User does not have a special avatar URL!" if @user_with_special_gravatar.avatar_url.nil?
end
# Ensures that authenticating a user using the wrong password fails.
@@ -49,4 +60,33 @@ class UserTest < ActiveSupport::TestCase
def test_verified_with_unverified_user
flunk "User is not verified." if @unverified_user.verified?
end
+
+ # Ensures that a user with no avatar returns the right value when queries.
+ def test_avatar_reports_no
+ flunk "User should not say he has an avatar." if @user_with_no_avatar.avatar?
+ end
+
+ # Ensures that a user with an avatar URL returns the right URL.
+ def test_avatar_url
+ expected = @user_with_avatar.url_for_avatar
+
+ assert_equal expected, @user_with_avatar.avatar_url,
+ "Wrong avatar URL returned."
+ end
+
+ # Ensures that a user with a gravatar image returns the right url.
+ def test_gravatar_url
+ expected = gravatar_url((a)user_with_gravatar.email)
+
+ assert_equal expected, @user_with_gravatar.avatar_url,
+ "Wrong avatar URL returned."
+ end
+
+ # Ensures that a user with a special gravatar image gets the right URL.
+ def test_gravatar_special_url
+ expected = gravatar_url((a)user_with_special_gravatar.url_for_avatar)
+
+ assert_equal expected, @user_with_special_gravatar.avatar_url,
+ "Wrong avatar URL returned."
+ end
end
--
1.6.0.6
15 years
[PATCH] *** SUBJECT HERE ***
by Darryl L. Pierce
This series of patches introduces support for wiki markup text in several
parts of the application. You'll need to install the RedCloth gem. The
documentation for Textile (of which RedCloth is a Ruby implementation) can
be found here:
http://textism.com/tools/textile/
Darryl L. Pierce (6):
Tasks can use wiki markup. #106
User story descriptions can use wiki markup. #107
Sprint descriptions can use wiki mark. #108
Product descriptions can use wiki markup. #109
Project descriptions can contain wiki markup. #110
User introductions can use wiki markup. #111
app/views/products/_list.html.erb | 2 +-
app/views/products/_product.html.erb | 2 +-
app/views/projects/_details.html.erb | 50 +++++++++++++++++-----------------
app/views/projects/index.html.erb | 2 +-
app/views/projects/show.html.erb | 2 +-
app/views/sprints/create.html.erb | 2 -
app/views/sprints/destroy.html.erb | 2 -
app/views/sprints/show.html.erb | 2 +-
app/views/stories/create.html.erb | 2 -
app/views/stories/show.html.erb | 2 +-
app/views/tasks/_list.html.erb | 2 +-
app/views/tasks/show.html.erb | 4 ++-
app/views/users/_list.html.erb | 2 +-
app/views/users/_user.html.erb | 2 +-
app/views/users/show.html.erb | 2 +-
config/environment.rb | 1 +
doc/ChangeLog | 1 +
17 files changed, 40 insertions(+), 42 deletions(-)
delete mode 100644 app/views/sprints/create.html.erb
delete mode 100644 app/views/sprints/destroy.html.erb
delete mode 100644 app/views/stories/create.html.erb
15 years
[PATCH] Users can select another sprint from the sprint page. #105
by Darryl L. Pierce
The controller loads the list of all sprints, sorted by status. On the
sprint details page the drop down list is show with the current sprint
selected. If the user selects a different sprint and clicks the switch
button then the other sprint is shown.
Added a new route, show_product_sprint_path, that takes as input a
product reference and an argument which is the sprint id.
Signed-off-by: Darryl L. Pierce <mcpierce(a)gmail.com>
---
app/controllers/sprints_controller.rb | 1 +
app/views/sprints/show.html.erb | 9 +++++++++
config/routes.rb | 4 ++++
doc/ChangeLog | 1 +
test/functional/sprints_controller_test.rb | 1 +
5 files changed, 16 insertions(+), 0 deletions(-)
diff --git a/app/controllers/sprints_controller.rb b/app/controllers/sprints_controller.rb
index 528c1ac..3535a5e 100644
--- a/app/controllers/sprints_controller.rb
+++ b/app/controllers/sprints_controller.rb
@@ -39,6 +39,7 @@ class SprintsController < ApplicationController
# GET /products/1/sprints/1
def show
+ @sprints = Sprint.find(:all, :order => "status ASC", :conditions =>["product_id = ?", @product.id])
@title = "Sprint #{(a)sprint.id}"
@backlog_items = BacklogItem.paginate(
:conditions => ['sprint_id = ?', @sprint.id],
diff --git a/app/views/sprints/show.html.erb b/app/views/sprints/show.html.erb
index 547a0f7..45e2e81 100644
--- a/app/views/sprints/show.html.erb
+++ b/app/views/sprints/show.html.erb
@@ -33,6 +33,15 @@
</tr>
<tr>
+ <td class="label">Switch to:</td>
+ <td class="value">
+ <%= form_tag show_product_sprint_path(@product) %>
+ <%= select_tag("id", options_for_select((a)sprints.collect{ |sprint| ["#{sprint.title} (#{sprint.status_text})", sprint.id] }, @sprint.id)) %>
+ <%= submit_tag "Switch" %>
+ </td>
+ </tr>
+
+ <tr>
<td class="label">Team lead:</td>
<td class="value"><%= mail_to @sprint.team_lead.email, @sprint.team_lead.display_name %></td>
</tr>
diff --git a/config/routes.rb b/config/routes.rb
index c667c1f..aa2b8fc 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -30,6 +30,10 @@ ActionController::Routing::Routes.draw do |map|
map.resources :products do |product|
product.resources :roles
product.resources :stories
+ product.resource :sprint, :member =>
+ {
+ :show => :post
+ }
product.resources(:sprints, :member =>
{
:plan => :get,
diff --git a/doc/ChangeLog b/doc/ChangeLog
index 947c98f..f72af9a 100644
--- a/doc/ChangeLog
+++ b/doc/ChangeLog
@@ -3,6 +3,7 @@ Change Log (0.2.0):
* #98 - Admins can approve projects. - Darryl L. Pierce <mcpierce(a)gmail.com>
* #99 - Sprints can have team leads. - Darryl L. Pierce <mcpierce(a)gmail.com>
* #100 - Team leads can reopen items. - Darryl L. Pierce <mcpierce(a)gmail.com>
+ * #105 - Users can select another sprint from the sprint details page. - Darryl L. Pierce <mcpierce(a)gmail.com>
* #125 - Added a system title to all pages. - Darryl L. Pierce <mcpierce(a)gmail.com>
* #131 - Admins can filter out unapproved projects. - Darryl L. Pierce <mcpierce(a)gmail.com>
* #132 - Team leads can start sprints. - Darryl L. Pierce <mcpierce(a)gmail.com>
diff --git a/test/functional/sprints_controller_test.rb b/test/functional/sprints_controller_test.rb
index c2a7066..7536418 100644
--- a/test/functional/sprints_controller_test.rb
+++ b/test/functional/sprints_controller_test.rb
@@ -108,6 +108,7 @@ class SprintsControllerTest < ActionController::TestCase
assert_response :success
assert assigns['sprint'], "Failed to load a sprint."
+ assert assigns['sprints'], "Failed to load all sprints."
assert_equal @active_sprint.id, assigns['sprint'].id,
"Failed to load the correct sprint."
assert assigns['backlog_items'], "Failed to set the backlog."
--
1.6.0.6
15 years