diff --git a/app/controllers/versions_controller.rb b/app/controllers/versions_controller.rb
index 4e9016eb..1365c97e 100644
--- a/app/controllers/versions_controller.rb
+++ b/app/controllers/versions_controller.rb
@@ -21,6 +21,9 @@ class VersionsController < ApplicationController
cache_sweeper :version_sweeper, :only => [ :edit, :destroy ]
+ def show
+ end
+
def edit
if request.post? and @version.update_attributes(params[:version])
flash[:notice] = l(:notice_successful_update)
@@ -49,6 +52,13 @@ class VersionsController < ApplicationController
flash[:notice] = l(:notice_successful_delete)
redirect_to :controller => 'projects', :action => 'list_files', :id => @project
end
+
+ def status_by
+ respond_to do |format|
+ format.html { render :action => 'show' }
+ format.js { render(:update) {|page| page.replace_html 'status_by', render_issue_status_by(@version, params[:status_by])} }
+ end
+ end
private
def find_project
diff --git a/app/helpers/versions_helper.rb b/app/helpers/versions_helper.rb
index 452f4d7f..0fcc6407 100644
--- a/app/helpers/versions_helper.rb
+++ b/app/helpers/versions_helper.rb
@@ -16,4 +16,32 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module VersionsHelper
+
+ STATUS_BY_CRITERIAS = %w(category tracker priority author assigned_to)
+
+ def render_issue_status_by(version, criteria)
+ criteria ||= 'category'
+ raise 'Unknown criteria' unless STATUS_BY_CRITERIAS.include?(criteria)
+
+ h = Hash.new {|k,v| k[v] = [0, 0]}
+ begin
+ # Total issue count
+ Issue.count(:group => criteria,
+ :conditions => ["#{Issue.table_name}.fixed_version_id = ?", version.id]).each {|c,s| h[c][0] = s}
+ # Open issues count
+ Issue.count(:group => criteria,
+ :include => :status,
+ :conditions => ["#{Issue.table_name}.fixed_version_id = ? AND #{IssueStatus.table_name}.is_closed = ?", version.id, false]).each {|c,s| h[c][1] = s}
+ rescue ActiveRecord::RecordNotFound
+ # When grouping by an association, Rails throws this exception if there's no result (bug)
+ end
+ counts = h.keys.compact.sort.collect {|k| {:group => k, :total => h[k][0], :open => h[k][1], :closed => (h[k][0] - h[k][1])}}
+ max = counts.collect {|c| c[:total]}.max
+
+ render :partial => 'issue_counts', :locals => {:version => version, :criteria => criteria, :counts => counts, :max => max}
+ end
+
+ def status_by_options_for_select(value)
+ options_for_select(STATUS_BY_CRITERIAS.collect {|criteria| [l("field_#{criteria}".to_sym), criteria]}, value)
+ end
end
diff --git a/app/models/issue_category.rb b/app/models/issue_category.rb
index 9478504f..51baeb41 100644
--- a/app/models/issue_category.rb
+++ b/app/models/issue_category.rb
@@ -35,5 +35,9 @@ class IssueCategory < ActiveRecord::Base
destroy_without_reassign
end
+ def <=>(category)
+ name <=> category.name
+ end
+
def to_s; name end
end
diff --git a/app/models/tracker.rb b/app/models/tracker.rb
index 6de2a098..8d864774 100644
--- a/app/models/tracker.rb
+++ b/app/models/tracker.rb
@@ -29,6 +29,10 @@ class Tracker < ActiveRecord::Base
def to_s; name end
+ def <=>(tracker)
+ name <=> tracker.name
+ end
+
def self.all
find(:all, :order => 'position')
end
diff --git a/app/views/projects/roadmap.rhtml b/app/views/projects/roadmap.rhtml
index 7c3544a5..daf7639f 100644
--- a/app/views/projects/roadmap.rhtml
+++ b/app/views/projects/roadmap.rhtml
@@ -5,48 +5,28 @@
<% end %>
<% @versions.each do |version| %>
- <%= version.name %>
- <% if version.completed? %>
-
<%= format_date(version.effective_date) %>
- <% elsif version.overdue? %>
- <%= l(:label_roadmap_overdue, distance_of_time_in_words(Time.now, version.effective_date)) %> (<%= format_date(version.effective_date) %>)
- <% elsif version.effective_date %>
- <%=l(:label_roadmap_due_in)%> <%= distance_of_time_in_words Time.now, version.effective_date %> (<%= format_date(version.effective_date) %>)
- <% end %>
- <%=h version.description %>
-
- <% if version.fixed_issues.count > 0 %>
- <%= progress_bar([version.closed_pourcent, version.completed_pourcent], :width => '40em', :legend => ('%0.0f%' % version.completed_pourcent)) %>
-
- <%= link_to(version.closed_issues_count, :controller => 'issues', :action => 'index', :project_id => @project, :status_id => 'c', :fixed_version_id => version, :set_filter => 1) %>
- <%= lwr(:label_closed_issues, version.closed_issues_count) %>
- (<%= '%0.0f' % (version.closed_issues_count.to_f / version.fixed_issues.count * 100) %>%)
-
- <%= link_to(version.open_issues_count, :controller => 'issues', :action => 'index', :project_id => @project, :status_id => 'o', :fixed_version_id => version, :set_filter => 1) %>
- <%= lwr(:label_open_issues, version.open_issues_count)%>
- (<%= '%0.0f' % (version.open_issues_count.to_f / version.fixed_issues.count * 100) %>%)
-
- <%= render(:partial => "wiki/content", :locals => {:content => version.wiki_page.content}) if version.wiki_page %>
- <% issues = version.fixed_issues.find(:all,
- :include => [:status, :tracker],
- :conditions => ["tracker_id in (#{@selected_tracker_ids.join(',')})"],
- :order => "#{Tracker.table_name}.position") unless @selected_tracker_ids.empty?
- issues ||= []
- %>
-
- <% if issues.size > 0 %>
- <% issues.each do |issue| %>
- -
- <%= link = link_to_issue(issue)
- issue.status.is_closed? ? content_tag("del", link) : link %>: <%=h issue.subject %>
- <%= content_tag "em", "(#{l(:label_closed_issues)})" if issue.status.is_closed? %>
-
- <% end %>
- <% end %>
-
- <% else %>
- <%= l(:label_roadmap_no_issues) %>
- <% end %>
+ <%= tag 'a', :name => version.name %>
+ <%= link_to h(version.name), :controller => 'versions', :action => 'show', :id => version %>
+ <%= render :partial => 'versions/overview', :locals => {:version => version} %>
+ <%= render(:partial => "wiki/content", :locals => {:content => version.wiki_page.content}) if version.wiki_page %>
+
+ <% issues = version.fixed_issues.find(:all,
+ :include => [:status, :tracker],
+ :conditions => ["tracker_id in (#{@selected_tracker_ids.join(',')})"],
+ :order => "#{Tracker.table_name}.position") unless @selected_tracker_ids.empty?
+ issues ||= []
+ %>
+
+ <% if issues.size > 0 %>
+ <% issues.each do |issue| %>
+ -
+ <%= link = link_to_issue(issue)
+ issue.status.is_closed? ? content_tag("del", link) : link %>: <%=h issue.subject %>
+ <%= content_tag "em", "(#{l(:label_closed_issues)})" if issue.status.is_closed? %>
+
+ <% end %>
+ <% end %>
+
<% end %>
<% content_for :sidebar do %>
@@ -66,3 +46,5 @@
<%= link_to version.name, :anchor => version.name %>
<% end %>
<% end %>
+
+<% set_html_title l(:label_roadmap) %>
diff --git a/app/views/projects/settings/_versions.rhtml b/app/views/projects/settings/_versions.rhtml
index 63c408b0..7329c7f3 100644
--- a/app/views/projects/settings/_versions.rhtml
+++ b/app/views/projects/settings/_versions.rhtml
@@ -11,7 +11,7 @@
<% for version in @project.versions.sort %>
- <%=h version.name %> |
+ <%= link_to h(version.name), :controller => 'versions', :action => 'show', :id => version %> |
<%= format_date(version.effective_date) %> |
<%=h version.description %> |
<%= link_to(version.wiki_page_title, :controller => 'wiki', :page => Wiki.titleize(version.wiki_page_title)) unless version.wiki_page_title.blank? || @project.wiki.nil? %> |
diff --git a/app/views/versions/_issue_counts.rhtml b/app/views/versions/_issue_counts.rhtml
new file mode 100644
index 00000000..4bab5c65
--- /dev/null
+++ b/app/views/versions/_issue_counts.rhtml
@@ -0,0 +1,35 @@
+
diff --git a/app/views/versions/_overview.rhtml b/app/views/versions/_overview.rhtml
new file mode 100644
index 00000000..d3aa6b18
--- /dev/null
+++ b/app/views/versions/_overview.rhtml
@@ -0,0 +1,24 @@
+<% if version.completed? %>
+ <%= format_date(version.effective_date) %>
+<% elsif version.overdue? %>
+ <%= l(:label_roadmap_overdue, distance_of_time_in_words(Time.now, version.effective_date)) %> (<%= format_date(version.effective_date) %>)
+<% elsif version.effective_date %>
+ <%=l(:label_roadmap_due_in)%> <%= distance_of_time_in_words Time.now, version.effective_date %> (<%= format_date(version.effective_date) %>)
+<% end %>
+
+<%=h version.description %>
+
+<% if version.fixed_issues.count > 0 %>
+ <%= progress_bar([version.closed_pourcent, version.completed_pourcent], :width => '40em', :legend => ('%0.0f%' % version.completed_pourcent)) %>
+
+ <%= link_to(version.closed_issues_count, :controller => 'issues', :action => 'index', :project_id => version.project, :status_id => 'c', :fixed_version_id => version, :set_filter => 1) %>
+ <%= lwr(:label_closed_issues, version.closed_issues_count) %>
+ (<%= '%0.0f' % (version.closed_issues_count.to_f / version.fixed_issues.count * 100) %>%)
+
+ <%= link_to(version.open_issues_count, :controller => 'issues', :action => 'index', :project_id => version.project, :status_id => 'o', :fixed_version_id => version, :set_filter => 1) %>
+ <%= lwr(:label_open_issues, version.open_issues_count)%>
+ (<%= '%0.0f' % (version.open_issues_count.to_f / version.fixed_issues.count * 100) %>%)
+
+<% else %>
+ <%= l(:label_roadmap_no_issues) %>
+<% end %>
diff --git a/app/views/versions/show.rhtml b/app/views/versions/show.rhtml
new file mode 100644
index 00000000..d8e2d243
--- /dev/null
+++ b/app/views/versions/show.rhtml
@@ -0,0 +1,14 @@
+
+<%= link_to_if_authorized l(:button_edit), {:controller => 'versions', :action => 'edit', :id => @version}, :class => 'icon icon-edit' %>
+
+
+<%= h(@version.name) %>
+
+
+<%= render_issue_status_by(@version, params[:status_by]) if @version.fixed_issues.count > 0 %>
+
+
+<%= render :partial => 'versions/overview', :locals => {:version => @version} %>
+<%= render(:partial => "wiki/content", :locals => {:content => @version.wiki_page.content}) if @version.wiki_page %>
+
+<% set_html_title h(@version.name) %>
diff --git a/lang/bg.yml b/lang/bg.yml
index 5f2dc6a8..59095268 100644
--- a/lang/bg.yml
+++ b/lang/bg.yml
@@ -548,3 +548,4 @@ field_time_zone: Time zone
text_caracters_minimum: Must be at least %d characters long.
setting_bcc_recipients: Blind carbon copy recipients (bcc)
button_annotate: Annotate
+label_issues_by: Issues by %s
diff --git a/lang/cs.yml b/lang/cs.yml
index 49697dda..816f9b92 100644
--- a/lang/cs.yml
+++ b/lang/cs.yml
@@ -548,3 +548,4 @@ field_time_zone: Time zone
text_caracters_minimum: Must be at least %d characters long.
setting_bcc_recipients: Blind carbon copy recipients (bcc)
button_annotate: Annotate
+label_issues_by: Issues by %s
diff --git a/lang/de.yml b/lang/de.yml
index 870f4501..046ed999 100644
--- a/lang/de.yml
+++ b/lang/de.yml
@@ -548,3 +548,4 @@ field_time_zone: Zeitzone
text_caracters_minimum: Muss mindestens %d Zeichen lang sein.
setting_bcc_recipients: Blind carbon copy recipients (bcc)
button_annotate: Annotate
+label_issues_by: Issues by %s
diff --git a/lang/en.yml b/lang/en.yml
index cdecb669..104e7fe6 100644
--- a/lang/en.yml
+++ b/lang/en.yml
@@ -210,6 +210,7 @@ label_issue: Issue
label_issue_new: New issue
label_issue_plural: Issues
label_issue_view_all: View all issues
+label_issues_by: Issues by %s
label_document: Document
label_document_new: New document
label_document_plural: Documents
diff --git a/lang/es.yml b/lang/es.yml
index 5f25de7c..d806d066 100644
--- a/lang/es.yml
+++ b/lang/es.yml
@@ -551,3 +551,4 @@ notice_account_pending: "Su cuenta ha sido creada y está pendiende de la aproba
setting_time_format: Formato de hora
setting_bcc_recipients: Blind carbon copy recipients (bcc)
button_annotate: Annotate
+label_issues_by: Issues by %s
diff --git a/lang/fr.yml b/lang/fr.yml
index 63f367f0..36ccc463 100644
--- a/lang/fr.yml
+++ b/lang/fr.yml
@@ -210,6 +210,7 @@ label_issue: Demande
label_issue_new: Nouvelle demande
label_issue_plural: Demandes
label_issue_view_all: Voir toutes les demandes
+label_issues_by: Demandes par %s
label_document: Document
label_document_new: Nouveau document
label_document_plural: Documents
diff --git a/lang/he.yml b/lang/he.yml
index 4ebbecf7..6be1a4c7 100644
--- a/lang/he.yml
+++ b/lang/he.yml
@@ -548,3 +548,4 @@ field_time_zone: Time zone
text_caracters_minimum: Must be at least %d characters long.
setting_bcc_recipients: Blind carbon copy recipients (bcc)
button_annotate: Annotate
+label_issues_by: Issues by %s
diff --git a/lang/it.yml b/lang/it.yml
index fa13513c..8a3e954f 100644
--- a/lang/it.yml
+++ b/lang/it.yml
@@ -548,3 +548,4 @@ field_time_zone: Time zone
text_caracters_minimum: Must be at least %d characters long.
setting_bcc_recipients: Blind carbon copy recipients (bcc)
button_annotate: Annotate
+label_issues_by: Issues by %s
diff --git a/lang/ja.yml b/lang/ja.yml
index d2f7d579..1ecfb1ae 100644
--- a/lang/ja.yml
+++ b/lang/ja.yml
@@ -549,3 +549,4 @@ field_time_zone: Time zone
text_caracters_minimum: Must be at least %d characters long.
setting_bcc_recipients: Blind carbon copy recipients (bcc)
button_annotate: Annotate
+label_issues_by: Issues by %s
diff --git a/lang/ko.yml b/lang/ko.yml
index dd50ce2b..ef081e62 100644
--- a/lang/ko.yml
+++ b/lang/ko.yml
@@ -548,3 +548,4 @@ field_time_zone: Time zone
text_caracters_minimum: Must be at least %d characters long.
setting_bcc_recipients: Blind carbon copy recipients (bcc)
button_annotate: Annotate
+label_issues_by: Issues by %s
diff --git a/lang/nl.yml b/lang/nl.yml
index 895feb5e..24a343eb 100644
--- a/lang/nl.yml
+++ b/lang/nl.yml
@@ -549,3 +549,4 @@ field_time_zone: Time zone
text_caracters_minimum: Must be at least %d characters long.
setting_bcc_recipients: Blind carbon copy recipients (bcc)
button_annotate: Annotate
+label_issues_by: Issues by %s
diff --git a/lang/pl.yml b/lang/pl.yml
index 381dc5e2..ff5f8d82 100644
--- a/lang/pl.yml
+++ b/lang/pl.yml
@@ -548,3 +548,4 @@ field_time_zone: Strefa czasowa
text_caracters_minimum: Must be at least %d characters long.
setting_bcc_recipients: Blind carbon copy recipients (bcc)
button_annotate: Annotate
+label_issues_by: Issues by %s
diff --git a/lang/pt-br.yml b/lang/pt-br.yml
index 4c4d862d..8c903edd 100644
--- a/lang/pt-br.yml
+++ b/lang/pt-br.yml
@@ -548,3 +548,4 @@ field_time_zone: Time zone
text_caracters_minimum: Must be at least %d characters long.
setting_bcc_recipients: Blind carbon copy recipients (bcc)
button_annotate: Annotate
+label_issues_by: Issues by %s
diff --git a/lang/pt.yml b/lang/pt.yml
index 1aabc8ac..10de07b5 100644
--- a/lang/pt.yml
+++ b/lang/pt.yml
@@ -548,3 +548,4 @@ field_time_zone: Time zone
text_caracters_minimum: Must be at least %d characters long.
setting_bcc_recipients: Blind carbon copy recipients (bcc)
button_annotate: Annotate
+label_issues_by: Issues by %s
diff --git a/lang/ro.yml b/lang/ro.yml
index 0c10c772..f7d3acd5 100644
--- a/lang/ro.yml
+++ b/lang/ro.yml
@@ -548,3 +548,4 @@ field_time_zone: Time zone
text_caracters_minimum: Must be at least %d characters long.
setting_bcc_recipients: Blind carbon copy recipients (bcc)
button_annotate: Annotate
+label_issues_by: Issues by %s
diff --git a/lang/ru.yml b/lang/ru.yml
index c6e27ca9..cad357c0 100644
--- a/lang/ru.yml
+++ b/lang/ru.yml
@@ -548,3 +548,4 @@ field_time_zone: Часовой пояс
text_caracters_minimum: Must be at least %d characters long.
setting_bcc_recipients: Blind carbon copy recipients (bcc)
button_annotate: Annotate
+label_issues_by: Issues by %s
diff --git a/lang/sr.yml b/lang/sr.yml
index 42aa3cc3..f9008d89 100644
--- a/lang/sr.yml
+++ b/lang/sr.yml
@@ -549,3 +549,4 @@ field_time_zone: Time zone
text_caracters_minimum: Must be at least %d characters long.
setting_bcc_recipients: Blind carbon copy recipients (bcc)
button_annotate: Annotate
+label_issues_by: Issues by %s
diff --git a/lang/sv.yml b/lang/sv.yml
index 7cbcc7a5..a4f55a17 100644
--- a/lang/sv.yml
+++ b/lang/sv.yml
@@ -549,3 +549,4 @@ field_time_zone: Time zone
text_caracters_minimum: Must be at least %d characters long.
setting_bcc_recipients: Blind carbon copy recipients (bcc)
button_annotate: Annotate
+label_issues_by: Issues by %s
diff --git a/lang/zh.yml b/lang/zh.yml
index ad18cecc..18fe3fda 100644
--- a/lang/zh.yml
+++ b/lang/zh.yml
@@ -551,3 +551,4 @@ field_time_zone: Time zone
text_caracters_minimum: Must be at least %d characters long.
setting_bcc_recipients: Blind carbon copy recipients (bcc)
button_annotate: Annotate
+label_issues_by: Issues by %s
diff --git a/lib/redmine.rb b/lib/redmine.rb
index 32239ce5..1f105343 100644
--- a/lib/redmine.rb
+++ b/lib/redmine.rb
@@ -27,6 +27,7 @@ Redmine::AccessControl.map do |map|
# Issues
map.permission :view_issues, {:projects => [:changelog, :roadmap],
:issues => [:index, :changes, :show, :context_menu],
+ :versions => [:show, :status_by],
:queries => :index,
:reports => :issue_report}, :public => true
map.permission :add_issues, {:projects => :add_issue}
diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css
index a10524db..f05fb9d9 100644
--- a/public/stylesheets/application.css
+++ b/public/stylesheets/application.css
@@ -267,6 +267,8 @@ table.progress td.open { background: #FFF none repeat scroll 0%; }
p.pourcent {font-size: 80%;}
p.progress-info {clear: left; font-style: italic; font-size: 80%;}
+div#status_by { margin-left: 16px; margin-bottom: 16px; }
+
/***** Tabs *****/
#content .tabs{height: 2.6em;}
#content .tabs ul{margin:0;}
diff --git a/test/functional/versions_controller_test.rb b/test/functional/versions_controller_test.rb
new file mode 100644
index 00000000..e8327938
--- /dev/null
+++ b/test/functional/versions_controller_test.rb
@@ -0,0 +1,42 @@
+# redMine - project management software
+# Copyright (C) 2006-2007 Jean-Philippe Lang
+#
+# 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 2
+# 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, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+require File.dirname(__FILE__) + '/../test_helper'
+require 'versions_controller'
+
+# Re-raise errors caught by the controller.
+class VersionsController; def rescue_action(e) raise e end; end
+
+class VersionsControllerTest < Test::Unit::TestCase
+ fixtures :projects, :versions, :users, :roles, :members, :enabled_modules
+
+ def setup
+ @controller = VersionsController.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ User.current = nil
+ end
+
+ def test_show
+ get :show, :id => 2
+ assert_response :success
+ assert_template 'show'
+ assert_not_nil assigns(:version)
+
+ assert_tag :tag => 'h2', :content => /1.0/
+ end
+end