diff --git a/app/helpers/versions_helper.rb b/app/helpers/versions_helper.rb index ca93abd53..bf15d425e 100644 --- a/app/helpers/versions_helper.rb +++ b/app/helpers/versions_helper.rb @@ -73,4 +73,27 @@ module VersionsHelper def status_by_options_for_select(value) options_for_select(STATUS_BY_CRITERIAS.collect {|criteria| [l("field_#{criteria}".to_sym), criteria]}, value) end + + def version_burnup_chart_data(version) + return nil if version.visible_fixed_issues.empty? + chart_start_date = (version.start_date || version.created_on).to_date + chart_end_date = [version.due_date, version.visible_fixed_issues.maximum(:due_date), version.visible_fixed_issues.maximum(:updated_on)].compact.max.to_date + line_end_date = [User.current.today, chart_end_date].min + step_size = ((chart_start_date..chart_end_date).count.to_f / 90).ceil + return nil if step_size < 1 + reported = line_end_date.step(chart_start_date, -step_size).collect{|d| {:t => d.to_s, :y => version.visible_fixed_issues.where("#{Issue.table_name}.created_on<=?", d.end_of_day).count}} + closed = line_end_date.step(chart_start_date, -step_size).collect{|d| {:t => d.to_s, :y => version.visible_fixed_issues.open(false).where("#{Issue.table_name}.closed_on<=?", d.end_of_day).count}} + chart_data = { + :labels => chart_end_date.step(chart_start_date, -step_size).collect{|d|d.to_s}, + :datasets => [ + {:label => l(:label_reported_issues), :data => reported, + :borderColor => "rgba(128, 128, 128, 1)", :backgroundColor => "rgba(128, 128, 128, 0.1)", :pointBackgroundColor => "rgba(128, 128, 128, 1)", + :lineTension => 0, :borderWidth => 2}, + {:label => l(:label_closed_issues), :data => closed, + :borderColor => "rgba(186, 224, 186, 1)", :backgroundColor => "rgba(186, 224, 186, 0.1)", :pointBackgroundColor => "rgba(186, 224, 186, 1)", + :lineTension => 0, :borderWidth => 2} + ] + } + return chart_data + end end diff --git a/app/views/versions/show.html.erb b/app/views/versions/show.html.erb index e1b27ee0d..c2a1e00ed 100644 --- a/app/views/versions/show.html.erb +++ b/app/views/versions/show.html.erb @@ -51,6 +51,9 @@ <% end %> <% end %> +
+ +
<%= context_menu %> <% end %> @@ -58,3 +61,45 @@ <%= call_hook :view_versions_show_bottom, :version => @version %> <% html_title @version.name %> +<% if chart_data = version_burnup_chart_data(@version) %> + <%= javascript_tag do %> + function renderChart(canvas_id, title, chartData){ + new Chart($(canvas_id), { + type: 'line', + data: chartData, + options: { + responsive: true, + legend: { + position: 'right', + labels: { boxWidth: 20, fontSize: 10, padding: 10 } + }, + title: { display: true, text: title }, + tooltips: { + callbacks: { + title: function(tooltipItem, data) { return '' } + } + }, + scales: { + xAxes: [{ + type: "time", + time: { unit: "day", displayFormats: { day: 'YYYY-MM-DD' } }, + gridLines: { borderDash: [6, 4] }, + ticks: { source: 'labels', autoSkip: true } + }], + yAxes: [{ + gridLines: { borderDash: [6, 4] }, + ticks: { min: 0, max: <%= @version.fixed_issues.count + 1 %>, stepSize: 1 } + }] + } + } + }); + } + $(document).ready(function(){ + var chartData = <%= chart_data.to_json.html_safe %>; + renderChart("#version_chart", "Issues burnup", chartData); + }); + <% end %> + <% content_for :header_tags do %> + <%= javascript_include_tag "Chart.bundle.min" %> + <% end %> +<% end %> diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css index a4c77d118..66ad444d3 100644 --- a/public/stylesheets/application.css +++ b/public/stylesheets/application.css @@ -585,6 +585,7 @@ div#roadmap .wiki h1 { font-size: 120%; } div#roadmap .wiki h2 { font-size: 110%; } div#roadmap h2, div#roadmap h3 { display: inline; padding-right: 0;} body.controller-versions.action-show div#roadmap .related-issues {width:70%;} +body.controller-versions.action-show div#roadmap .version-report-graph { width: 70%; margin: 2em 0 } div#version-summary { float:right; width:28%; margin-left: 16px; margin-bottom: 16px; background-color: #fff; } div#version-summary fieldset { margin-bottom: 1em; } diff --git a/public/stylesheets/responsive.css b/public/stylesheets/responsive.css index aa5502ee6..76a39a6e8 100644 --- a/public/stylesheets/responsive.css +++ b/public/stylesheets/responsive.css @@ -679,6 +679,7 @@ .version-overview table.progress {width:75%;} div#version-summary {float:none; width:100%; margin-left:0;} body.controller-versions.action-show div#roadmap .related-issues {width:100%;} + body.controller-versions.action-show div#roadmap .version-report-graph {width:100%;} /* History and Changeset */ div#issue-changesets { diff --git a/test/helpers/version_helper_test.rb b/test/helpers/version_helper_test.rb index e1c83e225..e43846c5c 100644 --- a/test/helpers/version_helper_test.rb +++ b/test/helpers/version_helper_test.rb @@ -53,4 +53,41 @@ class VersionsHelperTest < Redmine::HelperTest version.project = Project.find(5) assert_match /^\/issues\?/, version_filtered_issues_path(version) end + + def test_version_burnup_chart_data_should_return_chart_data + User.any_instance.stubs(:today).returns(3.days.after.to_date) + version = Version.create!(:project => Project.find(1), :name => 'test', :due_date => 5.days.after) + issue = Issue.create!(:project => version.project, :fixed_version => version, + :tracker => version.project.trackers.first, :subject => 'text', :author => User.current, + :start_date => 1.days.after) + chart_data = { + :labels => [5.days.after.to_date.to_s, 4.days.after.to_date.to_s, 3.days.after.to_date.to_s, 2.days.after.to_date.to_s, 1.days.after.to_date.to_s], + :datasets => [ + {:label => l(:label_reported_issues), + :data => [{:t => 3.days.after.to_date.to_s, :y => 1}, {:t => 2.days.after.to_date.to_s, :y => 1}, {:t => 1.days.after.to_date.to_s, :y => 1}], + :borderColor => "rgba(128, 128, 128, 1)", :backgroundColor => "rgba(128, 128, 128, 0.1)", :pointBackgroundColor => "rgba(128, 128, 128, 1)", + :lineTension => 0, :borderWidth => 2}, + {:label => l(:label_closed_issues), + :data => [{:t => 3.days.after.to_date.to_s, :y => 0}, {:t => 2.days.after.to_date.to_s, :y => 0}, {:t => 1.days.after.to_date.to_s, :y => 0}], + :borderColor => "rgba(186, 224, 186, 1)", :backgroundColor => "rgba(186, 224, 186, 0.1)", :pointBackgroundColor => "rgba(186, 224, 186, 1)", + :lineTension => 0, :borderWidth => 2} + ] + } + assert_equal chart_data, version_burnup_chart_data(version) + end + + def test_version_burnup_chart_data_should_return_nil_when_visible_fixed_issues_empty + version = Version.create!(:project => Project.find(1), :name => 'test') + version.visible_fixed_issues.destroy_all + assert_empty version.visible_fixed_issues + assert_nil version_burnup_chart_data(version) + end + + def test_version_burnup_chart_data_should_return_nil_when_order_of_start_date_and_due_date_is_reversed + version = Version.create!(:project => Project.find(1), :name => 'test', :due_date => 10.days.after) + issue = Issue.create!(:project => version.project, :fixed_version => version, + :tracker => version.project.trackers.first, :subject => 'text', :author => User.current, + :start_date => 11.days.after) + assert_nil version_burnup_chart_data(version) + end end