Project

General

Profile

Feature #34830 » issues-burndown-in-version-detail.patch

Takenori TAKAKI, 2021-03-03 08:42

View differences:

app/helpers/versions_helper.rb
104 104
      end
105 105
    end
106 106
  end
107

  
108
  def issues_burndown_chart_data(version)
109
    return nil if version.visible_fixed_issues.empty?
110
    chart_start_date = (version.start_date || version.created_on).to_date
111
    chart_end_date = [version.due_date, version.visible_fixed_issues.maximum(:due_date), version.visible_fixed_issues.maximum(:updated_on)].compact.max.to_date
112
    line_end_date = [User.current.today, chart_end_date].min
113
    step_size = ((chart_start_date..chart_end_date).count.to_f / 90).ceil
114
    issues = version.visible_fixed_issues
115
    return nil if step_size < 1
116
    total_closed = chart_start_date.step(line_end_date, step_size).collect{|d| {:t => d.to_s, :y => issues.where("#{Issue.table_name}.closed_on IS NULL OR #{Issue.table_name}.closed_on>=?", d.end_of_day).count}}
117
    open = chart_start_date.step(line_end_date, step_size).collect{|d| {:t => d.to_s, :y => issues.where("#{Issue.table_name}.created_on<=?", d.end_of_day).count - issues.open(false).where("#{Issue.table_name}.closed_on<=?", d.end_of_day).count}}
118
    chart_data = {
119
      :labels => chart_start_date.step(chart_end_date, step_size).collect{|d|d.to_s},
120
      :datasets => [
121
        {:label => l(:label_ideal), :data => [{:t => chart_start_date.to_s, :y => total_closed.first[:y]}, {:t => chart_end_date.to_s, :y => 0}],
122
         :backgroundColor => "rgba(0, 0, 0, 0)",
123
         :lineTension => 0, :borderWidth => 2, :borderDash => [5, 2], :spanGaps => true},
124
        {:label => l(:label_total_substract_closed), :data => total_closed,
125
         :borderColor => "rgba(186, 224, 186, 1)", :backgroundColor => "rgba(0, 0, 0, 0)", :pointBackgroundColor => "rgba(186, 224, 186, 1)",
126
         :lineTension => 0, :borderWidth => 2, :borderDash => [5, 2]},
127
        {:label => l(:label_open), :data => open,
128
         :borderColor => "rgba(186, 224, 186, 1)", :backgroundColor => "rgba(186, 224, 186, 0.1)", :pointBackgroundColor => "rgba(186, 224, 186, 1)",
129
         :lineTension => 0, :borderWidth => 2}
130
      ]
131
    }
132
    return chart_data
133
  end
107 134
end
app/views/versions/show.html.erb
52 52
  <% end %>
53 53
  </table>
54 54
<% end %>
55
<div class="version-report-graph">
56
  <canvas id="version_chart"></canvas>
57
</div>
55 58
<%= context_menu %>
56 59
<% end %>
57 60
</div>
......
59 62
<%= call_hook :view_versions_show_bottom, :version => @version %>
60 63

  
61 64
<% html_title @version.name %>
65
<% if chart_data = issues_burndown_chart_data(@version) %>
66
  <%= javascript_tag do %>
67
  function renderChart(canvas_id, title, y_label, chartData){
68
    new Chart($(canvas_id), {
69
      type: 'line',
70
      data: chartData,
71
      options: {
72
        responsive: true,
73
        legend: {
74
          position: 'right',
75
          labels: { boxWidth: 20, fontSize: 10, padding: 10 }
76
        },
77
        title: { display: true, text: title },
78
        tooltips: {
79
          callbacks: {
80
            title: function(tooltipItem, data) { return '' }
81
          }
82
        },
83
        scales: {
84
          xAxes: [{
85
            type: "time",
86
            time: { unit: "day", displayFormats: { day: 'YYYY-MM-DD' } },
87
            gridLines: { borderDash: [6, 4] },
88
            ticks: { source: 'labels', autoSkip: true }
89
          }],
90
          yAxes: [{
91
            scaleLabel: { display: true, labelString: y_label },
92
            gridLines: { borderDash: [6, 4] },
93
            ticks: { min: 0, max: <%= @version.fixed_issues.count %>, stepSize: 1 }
94
          }]
95
        }
96
      }
97
    });
98
  }
99
  $(document).ready(function(){
100
    var title = "<%= l(:label_issues_burndown) %>";
101
    var y_label = "<%= l(:label_issues_burndown_y_label) %>";
102
    var chartData = <%= chart_data.to_json.html_safe %>;
103
    renderChart("#version_chart", title, y_label, chartData);
104
  });
105
  <% end %>
106
  <% content_for :header_tags do %>
107
    <%= javascript_include_tag "Chart.bundle.min" %>
108
  <% end %>
109
<% end %>
config/locales/en.yml
731 731
  label_total: Total
732 732
  label_total_plural: Totals
733 733
  label_total_time: Total time
734
  label_issues_burndown: burndown
735
  label_issues_burndown_y_label: Number of Issues
736
  label_ideal: Ideal
737
  label_total_substract_closed: Total - Closed
738
  label_open: Open
734 739
  label_permissions: Permissions
735 740
  label_current_status: Current status
736 741
  label_new_statuses_allowed: New statuses allowed
public/stylesheets/application.css
630 630
div#roadmap .wiki h2 { font-size: 110%; }
631 631
div#roadmap h2, div#roadmap h3 {padding-right: 0;}
632 632
body.controller-versions.action-show div#roadmap .related-issues {width:70%;}
633
body.controller-versions.action-show div#roadmap .version-report-graph { width: 70%; margin: 2em 0 }
633 634

  
634 635
div#version-summary { float:right; width:28%; margin-left: 16px; margin-bottom: 16px; background-color: #fff; }
635 636
div#version-summary fieldset { margin-bottom: 1em; }
public/stylesheets/responsive.css
666 666
  .version-overview table.progress {width:75%;}
667 667
  div#version-summary {float:none; width:100%; margin-left:0;}
668 668
  body.controller-versions.action-show div#roadmap .related-issues {width:100%;}
669
  body.controller-versions.action-show div#roadmap .version-report-graph {width:100%;}
669 670

  
670 671
  /* History and Changeset */
671 672
  div#issue-changesets {
test/helpers/version_helper_test.rb
25 25
  fixtures :projects, :versions, :enabled_modules,
26 26
           :users, :members, :roles, :member_roles,
27 27
           :trackers, :projects_trackers,
28
           :enumerations,
28 29
           :issue_statuses
29 30

  
30 31
  def test_version_filtered_issues_path_sharing_none
......
120 121
    # href should contain param tracker_id=2 because for tracker_id 1, user has only readonly permissions on fixed_version_id
121 122
    assert_select_in link_to_new_issue(version, project), '[href=?]', '/projects/ecookbook/issues/new?back_url=%2Fversions%2F3&issue%5Bfixed_version_id%5D=3&issue%5Btracker_id%5D=2'
122 123
  end
124

  
125
  def test_issues_burndown_chart_data_should_return_chart_data
126
    User.any_instance.stubs(:today).returns(3.days.after.to_date)
127
    version = Version.create!(:project => Project.find(1), :name => 'test', :due_date => 5.days.after.to_date)
128
    issue = Issue.create!(:project => version.project, :fixed_version => version,
129
                          :priority => IssuePriority.find_by_name('Normal'),
130
                          :tracker => version.project.trackers.first, :subject => 'text', :author => User.current,
131
                          :start_date => 1.days.after.to_date)
132
    chart_data = {
133
      :labels => (1.days.after.to_date..5.days.after.to_date).map { |d| d.to_s },
134
      :datasets => [
135
        {:label => l(:label_ideal), :data => [{:t => 1.days.after.to_date.to_s, :y => 1}, {:t => 5.days.after.to_date.to_s, :y => 0}],
136
         :backgroundColor => "rgba(0, 0, 0, 0)",
137
         :lineTension => 0, :borderWidth => 2, :borderDash => [5, 2], :spanGaps => true},
138
         {:label => l(:label_total_substract_closed), :data => [{:t => 1.days.after.to_date.to_s, :y => 1}, {:t => 2.days.after.to_date.to_s, :y => 1}, {:t => 3.days.after.to_date.to_s, :y => 1}],
139
         :borderColor => "rgba(186, 224, 186, 1)", :backgroundColor => "rgba(0, 0, 0, 0)", :pointBackgroundColor => "rgba(186, 224, 186, 1)",
140
         :lineTension => 0, :borderWidth => 2, :borderDash => [5, 2]},
141
         {:label => l(:label_open), :data => [{:t => 1.days.after.to_date.to_s, :y => 1}, {:t => 2.days.after.to_date.to_s, :y => 1}, {:t => 3.days.after.to_date.to_s, :y => 1}],
142
         :borderColor => "rgba(186, 224, 186, 1)", :backgroundColor => "rgba(186, 224, 186, 0.1)", :pointBackgroundColor => "rgba(186, 224, 186, 1)",
143
         :lineTension => 0, :borderWidth => 2}
144
      ]
145
    }
146
    assert_equal chart_data, issues_burndown_chart_data(version)
147
  end
148

  
149
  def test_issues_burndown_chart_data_should_return_nil_when_visible_fixed_issues_empty
150
    version = Version.create!(:project => Project.find(1), :name => 'test')
151
    version.visible_fixed_issues.destroy_all
152
    assert_empty version.visible_fixed_issues
153
    assert_nil issues_burndown_chart_data(version)
154
  end
155

  
156
  def test_issues_burndown_chart_data_should_return_nil_when_order_of_start_date_and_due_date_is_reversed
157
    version = Version.create!(:project => Project.find(1), :name => 'test', :due_date => 10.days.after)
158
    issue = Issue.create!(:project => version.project, :fixed_version => version,
159
                          :priority => IssuePriority.find_by_name('Normal'),
160
                          :tracker => version.project.trackers.first, :subject => 'text', :author => User.current,
161
                          :start_date => 11.days.after)
162
    assert_nil issues_burndown_chart_data(version)
163
  end
123 164
end
(2-2/2)