From 1a4ff5d720fa0ec65f925ef381fced3d5f9a040f Mon Sep 17 00:00:00 2001
From: James Lopez <james@jameslopez.es>
Date: Thu, 20 Oct 2016 10:08:53 +0200
Subject: [PATCH] Added code events spec and logic. Also fixed SQL issues and
 refactored the code a bit.

---
 lib/gitlab/cycle_analytics/events.rb          | 11 ++++--
 lib/gitlab/cycle_analytics/events_fetcher.rb  | 34 ++++++++++++++++---
 lib/gitlab/cycle_analytics/metrics_fetcher.rb |  2 +-
 lib/gitlab/database/date_time.rb              |  4 +--
 .../lib/gitlab/cycle_analytics/events_spec.rb | 30 +++++++++++++++-
 5 files changed, 70 insertions(+), 11 deletions(-)

diff --git a/lib/gitlab/cycle_analytics/events.rb b/lib/gitlab/cycle_analytics/events.rb
index 51fa4653634..ea8e78d7ab7 100644
--- a/lib/gitlab/cycle_analytics/events.rb
+++ b/lib/gitlab/cycle_analytics/events.rb
@@ -9,10 +9,10 @@ module Gitlab
         @fetcher = EventsFetcher.new(project: project, from: from)
       end
 
-      #TODO: backend pagination - specially for commits, etc...
+      # TODO: backend pagination - specially for commits, etc...
 
       def issue_events
-        #TODO figure out what the frontend needs for displaying the avatar
+        # TODO figure out what the frontend needs for displaying the avatar
         @fetcher.fetch_issue_events.each do |event|
           event['total_time'] = distance_of_time_in_words(event['total_time'].to_f)
           event['created_at'] = interval_in_words(event['created_at'])
@@ -27,6 +27,13 @@ module Gitlab
         end
       end
 
+      def code_events
+        @fetcher.fetch_code_events.each do |event|
+          event['total_time'] = distance_of_time_in_words(event['total_time'].to_f)
+          event['created_at'] = interval_in_words(event['created_at'])
+        end
+      end
+
       private
 
       def first_time_reference_commit(commits, event)
diff --git a/lib/gitlab/cycle_analytics/events_fetcher.rb b/lib/gitlab/cycle_analytics/events_fetcher.rb
index c25bc6c5f32..cb61cda200d 100644
--- a/lib/gitlab/cycle_analytics/events_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/events_fetcher.rb
@@ -16,18 +16,33 @@ module Gitlab
           project(extract_epoch(diff_fn).as('total_time'), *issue_projections).
           order(issue_table[:created_at].desc)
 
-        ActiveRecord::Base.connection.execute(query.to_sql).to_a
+        execute(query)
       end
 
       def fetch_plan_events
         base_query = base_query_for(:plan)
-        diff_fn = subtract_datetimes_diff(base_query, issue_table[:created_at], plan_attributes)
+        diff_fn = subtract_datetimes_diff(base_query,
+                                          issue_metrics_table[:first_associated_with_milestone_at],
+                                          plan_attributes)
 
         query = base_query.join(mr_diff_table).on(mr_diff_table[:merge_request_id].eq(mr_table[:id])).
           project(extract_epoch(diff_fn).as('total_time'), *plan_projections).
-          order(issue_table[:created_at].desc)
+          order(issue_metrics_table[:first_associated_with_milestone_at].desc)
 
-        ActiveRecord::Base.connection.execute(query.to_sql).to_a
+        execute(query)
+      end
+
+      def fetch_code_events
+        base_query = base_query_for(:code)
+        diff_fn = subtract_datetimes_diff(base_query,
+                                          issue_metrics_table[:first_mentioned_in_commit_at],
+                                          issue_table[:created_at])
+
+        query = base_query.join(user_table).on(issue_table[:author_id].eq(user_table[:id])).
+          project(extract_epoch(diff_fn).as('total_time'), *code_projections).
+          order(mr_table[:created_at].desc)
+
+        execute(query)
       end
 
       private
@@ -38,7 +53,8 @@ module Gitlab
       end
 
       def plan_attributes
-        issue_attributes + [issue_metrics_table[:first_mentioned_in_commit_at]]
+        [issue_metrics_table[:first_added_to_board_at],
+         issue_metrics_table[:first_mentioned_in_commit_at]]
       end
 
       def issue_projections
@@ -49,6 +65,10 @@ module Gitlab
         [mr_diff_table[:st_commits].as('commits'), issue_metrics_table[:first_mentioned_in_commit_at]]
       end
 
+      def code_projections
+        [mr_table[:title], mr_table[:iid], mr_table[:created_at], User.arel_table[:name]]
+      end
+
       def user_table
         User.arel_table
       end
@@ -56,6 +76,10 @@ module Gitlab
       def extract_epoch(arel_attribute)
         Arel.sql(%Q{EXTRACT(EPOCH FROM (#{arel_attribute.to_sql}))})
       end
+
+      def execute(query)
+        ActiveRecord::Base.connection.execute(query.to_sql).to_a
+      end
     end
   end
 end
diff --git a/lib/gitlab/cycle_analytics/metrics_fetcher.rb b/lib/gitlab/cycle_analytics/metrics_fetcher.rb
index 7fcacf652a6..62988fbdec1 100644
--- a/lib/gitlab/cycle_analytics/metrics_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/metrics_fetcher.rb
@@ -17,7 +17,7 @@ module Gitlab
         # cycle analytics stage.
         interval_query = Arel::Nodes::As.new(
           cte_table,
-          subtract_datetimes(base_query_for(name), end_time_attrs, start_time_attrs, name.to_s))
+          subtract_datetimes(base_query_for(name), start_time_attrs, end_time_attrs, name.to_s))
 
         median_datetime(cte_table, interval_query, name)
       end
diff --git a/lib/gitlab/database/date_time.rb b/lib/gitlab/database/date_time.rb
index e2524886de2..38a6fd4276b 100644
--- a/lib/gitlab/database/date_time.rb
+++ b/lib/gitlab/database/date_time.rb
@@ -7,13 +7,13 @@ module Gitlab
       #
       # Note: For MySQL, the interval is returned in seconds.
       #       For PostgreSQL, the interval is returned as an INTERVAL type.
-      def subtract_datetimes(query_so_far, end_time_attrs, start_time_attrs, as)
+      def subtract_datetimes(query_so_far, start_time_attrs, end_time_attrs, as)
         diff_fn = subtract_datetimes_diff(query_so_far, end_time_attrs, start_time_attrs)
 
         query_so_far.project(diff_fn.as(as))
       end
 
-      def subtract_datetimes_diff(query_so_far, end_time_attrs, start_time_attrs)
+      def subtract_datetimes_diff(query_so_far, start_time_attrs, end_time_attrs)
         if Gitlab::Database.postgresql?
           Arel::Nodes::Subtraction.new(
             Arel::Nodes::NamedFunction.new("COALESCE", Array.wrap(end_time_attrs)),
diff --git a/spec/lib/gitlab/cycle_analytics/events_spec.rb b/spec/lib/gitlab/cycle_analytics/events_spec.rb
index 4f8ba465d71..4a329737c7e 100644
--- a/spec/lib/gitlab/cycle_analytics/events_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/events_spec.rb
@@ -43,7 +43,35 @@ describe Gitlab::CycleAnalytics::Events do
     end
 
     it 'has the total time' do
-      expect(subject.plan_events.first['total_time']).to eq('2 days')
+      expect(subject.plan_events.first['total_time']).to eq('less than a minute')
+    end
+  end
+
+  describe '#code_events' do
+    let!(:context) { create(:issue, project: project, created_at: 2.days.ago) }
+
+    before do
+      create_commit_referencing_issue(context)
+    end
+
+    it 'has the total time' do
+      expect(subject.code_events.first['total_time']).to eq('2 days')
+    end
+
+    it 'has a title' do
+      expect(subject.code_events.first['title']).to eq('Awesome merge_request')
+    end
+
+    it 'has an iid' do
+      expect(subject.code_events.first['iid']).to eq(context.iid.to_s)
+    end
+
+    it 'has a created_at timestamp' do
+      expect(subject.code_events.first['created_at']).to end_with('ago')
+    end
+
+    it "has the author's name" do
+      expect(subject.code_events.first['name']).to eq(context.author.name)
     end
   end
 
-- 
GitLab