Commit cf2dcf04 authored by James Lopez's avatar James Lopez

Refactor all query config stuff into separate classes and added specs

parent cbc9f0cd
module Projects module Projects
module CycleAnalytics module CycleAnalytics
class EventsController < Projects::ApplicationController class EventsController < Projects::ApplicationController
include CycleAnalyticsParams include CycleAnalyticsParams
before_action :authorize_read_cycle_analytics! before_action :authorize_read_cycle_analytics!
before_action :authorize_builds!, only: [:test, :staging] before_action :authorize_builds!, only: [:test, :staging]
def issue def issue
render_events(events.issue_events) render_events(events.issue_events)
end end
def plan def plan
render_events(events.plan_events) render_events(events.plan_events)
end end
def code def code
render_events(events.code_events) render_events(events.code_events)
end end
def test def test
@options = { from: start_date(events_params), branch: events_params[:branch_name] } @options = { from: start_date(events_params), branch: events_params[:branch_name] }
render_events(events.test_events) render_events(events.test_events)
end end
def review def review
render_events(events.review_events) render_events(events.review_events)
end end
def staging def staging
render_events(events.staging_events) render_events(events.staging_events)
end end
def production def production
render_events(events.production_events) render_events(events.production_events)
end end
private private
def render_events(events_list) def render_events(events_list)
respond_to do |format| respond_to do |format|
format.html format.html
format.json { render json: { events: events_list } } format.json { render json: { events: events_list } }
end
end
def events
@events ||= Gitlab::CycleAnalytics::Events.new(project: project, options: options)
end
def options
@options ||= { from: start_date(events_params) }
end
def events_params
return {} unless params[:events].present?
params[:events].slice(:start_date, :branch_name)
end
def authorize_builds!
return access_denied! unless current_user.can?(:read_build, project)
end
end end
end end
def events
@events ||= Gitlab::CycleAnalytics::Events.new(project: project, options: options)
end
def options
@options ||= { from: start_date(events_params) }
end
def events_params
return {} unless params[:events].present?
params[:events].slice(:start_date, :branch_name)
end
def authorize_builds!
return access_denied! unless current_user.can?(:read_build, project)
end
end
end
end end
...@@ -4,4 +4,4 @@ module EntityDateHelper ...@@ -4,4 +4,4 @@ module EntityDateHelper
def interval_in_words(diff) def interval_in_words(diff)
"#{distance_of_time_in_words(diff.to_f)} ago" "#{distance_of_time_in_words(diff.to_f)} ago"
end end
end end
\ No newline at end of file
module Gitlab
module CycleAnalytics
class BaseConfig
extend MetricsFetcher
class << self
attr_reader :start_time_attrs, :end_time_attrs, :projections
end
def self.order
@order || @start_time_attrs
end
def self.query(base_query); end
end
end
end
module Gitlab
module CycleAnalytics
class CodeConfig < BaseConfig
@start_time_attrs = issue_metrics_table[:first_mentioned_in_commit_at]
@end_time_attrs = mr_table[:created_at]
@projections = [mr_table[:title],
mr_table[:iid],
mr_table[:id],
mr_table[:created_at],
mr_table[:state],
mr_table[:author_id]]
@order = mr_table[:created_at]
end
end
end
...@@ -8,22 +8,10 @@ module Gitlab ...@@ -8,22 +8,10 @@ module Gitlab
end end
def fetch(stage:) def fetch(stage:)
custom_query = "#{stage}_custom_query".to_sym @query.execute(stage) do |stage_class, base_query|
stage_class.query(base_query)
@query.execute(stage) do |base_query|
public_send(custom_query, base_query) if self.respond_to?(custom_query)
end end
end end
def plan_custom_query(base_query)
base_query.join(mr_diff_table).on(mr_diff_table[:merge_request_id].eq(mr_table[:id]))
end
def test_custom_query(base_query)
base_query.join(build_table).on(mr_metrics_table[:pipeline_id].eq(build_table[:commit_id]))
end
alias_method :staging_custom_query, :test_custom_query
end end
end end
end end
...@@ -11,7 +11,6 @@ module Gitlab ...@@ -11,7 +11,6 @@ module Gitlab
def execute(stage, &block) def execute(stage, &block)
@stage = stage @stage = stage
@config = QueryConfig.get(stage)
query = build_query(&block) query = build_query(&block)
ActiveRecord::Base.connection.exec_query(query.to_sql) ActiveRecord::Base.connection.exec_query(query.to_sql)
...@@ -21,15 +20,11 @@ module Gitlab ...@@ -21,15 +20,11 @@ module Gitlab
def build_query def build_query
base_query = base_query_for(@stage) base_query = base_query_for(@stage)
diff_fn = subtract_datetimes_diff(@config[:base_query], @config[:start_time_attrs], @config[:end_time_attrs]) diff_fn = subtract_datetimes_diff(base_query, stage_class.start_time_attrs, stage_class.end_time_attrs)
yield base_query if block_given? yield(stage_class, base_query) if block_given?
base_query.project(extract_epoch(diff_fn).as('total_time'), *@config[:projections]).order(order.desc) base_query.project(extract_epoch(diff_fn).as('total_time'), *stage_class.projections).order(stage_class.order.desc)
end
def order
@config[:order] || @config[:start_time_attrs]
end end
def extract_epoch(arel_attribute) def extract_epoch(arel_attribute)
...@@ -37,6 +32,10 @@ module Gitlab ...@@ -37,6 +32,10 @@ module Gitlab
Arel.sql(%Q{EXTRACT(EPOCH FROM (#{arel_attribute.to_sql}))}) Arel.sql(%Q{EXTRACT(EPOCH FROM (#{arel_attribute.to_sql}))})
end end
def stage_class
@stage_class ||= "Gitlab::CycleAnalytics::#{@stage.to_s.camelize}Config".constantize
end
end end
end end
end end
module Gitlab
module CycleAnalytics
class IssueConfig < BaseConfig
@start_time_attrs = issue_table[:created_at]
@end_time_attrs = [issue_metrics_table[:first_associated_with_milestone_at],
issue_metrics_table[:first_added_to_board_at]]
@projections = [issue_table[:title],
issue_table[:iid],
issue_table[:id],
issue_table[:created_at],
issue_table[:author_id]]
end
end
end
module Gitlab
module CycleAnalytics
class PlanConfig < BaseConfig
@start_time_attrs = issue_metrics_table[:first_associated_with_milestone_at]
@end_time_attrs = [issue_metrics_table[:first_added_to_board_at],
issue_metrics_table[:first_mentioned_in_commit_at]]
@projections = [mr_diff_table[:st_commits].as('commits'),
issue_metrics_table[:first_mentioned_in_commit_at]]
def self.query(base_query)
base_query.join(mr_diff_table).on(mr_diff_table[:merge_request_id].eq(mr_table[:id]))
end
end
end
end
module Gitlab
module CycleAnalytics
class ProductionConfig < BaseConfig
@start_time_attrs = issue_table[:created_at]
@end_time_attrs = mr_metrics_table[:first_deployed_to_production_at]
@projections = [issue_table[:title],
issue_table[:iid],
issue_table[:id],
issue_table[:created_at],
issue_table[:author_id]]
end
end
end
module Gitlab
module CycleAnalytics
class QueryConfig
include MetricsFetcher
def self.get(*args)
new(*args).get
end
def initialize(stage)
@stage = stage
end
def get
public_send(@stage).freeze if self.respond_to?(@stage)
end
def issue
{
start_time_attrs: issue_table[:created_at],
end_time_attrs: [issue_metrics_table[:first_associated_with_milestone_at],
issue_metrics_table[:first_added_to_board_at]],
projections: [issue_table[:title],
issue_table[:iid],
issue_table[:id],
issue_table[:created_at],
issue_table[:author_id]]
}
end
def plan
{
start_time_attrs: issue_metrics_table[:first_associated_with_milestone_at],
end_time_attrs: [issue_metrics_table[:first_added_to_board_at],
issue_metrics_table[:first_mentioned_in_commit_at]],
projections: [mr_diff_table[:st_commits].as('commits'),
issue_metrics_table[:first_mentioned_in_commit_at]]
}
end
def code
{
start_time_attrs: issue_metrics_table[:first_mentioned_in_commit_at],
end_time_attrs: mr_table[:created_at],
projections: [mr_table[:title],
mr_table[:iid],
mr_table[:id],
mr_table[:created_at],
mr_table[:state],
mr_table[:author_id]],
order: mr_table[:created_at]
}
end
def test
{
start_time_attrs: mr_metrics_table[:latest_build_started_at],
end_time_attrs: mr_metrics_table[:latest_build_finished_at],
projections: [build_table[:id]],
order: build_table[:created_at]
}
end
def review
{
start_time_attrs: mr_table[:created_at],
end_time_attrs: mr_metrics_table[:merged_at],
projections: [mr_table[:title],
mr_table[:iid],
mr_table[:id],
mr_table[:created_at],
mr_table[:state],
mr_table[:author_id]]
}
end
def staging
{
start_time_attrs: mr_metrics_table[:merged_at],
end_time_attrs: mr_metrics_table[:first_deployed_to_production_at],
projections: [build_table[:id]],
order: build_table[:created_at]
}
end
def production
{
start_time_attrs: issue_table[:created_at],
end_time_attrs: mr_metrics_table[:first_deployed_to_production_at],
projections: [issue_table[:title],
issue_table[:iid],
issue_table[:id],
issue_table[:created_at],
issue_table[:author_id]]
}
end
end
end
end
module Gitlab
module CycleAnalytics
class ReviewConfig < BaseConfig
@start_time_attrs = mr_table[:created_at]
@end_time_attrs = mr_metrics_table[:merged_at]
@projections = [mr_table[:title],
mr_table[:iid],
mr_table[:id],
mr_table[:created_at],
mr_table[:state],
mr_table[:author_id]]
end
end
end
module Gitlab
module CycleAnalytics
class StagingConfig < BaseConfig
@start_time_attrs = mr_metrics_table[:merged_at]
@end_time_attrs = mr_metrics_table[:first_deployed_to_production_at]
@projections = [build_table[:id]]
@order = build_table[:created_at]
def self.query(base_query)
base_query.join(build_table).on(mr_metrics_table[:pipeline_id].eq(build_table[:commit_id]))
end
end
end
end
module Gitlab
module CycleAnalytics
class TestConfig < BaseConfig
@start_time_attrs = mr_metrics_table[:latest_build_started_at]
@end_time_attrs = mr_metrics_table[:latest_build_finished_at]
@projections = [build_table[:id]]
@order = build_table[:created_at]
def self.query(base_query)
base_query.join(build_table).on(mr_metrics_table[:pipeline_id].eq(build_table[:commit_id]))
end
end
end
end
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_config_spec'
describe Gitlab::CycleAnalytics::CodeConfig do
it_behaves_like 'default query config'
it 'has the default order' do
expect(described_class.order).not_to eq(described_class.start_time_attrs)
end
end
...@@ -9,6 +9,8 @@ describe Gitlab::CycleAnalytics::Events do ...@@ -9,6 +9,8 @@ describe Gitlab::CycleAnalytics::Events do
subject { described_class.new(project: project, options: { from: from_date }) } subject { described_class.new(project: project, options: { from: from_date }) }
before do before do
allow_any_instance_of(Gitlab::ReferenceExtractor).to receive(:issues).and_return([context])
setup(context) setup(context)
end end
...@@ -317,6 +319,8 @@ describe Gitlab::CycleAnalytics::Events do ...@@ -317,6 +319,8 @@ describe Gitlab::CycleAnalytics::Events do
def setup(context) def setup(context)
milestone = create(:milestone, project: project) milestone = create(:milestone, project: project)
context.update(milestone: milestone) context.update(milestone: milestone)
create_merge_request_closing_issue(context) mr = create_merge_request_closing_issue(context)
ProcessCommitWorker.new.perform(project.id, user.id, mr.commits.last.sha)
end end
end end
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_config_spec'
describe Gitlab::CycleAnalytics::IssueConfig do
it_behaves_like 'default query config'
it 'has the default order' do
expect(described_class.order).to eq(described_class.start_time_attrs)
end
end
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_config_spec'
describe Gitlab::CycleAnalytics::PlanConfig do
it_behaves_like 'default query config'
it 'has the default order' do
expect(described_class.order).to eq(described_class.start_time_attrs)
end
end
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_config_spec'
describe Gitlab::CycleAnalytics::ProductionConfig do
it_behaves_like 'default query config'
it 'has the default order' do
expect(described_class.order).to eq(described_class.start_time_attrs)
end
end
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_config_spec'
describe Gitlab::CycleAnalytics::ReviewConfig do
it_behaves_like 'default query config'
it 'has the default order' do
expect(described_class.order).to eq(described_class.start_time_attrs)
end
end
require 'spec_helper'
shared_examples 'default query config' do
it 'has the start attributes' do
expect(described_class.start_time_attrs).not_to be_nil
end
it 'has the end attributes' do
expect(described_class.end_time_attrs ).not_to be_nil
end
it 'has the projection attributes' do
expect(described_class.projections).not_to be_nil
end
end
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_config_spec'
describe Gitlab::CycleAnalytics::StagingConfig do
it_behaves_like 'default query config'
it 'has the default order' do
expect(described_class.order).not_to eq(described_class.start_time_attrs)
end
end
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_config_spec'
describe Gitlab::CycleAnalytics::TestConfig do
it_behaves_like 'default query config'
it 'has the default order' do
expect(described_class.order).not_to eq(described_class.start_time_attrs)
end
end
...@@ -12,6 +12,8 @@ describe 'cycle analytics events' do ...@@ -12,6 +12,8 @@ describe 'cycle analytics events' do
deploy_master deploy_master
login_as(user) login_as(user)
allow_any_instance_of(Gitlab::ReferenceExtractor).to receive(:issues).and_return([context])
end end
it 'lists the issue events' do it 'lists the issue events' do
...@@ -143,6 +145,6 @@ describe 'cycle analytics events' do ...@@ -143,6 +145,6 @@ describe 'cycle analytics events' do
merge_merge_requests_closing_issue(issue) merge_merge_requests_closing_issue(issue)
Issue::Metrics.update_all(first_mentioned_in_commit_at: mr.commits.last.committed_date) ProcessCommitWorker.new.perform(project.id, user.id, mr.commits.last.sha)
end end
end end
...@@ -2,7 +2,7 @@ require 'spec_helper' ...@@ -2,7 +2,7 @@ require 'spec_helper'
describe AnalyticsGenericEntity do describe AnalyticsGenericEntity do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:entity_hash) { let(:entity_hash) do
{ {
total_time: "172802.724419", total_time: "172802.724419",
title: "Eos voluptatem inventore in sed.", title: "Eos voluptatem inventore in sed.",
...@@ -11,7 +11,7 @@ describe AnalyticsGenericEntity do ...@@ -11,7 +11,7 @@ describe AnalyticsGenericEntity do
created_at: "2016-11-12 15:04:02.948604", created_at: "2016-11-12 15:04:02.948604",
author: user, author: user,
} }
} end
let(:project) { create(:empty_project) } let(:project) { create(:empty_project) }
let(:request) { EntityRequest.new(project: project, entity: :merge_request) } let(:request) { EntityRequest.new(project: project, entity: :merge_request) }
......
...@@ -10,7 +10,7 @@ describe AnalyticsGenericSerializer do ...@@ -10,7 +10,7 @@ describe AnalyticsGenericSerializer do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:json) { serializer.as_json } let(:json) { serializer.as_json }
let(:project) { create(:project) } let(:project) { create(:project) }
let(:resource) { let(:resource) do
{ {
total_time: "172802.724419", total_time: "172802.724419",
title: "Eos voluptatem inventore in sed.", title: "Eos voluptatem inventore in sed.",
...@@ -19,7 +19,7 @@ describe AnalyticsGenericSerializer do ...@@ -19,7 +19,7 @@ describe AnalyticsGenericSerializer do
created_at: "2016-11-12 15:04:02.948604", created_at: "2016-11-12 15:04:02.948604",
author: user, author: user,
} }
} end
context 'when there is a single object provided' do context 'when there is a single object provided' do
it 'it generates payload for single object' do it 'it generates payload for single object' 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