From 331080bca683fdab73520f68c53f6a5367d17f22 Mon Sep 17 00:00:00 2001
From: Timothy Andrew <mail@timothyandrew.net>
Date: Fri, 26 Aug 2016 16:18:52 +0530
Subject: [PATCH] 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.
---
 .../projects/cycle_analytics_controller.rb    | 18 +++++++++-
 app/models/cycle_analytics.rb                 | 19 +++++-----
 app/models/cycle_analytics/queries.rb         |  8 ++---
 .../projects/cycle_analytics/show.html.haml   |  2 ++
 spec/models/cycle_analytics/issue_spec.rb     | 36 +++++++++++++------
 5 files changed, 59 insertions(+), 24 deletions(-)

diff --git a/app/controllers/projects/cycle_analytics_controller.rb b/app/controllers/projects/cycle_analytics_controller.rb
index 85c81cba511..002a71b593d 100644
--- a/app/controllers/projects/cycle_analytics_controller.rb
+++ b/app/controllers/projects/cycle_analytics_controller.rb
@@ -1,5 +1,21 @@
 class Projects::CycleAnalyticsController < Projects::ApplicationController
   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
diff --git a/app/models/cycle_analytics.rb b/app/models/cycle_analytics.rb
index 331bb450e08..d6a0b5fcc7a 100644
--- a/app/models/cycle_analytics.rb
+++ b/app/models/cycle_analytics.rb
@@ -1,46 +1,49 @@
 class CycleAnalytics
-  def initialize(project)
+  attr_reader :from
+
+  def initialize(project, from:)
     @project = project
+    @from = from
   end
 
   def issue
-    calculate_metric(Queries::issues(@project),
+    calculate_metric(Queries::issues(@project, created_after: @from),
                      -> (data_point) { data_point[:issue].created_at },
                      [Queries::issue_first_associated_with_milestone_at, Queries::issue_first_added_to_list_label_at])
   end
 
   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_closing_merge_request_opened_at)
   end
 
   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 },
                      [Queries::merge_request_first_assigned_to_user_other_than_author_at, Queries::merge_request_wip_flag_first_removed_at])
   end
 
   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_finished_at)
   end
 
   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_closed_at, Queries::merge_request_merged_at])
   end
 
   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_deployed_to_any_environment_at)
   end
 
   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 },
                      Queries::merge_request_deployed_to_production_at)
   end
diff --git a/app/models/cycle_analytics/queries.rb b/app/models/cycle_analytics/queries.rb
index 122b2599bd3..1a9a1daa2b0 100644
--- a/app/models/cycle_analytics/queries.rb
+++ b/app/models/cycle_analytics/queries.rb
@@ -1,12 +1,12 @@
 class CycleAnalytics
   module Queries
     class << self
-      def issues(project)
-        project.issues.map { |issue| { issue: issue } }
+      def issues(project, created_after:)
+        project.issues.where("created_at >= ?", created_after).map { |issue| { issue: issue } }
       end
 
-      def merge_requests_closing_issues(project)
-        issues(project).map do |data_point|
+      def merge_requests_closing_issues(project, options = {})
+        issues(project, options).map do |data_point|
           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 } }
         end.flatten
diff --git a/app/views/projects/cycle_analytics/show.html.haml b/app/views/projects/cycle_analytics/show.html.haml
index 160c8eaca72..50ac9904445 100644
--- a/app/views/projects/cycle_analytics/show.html.haml
+++ b/app/views/projects/cycle_analytics/show.html.haml
@@ -1,3 +1,5 @@
+%h2 Cycle Analytics from #{@cycle_analytics.from} to Today
+
 %ul.list-group
   %li.list-group-item
     Issue:
diff --git a/spec/models/cycle_analytics/issue_spec.rb b/spec/models/cycle_analytics/issue_spec.rb
index 6fdce020642..4dc7f62af98 100644
--- a/spec/models/cycle_analytics/issue_spec.rb
+++ b/spec/models/cycle_analytics/issue_spec.rb
@@ -2,7 +2,8 @@ require 'spec_helper'
 
 describe 'CycleAnalytics#issue', models: true do
   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:
                start: issue created_at
@@ -26,16 +27,6 @@ describe 'CycleAnalytics#issue', models: true do
         median_start_time, median_end_time = start_and_end_times[2]
         expect(subject.issue).to eq(median_end_time - median_start_time)
       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
 
     context "when a label is added to the issue" do
@@ -80,6 +71,29 @@ describe 'CycleAnalytics#issue', models: true do
 
         expect(subject.issue).to eq(milestone_add_time - start_time)
       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
-- 
GitLab