Commit 331080bc authored by Timothy Andrew's avatar Timothy Andrew

Fetch cycle analytics data for a specific date range.

1. Supported date ranges are 30 / 90 days ago. The default is 90 days
   ago.

2. All issues created before "x days ago" are filtered out, even if they
   have other related data (test runs, merge requests) within the filter
   range.
parent ce6bcdd0
class Projects::CycleAnalyticsController < Projects::ApplicationController class Projects::CycleAnalyticsController < Projects::ApplicationController
def show def show
@cycle_analytics = CycleAnalytics.new(@project) @cycle_analytics = CycleAnalytics.new(@project, from: parse_start_date)
end
private
def parse_start_date
case cycle_analytics_params[:start_date]
when '30' then 30.days.ago
when '90' then 90.days.ago
else 90.days.ago
end
end
def cycle_analytics_params
return {} unless params[:cycle_analytics].present?
{ start_date: params[:cycle_analytics][:start_date] }
end end
end end
class CycleAnalytics class CycleAnalytics
def initialize(project) attr_reader :from
def initialize(project, from:)
@project = project @project = project
@from = from
end end
def issue def issue
calculate_metric(Queries::issues(@project), calculate_metric(Queries::issues(@project, created_after: @from),
-> (data_point) { data_point[:issue].created_at }, -> (data_point) { data_point[:issue].created_at },
[Queries::issue_first_associated_with_milestone_at, Queries::issue_first_added_to_list_label_at]) [Queries::issue_first_associated_with_milestone_at, Queries::issue_first_added_to_list_label_at])
end end
def plan def plan
calculate_metric(Queries::issues(@project), calculate_metric(Queries::issues(@project, created_after: @from),
[Queries::issue_first_associated_with_milestone_at, Queries::issue_first_added_to_list_label_at], [Queries::issue_first_associated_with_milestone_at, Queries::issue_first_added_to_list_label_at],
Queries::issue_closing_merge_request_opened_at) Queries::issue_closing_merge_request_opened_at)
end end
def code def code
calculate_metric(Queries::merge_requests_closing_issues(@project), calculate_metric(Queries::merge_requests_closing_issues(@project, created_after: @from),
-> (data_point) { data_point[:merge_request].created_at }, -> (data_point) { data_point[:merge_request].created_at },
[Queries::merge_request_first_assigned_to_user_other_than_author_at, Queries::merge_request_wip_flag_first_removed_at]) [Queries::merge_request_first_assigned_to_user_other_than_author_at, Queries::merge_request_wip_flag_first_removed_at])
end end
def test def test
calculate_metric(Queries::merge_requests_closing_issues(@project), calculate_metric(Queries::merge_requests_closing_issues(@project, created_after: @from),
Queries::merge_request_build_started_at, Queries::merge_request_build_started_at,
Queries::merge_request_build_finished_at) Queries::merge_request_build_finished_at)
end end
def review def review
calculate_metric(Queries::merge_requests_closing_issues(@project), calculate_metric(Queries::merge_requests_closing_issues(@project, created_after: @from),
[Queries::merge_request_first_assigned_to_user_other_than_author_at, Queries::merge_request_wip_flag_first_removed_at], [Queries::merge_request_first_assigned_to_user_other_than_author_at, Queries::merge_request_wip_flag_first_removed_at],
[Queries::merge_request_first_closed_at, Queries::merge_request_merged_at]) [Queries::merge_request_first_closed_at, Queries::merge_request_merged_at])
end end
def staging def staging
calculate_metric(Queries::merge_requests_closing_issues(@project), calculate_metric(Queries::merge_requests_closing_issues(@project, created_after: @from),
Queries::merge_request_merged_at, Queries::merge_request_merged_at,
Queries::merge_request_deployed_to_any_environment_at) Queries::merge_request_deployed_to_any_environment_at)
end end
def production def production
calculate_metric(Queries::merge_requests_closing_issues(@project), calculate_metric(Queries::merge_requests_closing_issues(@project, created_after: @from),
-> (data_point) { data_point[:issue].created_at }, -> (data_point) { data_point[:issue].created_at },
Queries::merge_request_deployed_to_production_at) Queries::merge_request_deployed_to_production_at)
end end
......
class CycleAnalytics class CycleAnalytics
module Queries module Queries
class << self class << self
def issues(project) def issues(project, created_after:)
project.issues.map { |issue| { issue: issue } } project.issues.where("created_at >= ?", created_after).map { |issue| { issue: issue } }
end end
def merge_requests_closing_issues(project) def merge_requests_closing_issues(project, options = {})
issues(project).map do |data_point| issues(project, options).map do |data_point|
merge_requests = data_point[:issue].closed_by_merge_requests(nil, check_if_open: false) merge_requests = data_point[:issue].closed_by_merge_requests(nil, check_if_open: false)
merge_requests.map { |merge_request| { issue: data_point[:issue], merge_request: merge_request } } merge_requests.map { |merge_request| { issue: data_point[:issue], merge_request: merge_request } }
end.flatten end.flatten
......
%h2 Cycle Analytics from #{@cycle_analytics.from} to Today
%ul.list-group %ul.list-group
%li.list-group-item %li.list-group-item
Issue: Issue:
......
...@@ -2,7 +2,8 @@ require 'spec_helper' ...@@ -2,7 +2,8 @@ require 'spec_helper'
describe 'CycleAnalytics#issue', models: true do describe 'CycleAnalytics#issue', models: true do
let(:project) { create(:project) } let(:project) { create(:project) }
subject { CycleAnalytics.new(project) } let(:from_date) { 10.days.ago }
subject { CycleAnalytics.new(project, from: from_date) }
context "when calculating the median of times between: context "when calculating the median of times between:
start: issue created_at start: issue created_at
...@@ -26,16 +27,6 @@ describe 'CycleAnalytics#issue', models: true do ...@@ -26,16 +27,6 @@ describe 'CycleAnalytics#issue', models: true do
median_start_time, median_end_time = start_and_end_times[2] median_start_time, median_end_time = start_and_end_times[2]
expect(subject.issue).to eq(median_end_time - median_start_time) expect(subject.issue).to eq(median_end_time - median_start_time)
end end
it "does not include issues from other projects" do
5.times do
milestone = create(:milestone, project: project)
issue = create(:issue)
issue.update(milestone: milestone)
end
expect(subject.issue).to be_nil
end
end end
context "when a label is added to the issue" do context "when a label is added to the issue" do
...@@ -80,6 +71,29 @@ describe 'CycleAnalytics#issue', models: true do ...@@ -80,6 +71,29 @@ describe 'CycleAnalytics#issue', models: true do
expect(subject.issue).to eq(milestone_add_time - start_time) expect(subject.issue).to eq(milestone_add_time - start_time)
end end
it "does not include issues from other projects" do
milestone = create(:milestone, project: project)
list_label = create(:label, lists: [create(:list)])
issue = create(:issue)
issue.update(milestone: milestone)
issue.update(label_ids: [list_label.id])
expect(subject.issue).to be_nil
end
it "excludes issues created before the 'from' date" do
before_from_date = from_date - 5.days
milestone = create(:milestone, project: project)
list_label = create(:label, lists: [create(:list)])
issue = Timecop.freeze(before_from_date) { create(:issue, project: project)}
issue.update(milestone: milestone)
issue.update(label_ids: [list_label.id])
expect(subject.issue).to be_nil
end
end end
end end
end end
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