Commit f446ebc8 authored by Mike Greiling's avatar Mike Greiling Committed by Mike Greiling

Merge branch 'issue_91' into '91-milestone-burndown-charts'

Burndown charts (backend component)

See merge request !1466
parents c88b3a8f 226202e0
...@@ -42,6 +42,7 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -42,6 +42,7 @@ class Projects::MilestonesController < Projects::ApplicationController
end end
def show def show
@burndown = Burndown.new(@milestone)
end end
def create def create
......
class Burndown
attr_accessor :start_date, :end_date, :issues_count, :issues_weight
def initialize(milestone)
@milestone = milestone
@start_date = @milestone.start_date
@end_date = @milestone.due_date
issues = @milestone.issues
@issues_count = issues.count
@issues_weight = issues.sum(:weight)
end
# Returns the chart data in the following format:
# [date, issue count, issue weight] eg: [["2017-03-01", 33, 127], ["2017-03-02", 35, 73], ["2017-03-03", 28, 50]...]
def chart_data
return [] unless @start_date && @end_date
open_count = @issues_count
open_weight = @issues_weight
@start_date.upto(@end_date).each_with_object([]) do |date, chart_data|
closed, reopened = opened_and_closed_issues_by(date)
closed_count, closed_weight = count_and_weight_of(closed)
chart_data << [date.strftime("%Y-%m-%d"), open_count -= closed_count, open_weight -= closed_weight]
reopened_count, reopened_weight = count_and_weight_of(reopened)
open_count += reopened_count
open_weight += reopened_weight
end
end
private
def count_and_weight_of(issues)
weight = issues.map{ |i| i.weight }.compact.reduce(:+)
return issues.count, weight || 0
end
def opened_and_closed_issues_by(date)
current_date = date.beginning_of_day
closed = issues.select { |issue| issue.closed_at.beginning_of_day.to_i == current_date.to_i }
reopened = closed.select { |issue| issue.state == 'reopened' }
return closed, reopened
end
def issues
@issues ||=
@milestone.issues.select('closed_at, weight, state').
where('closed_at IS NOT NULL').
order('closed_at ASC')
end
end
...@@ -67,10 +67,6 @@ class Issue < ActiveRecord::Base ...@@ -67,10 +67,6 @@ class Issue < ActiveRecord::Base
before_transition any => :closed do |issue| before_transition any => :closed do |issue|
issue.closed_at = Time.zone.now issue.closed_at = Time.zone.now
end end
before_transition closed: any do |issue|
issue.closed_at = nil
end
end end
def hook_attrs def hook_attrs
......
---
title: Add burndown chart to milestones
merge_request:
author:
require './spec/support/sidekiq'
require './spec/support/test_env'
class Gitlab::Seeder::Burndown
def initialize(project, perf: false)
@project = project
end
def seed!
Timecop.travel 10.days.ago
Sidekiq::Testing.inline! do
create_milestone
puts '.'
create_issues
puts '.'
close_issues
puts '.'
reopen_issues
puts '.'
end
Timecop.return
print '.'
end
private
def create_milestone
milestone_params = {
title: "Sprint - #{FFaker::Lorem.sentence}",
description: FFaker::Lorem.sentence,
state: 'active',
start_date: Date.today,
due_date: rand(5..10).days.from_now
}
@milestone = Milestones::CreateService.new(@project, @project.team.users.sample, milestone_params).execute
end
def create_issues
20.times do
issue_params = {
title: FFaker::Lorem.sentence(6),
description: FFaker::Lorem.sentence,
state: 'opened',
milestone: @milestone,
assignee: @project.team.users.sample,
weight: rand(1..9)
}
Issues::CreateService.new(@project, @project.team.users.sample, issue_params).execute
end
end
def close_issues
@milestone.start_date.upto(@milestone.due_date) do |date|
Timecop.travel(date)
close_number = rand(0..2)
open_issues = @milestone.issues.opened
open_issues = open_issues.slice(0..close_number)
open_issues.each do |issue|
Issues::CloseService.new(@project, @project.team.users.sample, {}).execute(issue)
end
end
Timecop.return
end
def reopen_issues
count = @milestone.issues.closed.count / 3
issues = @milestone.issues.closed.slice(0..rand(count))
issues.each { |i| i.update(state: 'reopened') }
end
end
Gitlab::Seeder.quiet do
if project_id = ENV['PROJECT_ID']
project = Project.find(project_id)
seeder = Gitlab::Seeder::Burndown.new(project)
seeder.seed!
else
Project.all.each do |project|
seeder = Gitlab::Seeder::Burndown.new(project)
seeder.seed!
end
end
end
require 'spec_helper'
describe Burndown, models: true do
let(:start_date) { "2017-03-01" }
let(:due_date) { "2017-03-05" }
let(:milestone) { create(:milestone, start_date: start_date, due_date: due_date) }
let(:project) { milestone.project }
let(:user) { create(:user) }
let(:issue_params) do
{
title: FFaker::Lorem.sentence(6),
description: FFaker::Lorem.sentence,
state: 'opened',
milestone: milestone,
weight: 2
}
end
before do
project.add_master(user)
build_sample
end
after do
Timecop.return
end
subject { described_class.new(milestone).chart_data }
it "generates an array with date, issue count and weight" do
expect(subject).to eq([
["2017-03-01", 33, 66],
["2017-03-02", 35, 70],
["2017-03-03", 28, 56],
["2017-03-04", 32, 64],
["2017-03-05", 21, 42]
])
end
it "returns empty array if milestone start date is nil" do
milestone.update(start_date: nil)
expect(subject).to eq([])
end
it "returns empty array if milestone due date is nil" do
milestone.update(due_date: nil)
expect(subject).to eq([])
end
# Creates, closes and reopens issues only for odd days numbers
def build_sample
milestone.start_date.upto(milestone.due_date) do |date|
day = date.day
next if day.even?
count = day * 4
issues = []
Timecop.travel(date)
# Create issues
count.times { issues << Issues::CreateService.new(project, user, issue_params).execute }
# Close issues
closed = issues.slice(0..count / 2)
closed.each { |i| Issues::CloseService.new(project, user, {}).execute(i) }
# Reopen issues
closed.slice(0..count / 4).each { |i| i.reopen }
end
Timecop.travel(due_date)
end
end
...@@ -51,14 +51,6 @@ describe Issue, models: true do ...@@ -51,14 +51,6 @@ describe Issue, models: true do
expect(issue.closed_at).to eq(now) expect(issue.closed_at).to eq(now)
end end
it 'sets closed_at to nil when issue is reopened' do
issue = create(:issue, state: 'closed')
issue.reopen
expect(issue.closed_at).to be_nil
end
end end
describe '#to_reference' do describe '#to_reference' do
......
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