s.html_safe
end
+ # Renders descendants stats (total descendants (open - closed)) with query links
+ def render_descendants_stats(issue)
+ # Get issue descendants grouped by status type (open/closed) using a single query
+ subtasks_grouped = issue.descendants.visible.joins(:status).select(:is_closed, :id).group(:is_closed).reorder(:is_closed).count(:id)
+ # Cast keys to boolean in order to have consistent results between database types
+ subtasks_grouped.transform_keys! {|k| ActiveModel::Type::Boolean.new.cast(k)}
+
+ open_subtasks = subtasks_grouped[false].to_i
+ closed_subtasks = subtasks_grouped[true].to_i
+ all_subtasks = open_subtasks + closed_subtasks
+
+ return if all_subtasks == 0
+
+ all_block = content_tag(
+ 'span',
+ link_to(all_subtasks, issues_path(parent_id: "~#{issue.id}", set_filter: true, status_id: '*')),
+ class: 'badge badge-issues-count'
+ )
+
+ closed_block = content_tag(
+ 'span',
+ link_to_if(
+ closed_subtasks > 0,
+ l(:label_x_closed_issues_abbr, count: closed_subtasks),
+ issues_path(parent_id: "~#{issue.id}", set_filter: true, status_id: 'c')
+ ),
+ class: 'closed'
+ )
+
+ open_block = content_tag(
+ 'span',
+ link_to_if(
+ open_subtasks > 0,
+ l(:label_x_open_issues_abbr, :count => open_subtasks),
+ issues_path(:parent_id => "~#{issue.id}", :set_filter => true, :status_id => 'o')
+ ),
+ class: 'open'
+ )
+
+ content_tag(
+ 'span',
+ "#{all_block} (#{open_block} — #{closed_block})".html_safe,
+ :class => 'issues-stat'
+ )
+ end
+
# Renders the list of related issues on the issue details view
def render_issue_relations(issue, relations)
manage_relations = User.current.allowed_to?(:manage_issue_relations, issue.project)
<%= link_to_new_subtask(@issue) if User.current.allowed_to?(:manage_subtasks, @project) %>
</div>
<p>
-<strong><%=l(:label_subtask_plural)%></strong>
-<% if !@issue.leaf? %>
- (<%= link_to(l(:label_x_issues, :count => @issue.descendants.count),
- issues_path(:parent_id => "~#{@issue.id}", :set_filter => true, :status_id => '*')) %>
- : <%= link_to_if( @issue.descendants.select(&:closed?).count > 0,
- l(:label_x_closed_issues_abbr, :count => @issue.descendants.select(&:closed?).count ),
- issues_path(:parent_id => "~#{@issue.id}", :set_filter => true, :status_id => 'c')) %>
- —
- <%= link_to_if( @issue.descendants.open.count > 0,
- l(:label_x_open_issues_abbr, :count => @issue.descendants.open.count ),
- issues_path(:parent_id => "~#{@issue.id}", :set_filter => true, :status_id => 'o')) %>)
-<% end %>
+ <strong><%=l(:label_subtask_plural)%></strong>
+ <%= render_descendants_stats(@issue) unless @issue.leaf? %>
</p>
<%= form_tag({}, :data => {:cm_url => issues_context_menu_path}) do %>
<%= render_descendants_tree(@issue) unless @issue.leaf? %>
#issue_tree td.checkbox, #relations td.checkbox {display:none;}
#issue_tree td.subject, #relations td.subject {width: 50%;}
#issue_tree td.buttons, #relations td.buttons {padding:0;}
+#issue_tree .issues-stat {font-size: 80%}
+#issue_tree .issues-stat .badge {bottom: initial;}
#trackers_description {display:none;}
#trackers_description dt {font-weight: bold; text-decoration: underline;}
color: #1D781D;
border: 1px solid #1D781D;
}
+.badge-issues-count {
+ background: #EEEEEE;
+}
+
/***** Tooltips *****/
.ui-tooltip {
background: #000;
end
end
+ def test_show_should_show_subtasks_stats
+ @request.session[:user_id] = 1
+ child1 = Issue.generate!(parent_issue_id: 1, subject: 'Open child issue')
+ Issue.generate!(parent_issue_id: 1, subject: 'Closed child issue', status_id: 5)
+ Issue.generate!(parent_issue_id: child1.id, subject: 'Open child of child')
+ # Issue not visible for anonymous
+ Issue.generate!(parent_issue_id: 1, subject: 'Private child', project_id: 5)
+
+ get(:show, params: {:id => 1})
+ assert_response :success
+
+ assert_select 'div#issue_tree span.issues-stat' do
+ assert_select 'span.badge', text: '4'
+ assert_select 'span.open a', text: '3 open'
+ assert_equal CGI.unescape(css_select('span.open a').first.attr('href')),
+ "/issues?parent_id=~1&set_filter=true&status_id=o"
+
+ assert_select 'span.closed a', text: '1 closed'
+ assert_equal CGI.unescape(css_select('span.closed a').first.attr('href')),
+ "/issues?parent_id=~1&set_filter=true&status_id=c"
+ end
+ end
+
+ def test_show_subtasks_stats_should_not_link_if_issue_has_zero_open_or_closed_subtasks
+ child1 = Issue.generate!(parent_issue_id: 1, subject: 'Open child issue')
+
+ get(:show, params: {:id => 1})
+ assert_response :success
+
+ assert_select 'div#issue_tree span.issues-stat' do
+ assert_select 'span.open a', text: '1 open'
+ assert_equal CGI.unescape(css_select('span.open a').first.attr('href')),
+ "/issues?parent_id=~1&set_filter=true&status_id=o"
+ assert_select 'span.closed', text: '0 closed'
+ assert_select 'span.closed a', 0
+ end
+ end
+
+ def test_show_should_not_show_subtasks_stats_if_subtasks_are_not_visible
+ # Issue not visible for anonymous
+ Issue.generate!(parent_issue_id: 1, subject: 'Private child', project_id: 5)
+
+ get(:show, params: {:id => 1})
+ assert_response :success
+
+ assert_select 'div#issue_tree span.issues-stat', 0
+ end
+
def test_show_should_list_parents
issue = Issue.
create!(