Feature #34830 » issues-burndown-in-version-detail.patch
| 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 | 
- « Previous
- 1
- 2
- Next »