refactor events facade so it uses separate classes and refactor query stuff

This commit is contained in:
James Lopez 2016-11-17 13:22:27 +01:00
parent f9de157e70
commit ed39d61d74
33 changed files with 930 additions and 677 deletions

View file

@ -19,7 +19,7 @@ module Projects
end
def test
@options = { from: start_date(events_params), branch: events_params[:branch_name] }
@options[:branch] = events_params[:branch_name]
render_events(events.test_events)
end

View file

@ -1,9 +1,8 @@
class CycleAnalytics
include Gitlab::CycleAnalytics::MetricsFetcher
def initialize(project, from:)
@project = project
@from = from
@fetcher = Gitlab::CycleAnalytics::MetricsFetcher.new(project: project, from: from, branch: nil)
end
def summary
@ -11,45 +10,45 @@ class CycleAnalytics
end
def issue
calculate_metric(:issue,
@fetcher.calculate_metric(:issue,
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,
@fetcher.calculate_metric(:plan,
[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,
@fetcher.calculate_metric(:code,
Issue::Metrics.arel_table[:first_mentioned_in_commit_at],
MergeRequest.arel_table[:created_at])
end
def test
calculate_metric(:test,
@fetcher.calculate_metric(:test,
MergeRequest::Metrics.arel_table[:latest_build_started_at],
MergeRequest::Metrics.arel_table[:latest_build_finished_at])
end
def review
calculate_metric(:review,
@fetcher.calculate_metric(:review,
MergeRequest.arel_table[:created_at],
MergeRequest::Metrics.arel_table[:merged_at])
end
def staging
calculate_metric(:staging,
@fetcher.calculate_metric(:staging,
MergeRequest::Metrics.arel_table[:merged_at],
MergeRequest::Metrics.arel_table[:first_deployed_to_production_at])
end
def production
calculate_metric(:production,
@fetcher.calculate_metric(:production,
Issue.arel_table[:created_at],
MergeRequest::Metrics.arel_table[:first_deployed_to_production_at])
end

View file

@ -1,6 +1,4 @@
class AnalyticsGenericSerializer < BaseSerializer
entity AnalyticsGenericEntity
def represent(resource, opts = {})
resource.symbolize_keys!

View file

@ -1,9 +1,8 @@
class AnalyticsGenericEntity < Grape::Entity
class AnalyticsIssueEntity < Grape::Entity
include RequestAwareEntity
include EntityDateHelper
expose :title
expose :state, if: ->(_instance, options) { options[:request].entity == :merge_request }
expose :author, using: UserEntity
expose :iid do |object|
@ -19,7 +18,7 @@ class AnalyticsGenericEntity < Grape::Entity
end
expose :url do |object|
url_to("namespace_project_#{request.entity}".to_sym, id: object[:iid].to_s)
url_to(:namespace_project_issue, id: object[:iid].to_s)
end
private

View file

@ -0,0 +1,3 @@
class AnalyticsIssueSerializer < AnalyticsGenericSerializer
entity AnalyticsIssueEntity
end

View file

@ -0,0 +1,7 @@
class AnalyticsMergeRequestEntity < AnalyticsIssueEntity
expose :state
expose :url do |object|
url_to(:namespace_project_merge_request, id: object[:iid].to_s)
end
end

View file

@ -0,0 +1,3 @@
class AnalyticsMergeRequestSerializer < AnalyticsGenericSerializer
entity AnalyticsMergeRequestEntity
end

File diff suppressed because it is too large Load diff

View file

@ -1,17 +0,0 @@
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

View file

@ -0,0 +1,27 @@
module Gitlab
module CycleAnalytics
class BaseEvent
extend MetricsTables
class << self
attr_reader :stage, :start_time_attrs, :end_time_attrs, :projections
def order
@order || @start_time_attrs
end
def query(base_query); end
def fetch(query)
query.execute(self).map { |event| serialize(event, query) }
end
private
def serialize(event, query)
raise NotImplementedError.new("Expected #{self.name} to implement serialize(event)")
end
end
end
end
end

View file

@ -1,6 +1,7 @@
module Gitlab
module CycleAnalytics
class CodeConfig < BaseConfig
class CodeEvent < BaseEvent
@stage = :code
@start_time_attrs = issue_metrics_table[:first_mentioned_in_commit_at]
@end_time_attrs = mr_table[:created_at]
@ -13,6 +14,12 @@ module Gitlab
mr_table[:author_id]]
@order = mr_table[:created_at]
def self.serialize(event, query)
event['author'] = User.find(event.delete('author_id'))
AnalyticsMergeRequestSerializer.new(project: query.project).represent(event).as_json
end
end
end
end

View file

@ -1,6 +1,6 @@
module Gitlab
module CycleAnalytics
class ReviewConfig < BaseConfig
class TestEvent < BaseEvent
@start_time_attrs = mr_table[:created_at]
@end_time_attrs = mr_metrics_table[:merged_at]

View file

@ -3,73 +3,35 @@ module Gitlab
class Events
def initialize(project:, options:)
@project = project
@fetcher = EventsFetcher.new(project: project, options: options)
@query = EventsQuery.new(project: project, options: options)
end
def issue_events
@fetcher.fetch(stage: :issue).map { |event| serialize_event(event) }
IssueEvent.fetch(@query)
end
def plan_events
@fetcher.fetch(stage: :plan).map do |event|
st_commit = first_time_reference_commit(event.delete('commits'), event)
next unless st_commit
serialize_commit(event, st_commit)
end
PlanEvent.fetch(@query)
end
def code_events
@fetcher.fetch(stage: :code).map { |event| serialize_event(event, entity: :merge_request) }
CodeEvent.fetch(@query)
end
def test_events
@fetcher.fetch(stage: :test).map { |event| serialize_build_event(event) }
TestEvent.fetch(@query)
end
def review_events
@fetcher.fetch(stage: :review).map { |event| serialize_event(event, entity: :merge_request) }
ReviewEvent.fetch(@query)
end
def staging_events
@fetcher.fetch(stage: :staging).map { |event| serialize_build_event(event) }
StagingEvent.fetch(@query)
end
def production_events
@fetcher.fetch(stage: :production).map { |event| serialize_event(event) }
end
private
def serialize_event(event, entity: :issue)
event['author'] = User.find(event.delete('author_id'))
AnalyticsGenericSerializer.new(project: @project, entity: entity).represent(event).as_json
end
def serialize_build_event(event)
build = ::Ci::Build.find(event['id'])
AnalyticsBuildSerializer.new.represent(build).as_json
end
def first_time_reference_commit(commits, event)
YAML.load(commits).find do |commit|
next unless commit[:committed_date] && event['first_mentioned_in_commit_at']
commit[:committed_date].to_i == DateTime.parse(event['first_mentioned_in_commit_at'].to_s).to_i
end
end
def serialize_commit(event, st_commit)
commit = Commit.new(Gitlab::Git::Commit.new(st_commit), @project)
AnalyticsCommitSerializer.new(project: @project, total_time: event['total_time']).represent(commit).as_json
end
def interval_in_words(diff)
"#{distance_of_time_in_words(diff.to_f)} ago"
ProductionEvent.fetch(@query)
end
end
end

View file

@ -6,9 +6,7 @@ module Gitlab
end
def fetch(stage:)
@query.execute(stage) do |stage_class, base_query|
stage_class.query(base_query)
end
@query.execute(stage)
end
end
end

View file

@ -1,30 +1,30 @@
module Gitlab
module CycleAnalytics
class EventsQuery
include MetricsFetcher
attr_reader :project
def initialize(project:, options: {})
@project = project
@from = options[:from]
@branch = options[:branch]
@fetcher = Gitlab::CycleAnalytics::MetricsFetcher.new(project: project, from: @from, branch: @branch)
end
def execute(stage, &block)
@stage = stage
query = build_query(&block)
def execute(stage_class)
@stage_class = stage_class
ActiveRecord::Base.connection.exec_query(query.to_sql)
end
private
def build_query
base_query = base_query_for(@stage)
diff_fn = subtract_datetimes_diff(base_query, stage_class.start_time_attrs, stage_class.end_time_attrs)
def query
base_query = @fetcher.base_query_for(@stage_class.stage)
diff_fn = @fetcher.subtract_datetimes_diff(base_query, @stage_class.start_time_attrs, @stage_class.end_time_attrs)
yield(stage_class, base_query) if block_given?
@stage_class.query(base_query)
base_query.project(extract_epoch(diff_fn).as('total_time'), *stage_class.projections).order(stage_class.order.desc)
base_query.project(extract_epoch(diff_fn).as('total_time'), *@stage_class.projections).order(@stage_class.order.desc)
end
def extract_epoch(arel_attribute)
@ -32,10 +32,6 @@ module Gitlab
Arel.sql(%Q{EXTRACT(EPOCH FROM (#{arel_attribute.to_sql}))})
end
def stage_class
@stage_class ||= "Gitlab::CycleAnalytics::#{@stage.to_s.camelize}Config".constantize
end
end
end
end

View file

@ -1,6 +1,7 @@
module Gitlab
module CycleAnalytics
class IssueConfig < BaseConfig
class IssueEvent < BaseEvent
@stage = :issue
@start_time_attrs = issue_table[:created_at]
@end_time_attrs = [issue_metrics_table[:first_associated_with_milestone_at],
@ -11,6 +12,12 @@ module Gitlab
issue_table[:id],
issue_table[:created_at],
issue_table[:author_id]]
def self.serialize(event, query)
event['author'] = User.find(event.delete('author_id'))
AnalyticsIssueSerializer.new(project: query.project).represent(event).as_json
end
end
end
end

View file

@ -1,12 +1,18 @@
module Gitlab
module CycleAnalytics
module MetricsFetcher
class MetricsFetcher
include Gitlab::Database::Median
include Gitlab::Database::DateTime
include MetricsTables
DEPLOYMENT_METRIC_STAGES = %i[production staging]
private
def initialize(project:, from:, branch:)
@project = project
@project = project
@from = from
@branch = branch
end
def calculate_metric(name, start_time_attrs, end_time_attrs)
cte_table = Arel::Table.new("cte_table_for_#{name}")
@ -49,38 +55,6 @@ module Gitlab
query
end
def mr_metrics_table
MergeRequest::Metrics.arel_table
end
def mr_table
MergeRequest.arel_table
end
def mr_diff_table
MergeRequestDiff.arel_table
end
def mr_closing_issues_table
MergeRequestsClosingIssues.arel_table
end
def issue_table
Issue.arel_table
end
def issue_metrics_table
Issue::Metrics.arel_table
end
def user_table
User.arel_table
end
def build_table
::CommitStatus.arel_table
end
end
end
end

View file

@ -0,0 +1,37 @@
module Gitlab
module CycleAnalytics
module MetricsTables
def mr_metrics_table
MergeRequest::Metrics.arel_table
end
def mr_table
MergeRequest.arel_table
end
def mr_diff_table
MergeRequestDiff.arel_table
end
def mr_closing_issues_table
MergeRequestsClosingIssues.arel_table
end
def issue_table
Issue.arel_table
end
def issue_metrics_table
Issue::Metrics.arel_table
end
def user_table
User.arel_table
end
def build_table
::CommitStatus.arel_table
end
end
end
end

View file

@ -1,17 +0,0 @@
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

View file

@ -0,0 +1,44 @@
module Gitlab
module CycleAnalytics
class PlanEvent < BaseEvent
@stage = :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]]
class << self
def query(base_query)
base_query.join(mr_diff_table).on(mr_diff_table[:merge_request_id].eq(mr_table[:id]))
end
private
def serialize(event, query)
st_commit = first_time_reference_commit(event.delete('commits'), event)
return unless st_commit
serialize_commit(event, st_commit, query)
end
def first_time_reference_commit(commits, event)
YAML.load(commits).find do |commit|
next unless commit[:committed_date] && event['first_mentioned_in_commit_at']
commit[:committed_date].to_i == DateTime.parse(event['first_mentioned_in_commit_at'].to_s).to_i
end
end
def serialize_commit(event, st_commit, query)
commit = Commit.new(Gitlab::Git::Commit.new(st_commit), @project)
AnalyticsCommitSerializer.new(project: query.project, total_time: event['total_time']).represent(commit).as_json
end
end
end
end
end

View file

@ -1,6 +1,7 @@
module Gitlab
module CycleAnalytics
class ProductionConfig < BaseConfig
class ProductionEvent < BaseEvent
@stage = :production
@start_time_attrs = issue_table[:created_at]
@end_time_attrs = mr_metrics_table[:first_deployed_to_production_at]
@ -10,6 +11,12 @@ module Gitlab
issue_table[:id],
issue_table[:created_at],
issue_table[:author_id]]
def self.serialize(event, query)
event['author'] = User.find(event.delete('author_id'))
AnalyticsIssueSerializer.new(project: query.project).represent(event).as_json
end
end
end
end

View file

@ -0,0 +1,21 @@
module Gitlab
module CycleAnalytics
class ReviewEvent < BaseEvent
@stage = :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]]
def self.serialize(event, _query)
event['author'] = User.find(event.delete('author_id'))
AnalyticsMergeRequestSerializer.new(project: query.project).represent(event).as_json
end
end
end
end

View file

@ -1,6 +1,7 @@
module Gitlab
module CycleAnalytics
class StagingConfig < BaseConfig
class StagingEvent < BaseEvent
@stage = :staging
@start_time_attrs = mr_metrics_table[:merged_at]
@end_time_attrs = mr_metrics_table[:first_deployed_to_production_at]
@projections = [build_table[:id]]
@ -9,6 +10,12 @@ module Gitlab
def self.query(base_query)
base_query.join(build_table).on(mr_metrics_table[:pipeline_id].eq(build_table[:commit_id]))
end
def self.serialize(event, _query)
build = ::Ci::Build.find(event['id'])
AnalyticsBuildSerializer.new.represent(build).as_json
end
end
end
end

View file

@ -1,6 +1,7 @@
module Gitlab
module CycleAnalytics
class TestConfig < BaseConfig
class TestEvent < BaseEvent
@stage = :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]]
@ -9,6 +10,12 @@ module Gitlab
def self.query(base_query)
base_query.join(build_table).on(mr_metrics_table[:pipeline_id].eq(build_table[:commit_id]))
end
def self.serialize(event, _query)
build = ::Ci::Build.find(event['id'])
AnalyticsBuildSerializer.new.represent(build).as_json
end
end
end
end

View file

@ -1,7 +1,7 @@
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_config_spec'
require 'lib/gitlab/cycle_analytics/shared_event_spec'
describe Gitlab::CycleAnalytics::CodeConfig do
describe Gitlab::CycleAnalytics::CodeEvent do
it_behaves_like 'default query config'
it 'has the default order' do

View file

@ -1,7 +1,7 @@
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_config_spec'
require 'lib/gitlab/cycle_analytics/shared_event_spec'
describe Gitlab::CycleAnalytics::IssueConfig do
describe Gitlab::CycleAnalytics::IssueEvent do
it_behaves_like 'default query config'
it 'has the default order' do

View file

@ -1,7 +1,7 @@
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_config_spec'
require 'lib/gitlab/cycle_analytics/shared_event_spec'
describe Gitlab::CycleAnalytics::PlanConfig do
describe Gitlab::CycleAnalytics::PlanEvent do
it_behaves_like 'default query config'
it 'has the default order' do

View file

@ -1,7 +1,7 @@
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_config_spec'
require 'lib/gitlab/cycle_analytics/shared_event_spec'
describe Gitlab::CycleAnalytics::ProductionConfig do
describe Gitlab::CycleAnalytics::ProductionEvent do
it_behaves_like 'default query config'
it 'has the default order' do

View file

@ -1,7 +1,7 @@
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_config_spec'
require 'lib/gitlab/cycle_analytics/shared_event_spec'
describe Gitlab::CycleAnalytics::ReviewConfig do
describe Gitlab::CycleAnalytics::ReviewEvent do
it_behaves_like 'default query config'
it 'has the default order' do

View file

@ -12,4 +12,8 @@ shared_examples 'default query config' do
it 'has the projection attributes' do
expect(described_class.projections).not_to be_nil
end
it 'implements the fetch method' do
expect(described_class.fetch).not_to raise_error
end
end

View file

@ -1,7 +1,7 @@
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_config_spec'
require 'lib/gitlab/cycle_analytics/shared_event_spec'
describe Gitlab::CycleAnalytics::StagingConfig do
describe Gitlab::CycleAnalytics::StagingEvent do
it_behaves_like 'default query config'
it 'has the default order' do

View file

@ -1,7 +1,7 @@
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_config_spec'
require 'lib/gitlab/cycle_analytics/shared_event_spec'
describe Gitlab::CycleAnalytics::TestConfig do
describe Gitlab::CycleAnalytics::TestEvent do
it_behaves_like 'default query config'
it 'has the default order' do

View file

@ -1,6 +1,6 @@
require 'spec_helper'
describe AnalyticsGenericEntity do
describe AnalyticsIssueEntity do
let(:user) { create(:user) }
let(:entity_hash) do
{