Commit ed39d61d authored by James Lopez's avatar James Lopez

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

parent f9de157e
...@@ -19,7 +19,7 @@ module Projects ...@@ -19,7 +19,7 @@ module Projects
end end
def test def test
@options = { from: start_date(events_params), branch: events_params[:branch_name] } @options[:branch] = events_params[:branch_name]
render_events(events.test_events) render_events(events.test_events)
end end
......
class CycleAnalytics class CycleAnalytics
include Gitlab::CycleAnalytics::MetricsFetcher
def initialize(project, from:) def initialize(project, from:)
@project = project @project = project
@from = from @from = from
@fetcher = Gitlab::CycleAnalytics::MetricsFetcher.new(project: project, from: from, branch: nil)
end end
def summary def summary
...@@ -11,45 +10,45 @@ class CycleAnalytics ...@@ -11,45 +10,45 @@ class CycleAnalytics
end end
def issue def issue
calculate_metric(:issue, @fetcher.calculate_metric(:issue,
Issue.arel_table[:created_at], Issue.arel_table[:created_at],
[Issue::Metrics.arel_table[:first_associated_with_milestone_at], [Issue::Metrics.arel_table[:first_associated_with_milestone_at],
Issue::Metrics.arel_table[:first_added_to_board_at]]) Issue::Metrics.arel_table[:first_added_to_board_at]])
end end
def plan def plan
calculate_metric(:plan, @fetcher.calculate_metric(:plan,
[Issue::Metrics.arel_table[:first_associated_with_milestone_at], [Issue::Metrics.arel_table[:first_associated_with_milestone_at],
Issue::Metrics.arel_table[:first_added_to_board_at]], Issue::Metrics.arel_table[:first_added_to_board_at]],
Issue::Metrics.arel_table[:first_mentioned_in_commit_at]) Issue::Metrics.arel_table[:first_mentioned_in_commit_at])
end end
def code def code
calculate_metric(:code, @fetcher.calculate_metric(:code,
Issue::Metrics.arel_table[:first_mentioned_in_commit_at], Issue::Metrics.arel_table[:first_mentioned_in_commit_at],
MergeRequest.arel_table[:created_at]) MergeRequest.arel_table[:created_at])
end end
def test def test
calculate_metric(:test, @fetcher.calculate_metric(:test,
MergeRequest::Metrics.arel_table[:latest_build_started_at], MergeRequest::Metrics.arel_table[:latest_build_started_at],
MergeRequest::Metrics.arel_table[:latest_build_finished_at]) MergeRequest::Metrics.arel_table[:latest_build_finished_at])
end end
def review def review
calculate_metric(:review, @fetcher.calculate_metric(:review,
MergeRequest.arel_table[:created_at], MergeRequest.arel_table[:created_at],
MergeRequest::Metrics.arel_table[:merged_at]) MergeRequest::Metrics.arel_table[:merged_at])
end end
def staging def staging
calculate_metric(:staging, @fetcher.calculate_metric(:staging,
MergeRequest::Metrics.arel_table[:merged_at], MergeRequest::Metrics.arel_table[:merged_at],
MergeRequest::Metrics.arel_table[:first_deployed_to_production_at]) MergeRequest::Metrics.arel_table[:first_deployed_to_production_at])
end end
def production def production
calculate_metric(:production, @fetcher.calculate_metric(:production,
Issue.arel_table[:created_at], Issue.arel_table[:created_at],
MergeRequest::Metrics.arel_table[:first_deployed_to_production_at]) MergeRequest::Metrics.arel_table[:first_deployed_to_production_at])
end end
......
class AnalyticsGenericSerializer < BaseSerializer class AnalyticsGenericSerializer < BaseSerializer
entity AnalyticsGenericEntity
def represent(resource, opts = {}) def represent(resource, opts = {})
resource.symbolize_keys! resource.symbolize_keys!
......
class AnalyticsGenericEntity < Grape::Entity class AnalyticsIssueEntity < Grape::Entity
include RequestAwareEntity include RequestAwareEntity
include EntityDateHelper include EntityDateHelper
expose :title expose :title
expose :state, if: ->(_instance, options) { options[:request].entity == :merge_request }
expose :author, using: UserEntity expose :author, using: UserEntity
expose :iid do |object| expose :iid do |object|
...@@ -19,7 +18,7 @@ class AnalyticsGenericEntity < Grape::Entity ...@@ -19,7 +18,7 @@ class AnalyticsGenericEntity < Grape::Entity
end end
expose :url do |object| 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 end
private private
......
class AnalyticsIssueSerializer < AnalyticsGenericSerializer
entity AnalyticsIssueEntity
end
class AnalyticsMergeRequestEntity < AnalyticsIssueEntity
expose :state
expose :url do |object|
url_to(:namespace_project_merge_request, id: object[:iid].to_s)
end
end
class AnalyticsMergeRequestSerializer < AnalyticsGenericSerializer
entity AnalyticsMergeRequestEntity
end
This diff is collapsed.
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 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
module Gitlab module Gitlab
module CycleAnalytics module CycleAnalytics
class CodeConfig < BaseConfig class CodeEvent < BaseEvent
@stage = :code
@start_time_attrs = issue_metrics_table[:first_mentioned_in_commit_at] @start_time_attrs = issue_metrics_table[:first_mentioned_in_commit_at]
@end_time_attrs = mr_table[:created_at] @end_time_attrs = mr_table[:created_at]
...@@ -13,6 +14,12 @@ module Gitlab ...@@ -13,6 +14,12 @@ module Gitlab
mr_table[:author_id]] mr_table[:author_id]]
@order = mr_table[:created_at] @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 end
end end
module Gitlab module Gitlab
module CycleAnalytics module CycleAnalytics
class ReviewConfig < BaseConfig class TestEvent < BaseEvent
@start_time_attrs = mr_table[:created_at] @start_time_attrs = mr_table[:created_at]
@end_time_attrs = mr_metrics_table[:merged_at] @end_time_attrs = mr_metrics_table[:merged_at]
......
...@@ -3,73 +3,35 @@ module Gitlab ...@@ -3,73 +3,35 @@ module Gitlab
class Events class Events
def initialize(project:, options:) def initialize(project:, options:)
@project = project @project = project
@fetcher = EventsFetcher.new(project: project, options: options) @query = EventsQuery.new(project: project, options: options)
end end
def issue_events def issue_events
@fetcher.fetch(stage: :issue).map { |event| serialize_event(event) } IssueEvent.fetch(@query)
end end
def plan_events def plan_events
@fetcher.fetch(stage: :plan).map do |event| PlanEvent.fetch(@query)
st_commit = first_time_reference_commit(event.delete('commits'), event)
next unless st_commit
serialize_commit(event, st_commit)
end
end end
def code_events def code_events
@fetcher.fetch(stage: :code).map { |event| serialize_event(event, entity: :merge_request) } CodeEvent.fetch(@query)
end end
def test_events def test_events
@fetcher.fetch(stage: :test).map { |event| serialize_build_event(event) } TestEvent.fetch(@query)
end end
def review_events def review_events
@fetcher.fetch(stage: :review).map { |event| serialize_event(event, entity: :merge_request) } ReviewEvent.fetch(@query)
end end
def staging_events def staging_events
@fetcher.fetch(stage: :staging).map { |event| serialize_build_event(event) } StagingEvent.fetch(@query)
end end
def production_events def production_events
@fetcher.fetch(stage: :production).map { |event| serialize_event(event) } ProductionEvent.fetch(@query)
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"
end end
end end
end end
......
...@@ -6,9 +6,7 @@ module Gitlab ...@@ -6,9 +6,7 @@ module Gitlab
end end
def fetch(stage:) def fetch(stage:)
@query.execute(stage) do |stage_class, base_query| @query.execute(stage)
stage_class.query(base_query)
end
end end
end end
end end
......
module Gitlab module Gitlab
module CycleAnalytics module CycleAnalytics
class EventsQuery class EventsQuery
include MetricsFetcher attr_reader :project
def initialize(project:, options: {}) def initialize(project:, options: {})
@project = project @project = project
@from = options[:from] @from = options[:from]
@branch = options[:branch] @branch = options[:branch]
@fetcher = Gitlab::CycleAnalytics::MetricsFetcher.new(project: project, from: @from, branch: @branch)
end end
def execute(stage, &block) def execute(stage_class)
@stage = stage @stage_class = stage_class
query = build_query(&block)
ActiveRecord::Base.connection.exec_query(query.to_sql) ActiveRecord::Base.connection.exec_query(query.to_sql)
end end
private private
def build_query def query
base_query = base_query_for(@stage) base_query = @fetcher.base_query_for(@stage_class.stage)
diff_fn = subtract_datetimes_diff(base_query, stage_class.start_time_attrs, stage_class.end_time_attrs) 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 end
def extract_epoch(arel_attribute) def extract_epoch(arel_attribute)
...@@ -32,10 +32,6 @@ module Gitlab ...@@ -32,10 +32,6 @@ 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 Gitlab
module CycleAnalytics module CycleAnalytics
class IssueConfig < BaseConfig class IssueEvent < BaseEvent
@stage = :issue
@start_time_attrs = issue_table[:created_at] @start_time_attrs = issue_table[:created_at]
@end_time_attrs = [issue_metrics_table[:first_associated_with_milestone_at], @end_time_attrs = [issue_metrics_table[:first_associated_with_milestone_at],
...@@ -11,6 +12,12 @@ module Gitlab ...@@ -11,6 +12,12 @@ module Gitlab
issue_table[:id], issue_table[:id],
issue_table[:created_at], issue_table[:created_at],
issue_table[:author_id]] 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 end
end end
module Gitlab module Gitlab
module CycleAnalytics module CycleAnalytics
module MetricsFetcher class MetricsFetcher
include Gitlab::Database::Median include Gitlab::Database::Median
include Gitlab::Database::DateTime include Gitlab::Database::DateTime
include MetricsTables
DEPLOYMENT_METRIC_STAGES = %i[production staging] 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) def calculate_metric(name, start_time_attrs, end_time_attrs)
cte_table = Arel::Table.new("cte_table_for_#{name}") cte_table = Arel::Table.new("cte_table_for_#{name}")
...@@ -49,38 +55,6 @@ module Gitlab ...@@ -49,38 +55,6 @@ module Gitlab
query query
end 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 end
end end
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
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 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
\ No newline at end of file
module Gitlab module Gitlab
module CycleAnalytics module CycleAnalytics
class ProductionConfig < BaseConfig class ProductionEvent < BaseEvent
@stage = :production
@start_time_attrs = issue_table[:created_at] @start_time_attrs = issue_table[:created_at]
@end_time_attrs = mr_metrics_table[:first_deployed_to_production_at] @end_time_attrs = mr_metrics_table[:first_deployed_to_production_at]
...@@ -10,6 +11,12 @@ module Gitlab ...@@ -10,6 +11,12 @@ module Gitlab
issue_table[:id], issue_table[:id],
issue_table[:created_at], issue_table[:created_at],
issue_table[:author_id]] 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 end
end end
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
module Gitlab module Gitlab
module CycleAnalytics module CycleAnalytics
class StagingConfig < BaseConfig class StagingEvent < BaseEvent
@stage = :staging
@start_time_attrs = mr_metrics_table[:merged_at] @start_time_attrs = mr_metrics_table[:merged_at]
@end_time_attrs = mr_metrics_table[:first_deployed_to_production_at] @end_time_attrs = mr_metrics_table[:first_deployed_to_production_at]
@projections = [build_table[:id]] @projections = [build_table[:id]]
...@@ -9,6 +10,12 @@ module Gitlab ...@@ -9,6 +10,12 @@ module Gitlab
def self.query(base_query) def self.query(base_query)
base_query.join(build_table).on(mr_metrics_table[:pipeline_id].eq(build_table[:commit_id])) base_query.join(build_table).on(mr_metrics_table[:pipeline_id].eq(build_table[:commit_id]))
end end
def self.serialize(event, _query)
build = ::Ci::Build.find(event['id'])
AnalyticsBuildSerializer.new.represent(build).as_json
end
end end
end end
end end
module Gitlab module Gitlab
module CycleAnalytics module CycleAnalytics
class TestConfig < BaseConfig class TestEvent < BaseEvent
@stage = :test
@start_time_attrs = mr_metrics_table[:latest_build_started_at] @start_time_attrs = mr_metrics_table[:latest_build_started_at]
@end_time_attrs = mr_metrics_table[:latest_build_finished_at] @end_time_attrs = mr_metrics_table[:latest_build_finished_at]
@projections = [build_table[:id]] @projections = [build_table[:id]]
...@@ -9,6 +10,12 @@ module Gitlab ...@@ -9,6 +10,12 @@ module Gitlab
def self.query(base_query) def self.query(base_query)
base_query.join(build_table).on(mr_metrics_table[:pipeline_id].eq(build_table[:commit_id])) base_query.join(build_table).on(mr_metrics_table[:pipeline_id].eq(build_table[:commit_id]))
end end
def self.serialize(event, _query)
build = ::Ci::Build.find(event['id'])
AnalyticsBuildSerializer.new.represent(build).as_json
end
end end
end end
end end
require 'spec_helper' 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_behaves_like 'default query config'
it 'has the default order' do it 'has the default order' do
......
require 'spec_helper' 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_behaves_like 'default query config'
it 'has the default order' do it 'has the default order' do
......
require 'spec_helper' 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_behaves_like 'default query config'
it 'has the default order' do it 'has the default order' do
......
require 'spec_helper' 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_behaves_like 'default query config'
it 'has the default order' do it 'has the default order' do
......
require 'spec_helper' 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_behaves_like 'default query config'
it 'has the default order' do it 'has the default order' do
......
...@@ -12,4 +12,8 @@ shared_examples 'default query config' do ...@@ -12,4 +12,8 @@ shared_examples 'default query config' do
it 'has the projection attributes' do it 'has the projection attributes' do
expect(described_class.projections).not_to be_nil expect(described_class.projections).not_to be_nil
end end
it 'implements the fetch method' do
expect(described_class.fetch).not_to raise_error
end
end end
require 'spec_helper' 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_behaves_like 'default query config'
it 'has the default order' do it 'has the default order' do
......
require 'spec_helper' 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_behaves_like 'default query config'
it 'has the default order' do it 'has the default order' do
......
require 'spec_helper' require 'spec_helper'
describe AnalyticsGenericEntity do describe AnalyticsIssueEntity do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:entity_hash) do let(:entity_hash) 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