From 918e589c2b29c18d9fe3a8e6c93a3f490c86beb1 Mon Sep 17 00:00:00 2001
From: Timothy Andrew <mail@timothyandrew.net>
Date: Wed, 21 Sep 2016 00:47:37 +0530
Subject: [PATCH] Implement a second round of review comments from @DouweM.

- Don't use `TableReferences` - using `.arel_table` is shorter!
- Move some database-related code to `Gitlab::Database`
- Remove the `MergeRequest#issues_closed` and
  `Issue#closed_by_merge_requests`  associations. They were either
  shadowing or were too similar to existing methods. They are not being
  used anywhere, so it's better to remove them to reduce confusion.
- Use Rails 3-style validations
- Index for `MergeRequest::Metrics#first_deployed_to_production_at`
- Only include `CycleAnalyticsHelpers::TestGeneration` for specs that
  need it.
- Other minor refactorings.
---
 app/models/cycle_analytics.rb                 | 66 ++++++++-----------
 app/models/cycle_analytics/summary.rb         |  2 +-
 .../cycle_analytics/table_references.rb       | 25 -------
 app/models/deployment.rb                      | 37 ++++++-----
 app/models/issue.rb                           |  3 +-
 app/models/merge_request.rb                   |  8 +--
 app/models/merge_requests_closing_issues.rb   |  6 +-
 app/services/create_deployment_service.rb     |  2 +-
 app/views/projects/pipelines/_head.html.haml  |  9 +--
 ...5052008_add_table_merge_request_metrics.rb |  2 +-
 lib/gitlab/database/median.rb                 | 35 ++++++----
 lib/gitlab/database/util.rb                   | 12 ++++
 spec/models/cycle_analytics/code_spec.rb      | 25 ++++---
 spec/models/cycle_analytics/issue_spec.rb     | 37 +++++++----
 spec/models/cycle_analytics/plan_spec.rb      | 42 +++++++-----
 .../models/cycle_analytics/production_spec.rb | 37 ++++++-----
 spec/models/cycle_analytics/review_spec.rb    | 19 ++++--
 spec/models/cycle_analytics/staging_spec.rb   | 45 ++++++++-----
 spec/models/cycle_analytics/test_spec.rb      | 31 ++++-----
 .../merge_requests/create_service_spec.rb     |  2 +-
 .../merge_requests/refresh_service_spec.rb    |  2 +-
 .../merge_requests/update_service_spec.rb     |  6 +-
 .../test_generation.rb                        |  4 --
 23 files changed, 252 insertions(+), 205 deletions(-)
 delete mode 100644 app/models/cycle_analytics/table_references.rb
 create mode 100644 lib/gitlab/database/util.rb

diff --git a/app/models/cycle_analytics.rb b/app/models/cycle_analytics.rb
index 3ca160252ff..be295487fd2 100644
--- a/app/models/cycle_analytics.rb
+++ b/app/models/cycle_analytics.rb
@@ -5,55 +5,54 @@ class CycleAnalytics
   def initialize(project, from:)
     @project = project
     @from = from
-    @summary = Summary.new(project, from: from)
   end
 
   def summary
-    @summary
+    @summary ||= Summary.new(@project, from: @from)
   end
 
   def issue
     calculate_metric(:issue,
-                     TableReferences.issues[:created_at],
-                     [TableReferences.issue_metrics[:first_associated_with_milestone_at],
-                      TableReferences.issue_metrics[:first_added_to_board_at]])
+                     Issue.arel_table[:created_at],
+                     [Issue::Metrics.arel_table[:first_associated_with_milestone_at],
+                      Issue::Metrics.arel_table[:first_added_to_board_at]])
   end
 
   def plan
     calculate_metric(:plan,
-                     [TableReferences.issue_metrics[:first_associated_with_milestone_at],
-                      TableReferences.issue_metrics[:first_added_to_board_at]],
-                     TableReferences.issue_metrics[:first_mentioned_in_commit_at])
+                     [Issue::Metrics.arel_table[:first_associated_with_milestone_at],
+                      Issue::Metrics.arel_table[:first_added_to_board_at]],
+                     Issue::Metrics.arel_table[:first_mentioned_in_commit_at])
   end
 
   def code
     calculate_metric(:code,
-                     TableReferences.issue_metrics[:first_mentioned_in_commit_at],
-                     TableReferences.merge_requests[:created_at])
+                     Issue::Metrics.arel_table[:first_mentioned_in_commit_at],
+                     MergeRequest.arel_table[:created_at])
   end
 
   def test
     calculate_metric(:test,
-                     TableReferences.merge_request_metrics[:latest_build_started_at],
-                     TableReferences.merge_request_metrics[:latest_build_finished_at])
+                     MergeRequest::Metrics.arel_table[:latest_build_started_at],
+                     MergeRequest::Metrics.arel_table[:latest_build_finished_at])
   end
 
   def review
     calculate_metric(:review,
-                     TableReferences.merge_requests[:created_at],
-                     TableReferences.merge_request_metrics[:merged_at])
+                     MergeRequest.arel_table[:created_at],
+                     MergeRequest::Metrics.arel_table[:merged_at])
   end
 
   def staging
     calculate_metric(:staging,
-                     TableReferences.merge_request_metrics[:merged_at],
-                     TableReferences.merge_request_metrics[:first_deployed_to_production_at])
+                     MergeRequest::Metrics.arel_table[:merged_at],
+                     MergeRequest::Metrics.arel_table[:first_deployed_to_production_at])
   end
 
   def production
     calculate_metric(:production,
-                     TableReferences.issues[:created_at],
-                     TableReferences.merge_request_metrics[:first_deployed_to_production_at])
+                     Issue.arel_table[:created_at],
+                     MergeRequest::Metrics.arel_table[:first_deployed_to_production_at])
   end
 
   private
@@ -69,9 +68,7 @@ class CycleAnalytics
       cte_table,
       subtract_datetimes(base_query, end_time_attrs, start_time_attrs, name.to_s))
 
-    median_queries = Array.wrap(median_datetime(cte_table, interval_query, name))
-    results = median_queries.map { |query| run_query(query) }
-    extract_median(results).presence
+    median_datetime(cte_table, interval_query, name)
   end
 
   # Join table with a row for every <issue,merge_request> pair (where the merge request
@@ -79,27 +76,22 @@ class CycleAnalytics
   # are loaded with an inner join, so issues / merge requests without metrics are
   # automatically excluded.
   def base_query
-    arel_table = TableReferences.merge_requests_closing_issues
+    arel_table = MergeRequestsClosingIssues.arel_table
 
     # Load issues
-    query = arel_table.join(TableReferences.issues).on(TableReferences.issues[:id].eq(arel_table[:issue_id])).
-            join(TableReferences.issue_metrics).on(TableReferences.issues[:id].eq(TableReferences.issue_metrics[:issue_id])).
-            where(TableReferences.issues[:project_id].eq(@project.id)).
-            where(TableReferences.issues[:deleted_at].eq(nil)).
-            where(TableReferences.issues[:created_at].gteq(@from))
+    query = arel_table.join(Issue.arel_table).on(Issue.arel_table[:id].eq(arel_table[:issue_id])).
+            join(Issue::Metrics.arel_table).on(Issue.arel_table[:id].eq(Issue::Metrics.arel_table[:issue_id])).
+            where(Issue.arel_table[:project_id].eq(@project.id)).
+            where(Issue.arel_table[:deleted_at].eq(nil)).
+            where(Issue.arel_table[:created_at].gteq(@from))
 
     # Load merge_requests
-    query = query.join(TableReferences.merge_requests, Arel::Nodes::OuterJoin).
-            on(TableReferences.merge_requests[:id].eq(arel_table[:merge_request_id])).
-            join(TableReferences.merge_request_metrics).
-            on(TableReferences.merge_requests[:id].eq(TableReferences.merge_request_metrics[:merge_request_id]))
+    query = query.join(MergeRequest.arel_table, Arel::Nodes::OuterJoin).
+            on(MergeRequest.arel_table[:id].eq(arel_table[:merge_request_id])).
+            join(MergeRequest::Metrics.arel_table).
+            on(MergeRequest.arel_table[:id].eq(MergeRequest::Metrics.arel_table[:merge_request_id]))
 
     # Limit to merge requests that have been deployed to production after `@from`
-    query.where(TableReferences.merge_request_metrics[:first_deployed_to_production_at].gteq(@from))
-  end
-
-  def run_query(query)
-    query = query.to_sql unless query.is_a?(String)
-    ActiveRecord::Base.connection.execute(query)
+    query.where(MergeRequest::Metrics.arel_table[:first_deployed_to_production_at].gteq(@from))
   end
 end
diff --git a/app/models/cycle_analytics/summary.rb b/app/models/cycle_analytics/summary.rb
index 04a90a8da49..53b2cacb131 100644
--- a/app/models/cycle_analytics/summary.rb
+++ b/app/models/cycle_analytics/summary.rb
@@ -6,7 +6,7 @@ class CycleAnalytics
     end
 
     def new_issues
-      @project.issues.where("created_at > ?", @from).count
+      @project.issues.created_after(@from).count
     end
 
     def commits
diff --git a/app/models/cycle_analytics/table_references.rb b/app/models/cycle_analytics/table_references.rb
deleted file mode 100644
index f276723ee0e..00000000000
--- a/app/models/cycle_analytics/table_references.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-class CycleAnalytics
-  module TableReferences
-    class << self
-      def issues
-        Issue.arel_table
-      end
-
-      def issue_metrics
-        Issue::Metrics.arel_table
-      end
-
-      def merge_requests
-        MergeRequest.arel_table
-      end
-
-      def merge_request_metrics
-        MergeRequest::Metrics.arel_table
-      end
-
-      def merge_requests_closing_issues
-        MergeRequestsClosingIssues.arel_table
-      end
-    end
-  end
-end
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index 15f349c89bb..07d7e19e70d 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -43,23 +43,30 @@ class Deployment < ActiveRecord::Base
     project.repository.is_ancestor?(commit.id, sha)
   end
 
-  def update_merge_request_metrics
-    if environment.update_merge_request_metrics?
-      merge_requests = project.merge_requests.
-                       joins(:metrics).
-                       where(target_branch: self.ref, merge_request_metrics: { first_deployed_to_production_at: nil }).
-                       where("merge_request_metrics.merged_at <= ?", self.created_at)
-
-      if previous_deployment
-        merge_requests = merge_requests.where("merge_request_metrics.merged_at >= ?", previous_deployment.created_at)
-      end
+  def update_merge_request_metrics!
+    return unless environment.update_merge_request_metrics?
+
+    merge_requests = project.merge_requests.
+                     joins(:metrics).
+                     where(target_branch: self.ref, merge_request_metrics: { first_deployed_to_production_at: nil }).
+                     where("merge_request_metrics.merged_at <= ?", self.created_at)
 
-      # Need to use `map` instead of `select` because MySQL doesn't allow `SELECT`ing from the same table
-      # that we're updating.
-      MergeRequest::Metrics.
-        where(merge_request_id: merge_requests.map(&:id), first_deployed_to_production_at: nil).
-        update_all(first_deployed_to_production_at: self.created_at)
+    if previous_deployment
+      merge_requests = merge_requests.where("merge_request_metrics.merged_at >= ?", previous_deployment.created_at)
     end
+
+    # Need to use `map` instead of `select` because MySQL doesn't allow `SELECT`ing from the same table
+    # that we're updating.
+    merge_request_ids =
+      if Gitlab::Database.postgresql?
+        merge_requests.select(:id)
+      elsif Gitlab::Database.mysql?
+        merge_requests.map(&:id)
+      end
+
+    MergeRequest::Metrics.
+      where(merge_request_id: merge_request_ids, first_deployed_to_production_at: nil).
+      update_all(first_deployed_to_production_at: self.created_at)
   end
 
   def previous_deployment
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 6a264ace165..25ba38a1cff 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -24,7 +24,6 @@ class Issue < ActiveRecord::Base
   has_many :events, as: :target, dependent: :destroy
 
   has_many :merge_requests_closing_issues, class_name: 'MergeRequestsClosingIssues'
-  has_many :closed_by_merge_requests, through: :merge_requests_closing_issues, source: :merge_request
 
   validates :project, presence: true
 
@@ -39,6 +38,8 @@ class Issue < ActiveRecord::Base
   scope :order_due_date_asc, -> { reorder('issues.due_date IS NULL, issues.due_date ASC') }
   scope :order_due_date_desc, -> { reorder('issues.due_date IS NULL, issues.due_date DESC') }
 
+  scope :created_after, -> (datetime) { where("created_at >= ?", datetime) }
+
   attr_spammable :title, spam_title: true
   attr_spammable :description, spam_description: true
 
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 0ac291ce1a0..c05718f4d5a 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -17,7 +17,6 @@ class MergeRequest < ActiveRecord::Base
   has_many :events, as: :target, dependent: :destroy
 
   has_many :merge_requests_closing_issues, class_name:  'MergeRequestsClosingIssues'
-  has_many :issues_closed, through: :merge_requests_closing_issues, source: :issue
 
   serialize :merge_params, Hash
 
@@ -524,11 +523,8 @@ class MergeRequest < ActiveRecord::Base
   # Return the set of issues that will be closed if this merge request is accepted.
   def closes_issues(current_user = self.author)
     if target_branch == project.default_branch
-      messages = if merge_request_diff
-                   commits.map(&:safe_message) << description
-                 else
-                   [description]
-                 end
+      messages = [description]
+      messages.concat(commits.map(&:safe_message)) if merge_request_diff
 
       Gitlab::ClosingIssueExtractor.new(project, current_user).
         closed_by_message(messages.join("\n"))
diff --git a/app/models/merge_requests_closing_issues.rb b/app/models/merge_requests_closing_issues.rb
index cd49076002d..ab597c37947 100644
--- a/app/models/merge_requests_closing_issues.rb
+++ b/app/models/merge_requests_closing_issues.rb
@@ -2,8 +2,6 @@ class MergeRequestsClosingIssues < ActiveRecord::Base
   belongs_to :merge_request
   belongs_to :issue
 
-  validates_uniqueness_of :merge_request_id, scope: :issue_id
-
-  validates_presence_of :merge_request
-  validates_presence_of :issue
+  validates :merge_request_id, uniqueness: { scope: :issue_id }, presence: true
+  validates :issue_id, presence: true
 end
diff --git a/app/services/create_deployment_service.rb b/app/services/create_deployment_service.rb
index e958e91d612..799ad3e1bd0 100644
--- a/app/services/create_deployment_service.rb
+++ b/app/services/create_deployment_service.rb
@@ -13,7 +13,7 @@ class CreateDeploymentService < BaseService
       deployable: deployable
     )
 
-    deployment.update_merge_request_metrics
+    deployment.update_merge_request_metrics!
 
     deployment
   end
diff --git a/app/views/projects/pipelines/_head.html.haml b/app/views/projects/pipelines/_head.html.haml
index fa1470f5fbc..a97674e84ba 100644
--- a/app/views/projects/pipelines/_head.html.haml
+++ b/app/views/projects/pipelines/_head.html.haml
@@ -20,7 +20,8 @@
             %span
               Environments
 
-      = nav_link(controller: %w(cycle_analytics)) do
-        = link_to project_cycle_analytics_path(@project), title: 'Cycle Analytics' do
-          %span
-            Cycle Analytics
+      - if current_user
+        = nav_link(controller: %w(cycle_analytics)) do
+          = link_to project_cycle_analytics_path(@project), title: 'Cycle Analytics' do
+            %span
+              Cycle Analytics
diff --git a/db/migrate/20160825052008_add_table_merge_request_metrics.rb b/db/migrate/20160825052008_add_table_merge_request_metrics.rb
index bde9238b3c6..e01cc5038b9 100644
--- a/db/migrate/20160825052008_add_table_merge_request_metrics.rb
+++ b/db/migrate/20160825052008_add_table_merge_request_metrics.rb
@@ -29,7 +29,7 @@ class AddTableMergeRequestMetrics < ActiveRecord::Migration
 
       t.datetime 'latest_build_started_at'
       t.datetime 'latest_build_finished_at'
-      t.datetime 'first_deployed_to_production_at'
+      t.datetime 'first_deployed_to_production_at', index: true
       t.datetime 'merged_at'
 
       t.timestamps null: false
diff --git a/lib/gitlab/database/median.rb b/lib/gitlab/database/median.rb
index 94f060bd2c7..9adacd64934 100644
--- a/lib/gitlab/database/median.rb
+++ b/lib/gitlab/database/median.rb
@@ -3,11 +3,15 @@ module Gitlab
   module Database
     module Median
       def median_datetime(arel_table, query_so_far, column_sym)
-        if Gitlab::Database.postgresql?
-          pg_median_datetime(arel_table, query_so_far, column_sym)
-        elsif Gitlab::Database.mysql?
-          mysql_median_datetime(arel_table, query_so_far, column_sym)
-        end
+        median_queries =
+          if Gitlab::Database.postgresql?
+            pg_median_datetime_sql(arel_table, query_so_far, column_sym)
+          elsif Gitlab::Database.mysql?
+            mysql_median_datetime_sql(arel_table, query_so_far, column_sym)
+          end
+
+        results = Array.wrap(median_queries).map { |query| Util.run_query(query) }
+        extract_median(results).presence
       end
 
       def extract_median(results)
@@ -22,7 +26,7 @@ module Gitlab
         end
       end
 
-      def mysql_median_datetime(arel_table, query_so_far, column_sym)
+      def mysql_median_datetime_sql(arel_table, query_so_far, column_sym)
         query = arel_table.
                 from(arel_table.project(Arel.sql('*')).order(arel_table[column_sym]).as(arel_table.table_name)).
                 project(average([arel_table[column_sym]], 'median')).
@@ -44,7 +48,7 @@ module Gitlab
         ]
       end
 
-      def pg_median_datetime(arel_table, query_so_far, column_sym)
+      def pg_median_datetime_sql(arel_table, query_so_far, column_sym)
         # Create a CTE with the column we're operating on, row number (after sorting by the column
         # we're operating on), and count of the table we're operating on (duplicated across) all rows
         # of the CTE. For example, if we're looking to find the median of the `projects.star_count`
@@ -59,10 +63,11 @@ module Gitlab
         cte = Arel::Nodes::As.new(
           cte_table,
           arel_table.
-            project(arel_table[column_sym].as(column_sym.to_s),
-                    Arel::Nodes::Over.new(Arel::Nodes::NamedFunction.new("row_number", []),
-                                          Arel::Nodes::Window.new.order(arel_table[column_sym])).as('row_id'),
-                    arel_table.project("COUNT(1)").as('ct')).
+            project(
+              arel_table[column_sym].as(column_sym.to_s),
+              Arel::Nodes::Over.new(Arel::Nodes::NamedFunction.new("row_number", []),
+                                    Arel::Nodes::Window.new.order(arel_table[column_sym])).as('row_id'),
+              arel_table.project("COUNT(1)").as('ct')).
             # Disallow negative values
             where(arel_table[column_sym].gteq(zero_interval)))
 
@@ -70,9 +75,11 @@ module Gitlab
         # by 'where cte.row_id between cte.ct / 2.0 AND cte.ct / 2.0 + 1'). Find the average of the
         # selected rows, and this is the median value.
         cte_table.project(average([extract_epoch(cte_table[column_sym])], "median")).
-          where(Arel::Nodes::Between.new(cte_table[:row_id],
-                                         Arel::Nodes::And.new([(cte_table[:ct] / Arel.sql('2.0')),
-                                                               (cte_table[:ct] / Arel.sql('2.0') + 1)]))).
+          where(Arel::Nodes::Between.new(
+                  cte_table[:row_id],
+                  Arel::Nodes::And.new(
+                    [(cte_table[:ct] / Arel.sql('2.0')),
+                     (cte_table[:ct] / Arel.sql('2.0') + 1)]))).
           with(query_so_far, cte)
       end
 
diff --git a/lib/gitlab/database/util.rb b/lib/gitlab/database/util.rb
new file mode 100644
index 00000000000..12b68deae89
--- /dev/null
+++ b/lib/gitlab/database/util.rb
@@ -0,0 +1,12 @@
+module Gitlab
+  module Database
+    module Util
+      class << self
+        def run_query(query)
+          query = query.to_sql unless query.is_a?(String)
+          ActiveRecord::Base.connection.execute(query)
+        end
+      end
+    end
+  end
+end
diff --git a/spec/models/cycle_analytics/code_spec.rb b/spec/models/cycle_analytics/code_spec.rb
index e81c78df8f0..b9381e33914 100644
--- a/spec/models/cycle_analytics/code_spec.rb
+++ b/spec/models/cycle_analytics/code_spec.rb
@@ -1,19 +1,28 @@
 require 'spec_helper'
 
 describe 'CycleAnalytics#code', feature: true do
+  extend CycleAnalyticsHelpers::TestGeneration
+
   let(:project) { create(:project) }
   let(:from_date) { 10.days.ago }
   let(:user) { create(:user, :admin) }
   subject { CycleAnalytics.new(project, from: from_date) }
 
-  generate_cycle_analytics_spec(phase: :code,
-                                data_fn: -> (context) { { issue: context.create(:issue, project: context.project) } },
-                                start_time_conditions: [["issue mentioned in a commit", -> (context, data) { context.create_commit_referencing_issue(data[:issue]) }]],
-                                end_time_conditions:   [["merge request that closes issue is created", -> (context, data) { context.create_merge_request_closing_issue(data[:issue]) }]],
-                                post_fn: -> (context, data) do
-                                  context.merge_merge_requests_closing_issue(data[:issue])
-                                  context.deploy_master
-                                end)
+  generate_cycle_analytics_spec(
+    phase: :code,
+    data_fn: -> (context) { { issue: context.create(:issue, project: context.project) } },
+    start_time_conditions: [["issue mentioned in a commit",
+                             -> (context, data) do
+                               context.create_commit_referencing_issue(data[:issue])
+                             end]],
+    end_time_conditions:   [["merge request that closes issue is created",
+                             -> (context, data) do
+                               context.create_merge_request_closing_issue(data[:issue])
+                             end]],
+    post_fn: -> (context, data) do
+      context.merge_merge_requests_closing_issue(data[:issue])
+      context.deploy_master
+    end)
 
   context "when a regular merge request (that doesn't close the issue) is created" do
     it "returns nil" do
diff --git a/spec/models/cycle_analytics/issue_spec.rb b/spec/models/cycle_analytics/issue_spec.rb
index 8d7d03193f0..e9cc71254ab 100644
--- a/spec/models/cycle_analytics/issue_spec.rb
+++ b/spec/models/cycle_analytics/issue_spec.rb
@@ -1,23 +1,36 @@
 require 'spec_helper'
 
 describe 'CycleAnalytics#issue', models: true do
+  extend CycleAnalyticsHelpers::TestGeneration
+
   let(:project) { create(:project) }
   let(:from_date) { 10.days.ago }
   let(:user) { create(:user, :admin) }
   subject { CycleAnalytics.new(project, from: from_date) }
 
-  generate_cycle_analytics_spec(phase: :issue,
-                                data_fn: -> (context) { { issue: context.build(:issue, project: context.project) } },
-                                start_time_conditions: [["issue created", -> (context, data) { data[:issue].save }]],
-                                end_time_conditions:   [["issue associated with a milestone", -> (context, data) { data[:issue].update(milestone: context.create(:milestone, project: context.project)) if data[:issue].persisted? }],
-                                                        ["list label added to issue", -> (context, data) { data[:issue].update(label_ids: [context.create(:label, lists: [context.create(:list)]).id]) if data[:issue].persisted? }]],
-                                post_fn: -> (context, data) do
-                                  if data[:issue].persisted?
-                                    context.create_merge_request_closing_issue(data[:issue].reload)
-                                    context.merge_merge_requests_closing_issue(data[:issue])
-                                    context.deploy_master
-                                  end
-                                end)
+  generate_cycle_analytics_spec(
+    phase: :issue,
+    data_fn: -> (context) { { issue: context.build(:issue, project: context.project) } },
+    start_time_conditions: [["issue created", -> (context, data) { data[:issue].save }]],
+    end_time_conditions:   [["issue associated with a milestone",
+                             -> (context, data) do
+                               if data[:issue].persisted?
+                                 data[:issue].update(milestone: context.create(:milestone, project: context.project))
+                               end
+                             end],
+                            ["list label added to issue",
+                             -> (context, data) do
+                               if data[:issue].persisted?
+                                 data[:issue].update(label_ids: [context.create(:label, lists: [context.create(:list)]).id])
+                               end
+                             end]],
+    post_fn: -> (context, data) do
+      if data[:issue].persisted?
+        context.create_merge_request_closing_issue(data[:issue].reload)
+        context.merge_merge_requests_closing_issue(data[:issue])
+        context.deploy_master
+      end
+    end)
 
   context "when a regular label (instead of a list label) is added to the issue" do
     it "returns nil" do
diff --git a/spec/models/cycle_analytics/plan_spec.rb b/spec/models/cycle_analytics/plan_spec.rb
index 4a66615e53b..5b8c96dc992 100644
--- a/spec/models/cycle_analytics/plan_spec.rb
+++ b/spec/models/cycle_analytics/plan_spec.rb
@@ -1,26 +1,38 @@
 require 'spec_helper'
 
 describe 'CycleAnalytics#plan', feature: true do
+  extend CycleAnalyticsHelpers::TestGeneration
+
   let(:project) { create(:project) }
   let(:from_date) { 10.days.ago }
   let(:user) { create(:user, :admin) }
   subject { CycleAnalytics.new(project, from: from_date) }
 
-  generate_cycle_analytics_spec(phase: :plan,
-                                data_fn: -> (context) do
-                                  {
-                                    issue: context.create(:issue, project: context.project),
-                                    branch_name: context.random_git_name
-                                  }
-                                end,
-                                start_time_conditions: [["issue associated with a milestone", -> (context, data) { data[:issue].update(milestone: context.create(:milestone, project: context.project)) }],
-                                                        ["list label added to issue", -> (context, data) { data[:issue].update(label_ids: [context.create(:label, lists: [context.create(:list)]).id]) }]],
-                                end_time_conditions:   [["issue mentioned in a commit", -> (context, data) { context.create_commit_referencing_issue(data[:issue], branch_name: data[:branch_name]) }]],
-                                post_fn: -> (context, data) do
-                                  context.create_merge_request_closing_issue(data[:issue], source_branch: data[:branch_name])
-                                  context.merge_merge_requests_closing_issue(data[:issue])
-                                  context.deploy_master
-                                end)
+  generate_cycle_analytics_spec(
+    phase: :plan,
+    data_fn: -> (context) do
+      {
+        issue: context.create(:issue, project: context.project),
+        branch_name: context.random_git_name
+      }
+    end,
+    start_time_conditions: [["issue associated with a milestone",
+                             -> (context, data) do
+                               data[:issue].update(milestone: context.create(:milestone, project: context.project))
+                             end],
+                            ["list label added to issue",
+                             -> (context, data) do
+                               data[:issue].update(label_ids: [context.create(:label, lists: [context.create(:list)]).id])
+                             end]],
+    end_time_conditions:   [["issue mentioned in a commit",
+                             -> (context, data) do
+                               context.create_commit_referencing_issue(data[:issue], branch_name: data[:branch_name])
+                             end]],
+    post_fn: -> (context, data) do
+      context.create_merge_request_closing_issue(data[:issue], source_branch: data[:branch_name])
+      context.merge_merge_requests_closing_issue(data[:issue])
+      context.deploy_master
+    end)
 
   context "when a regular label (instead of a list label) is added to the issue" do
     it "returns nil" do
diff --git a/spec/models/cycle_analytics/production_spec.rb b/spec/models/cycle_analytics/production_spec.rb
index 703f8d5f782..1f5e5cab92d 100644
--- a/spec/models/cycle_analytics/production_spec.rb
+++ b/spec/models/cycle_analytics/production_spec.rb
@@ -1,28 +1,31 @@
 require 'spec_helper'
 
 describe 'CycleAnalytics#production', feature: true do
+  extend CycleAnalyticsHelpers::TestGeneration
+
   let(:project) { create(:project) }
   let(:from_date) { 10.days.ago }
   let(:user) { create(:user, :admin) }
   subject { CycleAnalytics.new(project, from: from_date) }
 
-  generate_cycle_analytics_spec(phase: :production,
-                                data_fn: -> (context) { { issue: context.build(:issue, project: context.project) } },
-                                start_time_conditions: [["issue is created", -> (context, data) { data[:issue].save }]],
-                                before_end_fn: lambda do |context, data|
-                                  context.create_merge_request_closing_issue(data[:issue])
-                                  context.merge_merge_requests_closing_issue(data[:issue])
-                                end,
-                                end_time_conditions:
-                                  [["merge request that closes issue is deployed to production", -> (context, data) { context.deploy_master }],
-                                   ["production deploy happens after merge request is merged (along with other changes)",
-                                    lambda do |context, data|
-                                      # Make other changes on master
-                                      sha = context.project.repository.commit_file(context.user, context.random_git_name, "content", "commit message", 'master', false)
-                                      context.project.repository.commit(sha)
-
-                                      context.deploy_master
-                                    end]])
+  generate_cycle_analytics_spec(
+    phase: :production,
+    data_fn: -> (context) { { issue: context.build(:issue, project: context.project) } },
+    start_time_conditions: [["issue is created", -> (context, data) { data[:issue].save }]],
+    before_end_fn: lambda do |context, data|
+      context.create_merge_request_closing_issue(data[:issue])
+      context.merge_merge_requests_closing_issue(data[:issue])
+    end,
+    end_time_conditions:
+      [["merge request that closes issue is deployed to production", -> (context, data) { context.deploy_master }],
+       ["production deploy happens after merge request is merged (along with other changes)",
+        lambda do |context, data|
+          # Make other changes on master
+          sha = context.project.repository.commit_file(context.user, context.random_git_name, "content", "commit message", 'master', false)
+          context.project.repository.commit(sha)
+
+          context.deploy_master
+        end]])
 
   context "when a regular merge request (that doesn't close the issue) is merged and deployed" do
     it "returns nil" do
diff --git a/spec/models/cycle_analytics/review_spec.rb b/spec/models/cycle_analytics/review_spec.rb
index 100ce11299a..b6e26d8f261 100644
--- a/spec/models/cycle_analytics/review_spec.rb
+++ b/spec/models/cycle_analytics/review_spec.rb
@@ -1,16 +1,25 @@
 require 'spec_helper'
 
 describe 'CycleAnalytics#review', feature: true do
+  extend CycleAnalyticsHelpers::TestGeneration
+
   let(:project) { create(:project) }
   let(:from_date) { 10.days.ago }
   let(:user) { create(:user, :admin) }
   subject { CycleAnalytics.new(project, from: from_date) }
 
-  generate_cycle_analytics_spec(phase: :review,
-                                data_fn: -> (context) { { issue: context.create(:issue, project: context.project) } },
-                                start_time_conditions: [["merge request that closes issue is created", -> (context, data) { context.create_merge_request_closing_issue(data[:issue]) }]],
-                                end_time_conditions:   [["merge request that closes issue is merged", -> (context, data) { context.merge_merge_requests_closing_issue(data[:issue]) }]],
-                                post_fn: -> (context, data) { context.deploy_master })
+  generate_cycle_analytics_spec(
+    phase: :review,
+    data_fn: -> (context) { { issue: context.create(:issue, project: context.project) } },
+    start_time_conditions: [["merge request that closes issue is created",
+                             -> (context, data) do
+                               context.create_merge_request_closing_issue(data[:issue])
+                             end]],
+    end_time_conditions:   [["merge request that closes issue is merged",
+                             -> (context, data) do
+                               context.merge_merge_requests_closing_issue(data[:issue])
+                             end]],
+    post_fn: -> (context, data) { context.deploy_master })
 
   context "when a regular merge request (that doesn't close the issue) is created and merged" do
     it "returns nil" do
diff --git a/spec/models/cycle_analytics/staging_spec.rb b/spec/models/cycle_analytics/staging_spec.rb
index 105f566f41c..af1c4477ddb 100644
--- a/spec/models/cycle_analytics/staging_spec.rb
+++ b/spec/models/cycle_analytics/staging_spec.rb
@@ -1,26 +1,41 @@
 require 'spec_helper'
 
 describe 'CycleAnalytics#staging', feature: true do
+  extend CycleAnalyticsHelpers::TestGeneration
+
   let(:project) { create(:project) }
   let(:from_date) { 10.days.ago }
   let(:user) { create(:user, :admin) }
   subject { CycleAnalytics.new(project, from: from_date) }
 
-  generate_cycle_analytics_spec(phase: :staging,
-                                data_fn: lambda do |context|
-                                  issue = context.create(:issue, project: context.project)
-                                  { issue: issue, merge_request: context.create_merge_request_closing_issue(issue) }
-                                end,
-                                start_time_conditions: [["merge request that closes issue is merged", -> (context, data) { context.merge_merge_requests_closing_issue(data[:issue])} ]],
-                                end_time_conditions:   [["merge request that closes issue is deployed to production", -> (context, data) { context.deploy_master }],
-                                                        ["production deploy happens after merge request is merged (along with other changes)",
-                                                         lambda do |context, data|
-                                                           # Make other changes on master
-                                                           sha = context.project.repository.commit_file(context.user, context.random_git_name, "content", "commit message", 'master', false)
-                                                           context.project.repository.commit(sha)
-
-                                                           context.deploy_master
-                                                         end]])
+  generate_cycle_analytics_spec(
+    phase: :staging,
+    data_fn: lambda do |context|
+      issue = context.create(:issue, project: context.project)
+      { issue: issue, merge_request: context.create_merge_request_closing_issue(issue) }
+    end,
+    start_time_conditions: [["merge request that closes issue is merged",
+                             -> (context, data) do
+                               context.merge_merge_requests_closing_issue(data[:issue])
+                             end ]],
+    end_time_conditions:   [["merge request that closes issue is deployed to production",
+                             -> (context, data) do
+                               context.deploy_master
+                             end],
+                            ["production deploy happens after merge request is merged (along with other changes)",
+                             lambda do |context, data|
+                               # Make other changes on master
+                               sha = context.project.repository.commit_file(
+                                 context.user,
+                                 context.random_git_name,
+                                 "content",
+                                 "commit message",
+                                 'master',
+                                 false)
+                               context.project.repository.commit(sha)
+
+                               context.deploy_master
+                             end]])
 
   context "when a regular merge request (that doesn't close the issue) is merged and deployed" do
     it "returns nil" do
diff --git a/spec/models/cycle_analytics/test_spec.rb b/spec/models/cycle_analytics/test_spec.rb
index 79edc29c173..89ace0b2742 100644
--- a/spec/models/cycle_analytics/test_spec.rb
+++ b/spec/models/cycle_analytics/test_spec.rb
@@ -1,26 +1,27 @@
 require 'spec_helper'
 
 describe 'CycleAnalytics#test', feature: true do
+  extend CycleAnalyticsHelpers::TestGeneration
+
   let(:project) { create(:project) }
   let(:from_date) { 10.days.ago }
   let(:user) { create(:user, :admin) }
   subject { CycleAnalytics.new(project, from: from_date) }
 
-  generate_cycle_analytics_spec(phase: :test,
-                                data_fn: lambda do |context|
-                                  issue = context.create(:issue, project: context.project)
-                                  merge_request = context.create_merge_request_closing_issue(issue)
-                                  {
-                                    pipeline: context.create(:ci_pipeline, ref: merge_request.source_branch, sha: merge_request.diff_head_sha, project: context.project),
-                                    issue: issue
-                                  }
-                                end,
-                                start_time_conditions: [["pipeline is started", -> (context, data) { data[:pipeline].run! }]],
-                                end_time_conditions:   [["pipeline is finished", -> (context, data) { data[:pipeline].succeed! }]],
-                                post_fn: -> (context, data) do
-                                  context.merge_merge_requests_closing_issue(data[:issue])
-                                  context.deploy_master
-                                end)
+  generate_cycle_analytics_spec(
+    phase: :test,
+    data_fn: lambda do |context|
+      issue = context.create(:issue, project: context.project)
+      merge_request = context.create_merge_request_closing_issue(issue)
+      pipeline = context.create(:ci_pipeline, ref: merge_request.source_branch, sha: merge_request.diff_head_sha, project: context.project)
+      { pipeline: pipeline, issue: issue }
+    end,
+    start_time_conditions: [["pipeline is started", -> (context, data) { data[:pipeline].run! }]],
+    end_time_conditions:   [["pipeline is finished", -> (context, data) { data[:pipeline].succeed! }]],
+    post_fn: -> (context, data) do
+      context.merge_merge_requests_closing_issue(data[:issue])
+      context.deploy_master
+    end)
 
   context "when the pipeline is for a regular merge request (that doesn't close an issue)" do
     it "returns nil" do
diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb
index a0614abcf62..ff6bad6aaba 100644
--- a/spec/services/merge_requests/create_service_spec.rb
+++ b/spec/services/merge_requests/create_service_spec.rb
@@ -108,7 +108,7 @@ describe MergeRequests::CreateService, services: true do
         allow(service).to receive(:execute_hooks)
         merge_request = service.execute
 
-        expect(merge_request.issues_closed).to match_array([first_issue, second_issue])
+        expect(merge_request.reload.closes_issues).to match_array([first_issue, second_issue])
       end
     end
   end
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index 7ecad8d6bad..22cb122526c 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -200,7 +200,7 @@ describe MergeRequests::RefreshService, services: true do
         allow(refresh_service).to receive(:execute_hooks)
         refresh_service.execute(@oldrev, @newrev, 'refs/heads/feature')
 
-        expect(merge_request.reload.issues_closed).to eq([issue])
+        expect(merge_request.reload.closes_issues).to eq([issue])
       end
     end
 
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index 84b0acffaac..68dd0382a3d 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -274,7 +274,7 @@ describe MergeRequests::UpdateService, services: true do
         allow(service).to receive(:execute_hooks)
         service.execute(merge_request)
 
-        expect(merge_request.reload.issues_closed).to match_array([first_issue, second_issue])
+        expect(merge_request.reload.closes_issues).to match_array([first_issue, second_issue])
       end
 
       it 'removes `MergeRequestsClosingIssues` records when issues are not closed anymore' do
@@ -288,13 +288,13 @@ describe MergeRequests::UpdateService, services: true do
 
         merge_request = MergeRequests::CreateService.new(project, user, opts).execute
 
-        expect(merge_request.issues_closed).to match_array([first_issue, second_issue])
+        expect(merge_request.reload.closes_issues).to match_array([first_issue, second_issue])
 
         service = described_class.new(project, user, description: "not closing any issues")
         allow(service).to receive(:execute_hooks)
         service.execute(merge_request.reload)
 
-        expect(merge_request.reload.issues_closed).to be_empty
+        expect(merge_request.reload.closes_issues).to be_empty
       end
     end
   end
diff --git a/spec/support/cycle_analytics_helpers/test_generation.rb b/spec/support/cycle_analytics_helpers/test_generation.rb
index 4d5ce86691f..8e19a6c92e2 100644
--- a/spec/support/cycle_analytics_helpers/test_generation.rb
+++ b/spec/support/cycle_analytics_helpers/test_generation.rb
@@ -159,7 +159,3 @@ module CycleAnalyticsHelpers
     end
   end
 end
-
-RSpec.configure do |config|
-  config.extend CycleAnalyticsHelpers::TestGeneration
-end
-- 
GitLab