Commit 7ffd96eb authored by Felipe Artur's avatar Felipe Artur

Improve burndown chart algorithm

Take issues created in the middle of a milestone into account
for burndown charts
parent 98fee756
......@@ -63,13 +63,6 @@ Since it only tracks when an issue was last closed (and not its full history), t
assumes that issue was open on days prior to that date. Reopened issues are
considered as open on one day after they were closed.
Note that with this design, if you create a new issue in the middle of the milestone period
(and assign the milestone to the issue), the Burndown Chart will appear as if the
issue was already open at the beginning of the milestone. A workaround is to simply
close the issue (so that a closed timestamp is stored in the system), and reopen
it to get the desired effect, with a rise in the chart appearing on the day after.
This is what appears in the example below.
The Burndown Chart can also be toggled to display the cumulative open issue
weight for a given day. When using this feature, make sure issue weights have
been properly assigned, since an open issue with no weight adds zero to the
......
class Burndown
include Gitlab::Utils::StrongMemoize
class Issue
attr_reader :closed_at, :weight, :state
......@@ -13,7 +15,7 @@ class Burndown
end
end
attr_reader :start_date, :due_date, :end_date, :issues_count, :issues_weight, :accurate, :legacy_data
attr_reader :start_date, :due_date, :end_date, :accurate, :legacy_data, :milestone
alias_method :accurate?, :accurate
alias_method :empty?, :legacy_data
......@@ -26,8 +28,6 @@ class Burndown
@accurate = milestone_issues.all?(&:closed_at)
@legacy_data = milestone_issues.any? && milestone_issues.none?(&:closed_at)
@issues_count, @issues_weight = milestone.issues.reorder(nil).pluck('COUNT(*), COALESCE(SUM(weight), 0)').first
end
# Returns the chart data in the following format:
......@@ -35,8 +35,8 @@ class Burndown
def as_json(opts = nil)
return [] unless valid?
open_issues_count = issues_count
open_issues_weight = issues_weight
open_issues_count = 0
open_issues_weight = 0
start_date.upto(end_date).each_with_object([]) do |date, chart_data|
closed, reopened = closed_and_reopened_issues_by(date)
......@@ -44,6 +44,10 @@ class Burndown
closed_issues_count = closed.count
closed_issues_weight = sum_issues_weight(closed)
issues_created = opened_issues_on(date)
open_issues_count += issues_created.count
open_issues_weight += sum_issues_weight(issues_created)
open_issues_count -= closed_issues_count
open_issues_weight -= closed_issues_weight
......@@ -63,6 +67,37 @@ class Burndown
private
def opened_issues_on(date)
return {} if opened_issues_grouped_by_date.empty?
date = date.to_date
# If issues.created_at < milestone.start_date
# we consider all of them created at milestone.start_date
if date == start_date
first_issue_created_at = opened_issues_grouped_by_date.keys.first
days_before_start_date = start_date.downto(first_issue_created_at).to_a
opened_issues_grouped_by_date.values_at(*days_before_start_date).flatten.compact
else
opened_issues_grouped_by_date[date] || []
end
end
def opened_issues_grouped_by_date
strong_memoize(:opened_issues_grouped_by_date) do
issues =
@milestone
.issues
.where('created_at <= ?', end_date)
.reorder(nil)
.order(:created_at).to_a
issues.group_by do |issue|
issue.created_at.to_date
end
end
end
def sum_issues_weight(issues)
issues.map(&:weight).compact.sum
end
......
---
title: Account for issues created in the middle of a milestone in burndown chart
merge_request:
author:
type: changed
......@@ -2,7 +2,6 @@ require 'spec_helper'
describe Burndown do
set(:user) { create(:user) }
let(:start_date) { "2017-03-01" }
let(:due_date) { "2017-03-03" }
......@@ -82,6 +81,23 @@ describe Burndown do
expect(burndown).not_to be_accurate
end
end
context 'when issues are created at the middle of the milestone' do
let(:creation_date) { "2017-03-02" }
it 'accounts for counts in issues created at the middle of the milestone' do
project = try(:group_project) || try(:project)
create(:issue, milestone: milestone, project: project, created_at: creation_date, weight: 2)
create(:issue, milestone: milestone, project: project, created_at: creation_date, weight: 3)
expect(subject).to eq([
['2017-03-01', 3, 6],
['2017-03-02', 6, 13],
['2017-03-03', 4, 9]
].to_json)
end
end
end
describe 'project milestone burndown' do
......@@ -93,7 +109,8 @@ describe Burndown do
milestone: milestone,
weight: 2,
project_id: project.id,
author: user
author: user,
created_at: milestone.start_date
}
end
let(:scope) { project }
......@@ -116,7 +133,8 @@ describe Burndown do
milestone: milestone,
weight: 2,
project_id: nested_group_project.id,
author: user
author: user,
created_at: milestone.start_date
}
end
let(:scope) { group }
......@@ -131,7 +149,8 @@ describe Burndown do
milestone: milestone,
weight: 2,
project_id: group_project.id,
author: user
author: user,
created_at: milestone.start_date
}
end
let(:scope) { group }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment