Commit 275292de authored by James Lopez's avatar James Lopez

Refactor the SQL/query stuff into a dynamic class. Also merged all the config in a single hash.

parent 954e1892
...@@ -13,11 +13,11 @@ module Gitlab ...@@ -13,11 +13,11 @@ module Gitlab
def issue_events 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 { |event| parse_event(event) } @fetcher.fetch(stage: :issue).each { |event| parse_event(event) }
end end
def plan_events def plan_events
@fetcher.fetch_plan_events.each do |event| @fetcher.fetch(stage: :plan).each do |event|
event['total_time'] = distance_of_time_in_words(event['total_time'].to_f) event['total_time'] = distance_of_time_in_words(event['total_time'].to_f)
commits = event.delete('commits') commits = event.delete('commits')
event['commit'] = first_time_reference_commit(commits, event) event['commit'] = first_time_reference_commit(commits, event)
...@@ -25,22 +25,22 @@ module Gitlab ...@@ -25,22 +25,22 @@ module Gitlab
end end
def code_events def code_events
@fetcher.fetch_code_events.each { |event| parse_event(event) } @fetcher.fetch(stage: :code).each { |event| parse_event(event) }
end end
def test_events def test_events
@fetcher.fetch_test_events.each do |event| @fetcher.fetch(stage: :test).each do |event|
event['total_time'] = distance_of_time_in_words(event['total_time'].to_f) event['total_time'] = distance_of_time_in_words(event['total_time'].to_f)
event['pipeline'] = ::Ci::Pipeline.find_by_id(event['ci_commit_id']) # we may not have a pipeline event['pipeline'] = ::Ci::Pipeline.find_by_id(event['ci_commit_id']) # we may not have a pipeline
end end
end end
def review_events def review_events
@fetcher.fetch_review_events.each { |event| parse_event(event) } @fetcher.fetch(stage: :review).each { |event| parse_event(event) }
end end
def staging_events def staging_events
@fetcher.fetch_staging_events.each do |event| @fetcher.fetch(stage: :staging).each do |event|
event['total_time'] = distance_of_time_in_words(event['total_time'].to_f) event['total_time'] = distance_of_time_in_words(event['total_time'].to_f)
event['pipeline'] = ::Ci::Pipeline.find_by_id(event['ci_commit_id']) # we may not have a pipeline event['pipeline'] = ::Ci::Pipeline.find_by_id(event['ci_commit_id']) # we may not have a pipeline
end end
......
...@@ -3,121 +3,73 @@ module Gitlab ...@@ -3,121 +3,73 @@ module Gitlab
class EventsFetcher class EventsFetcher
include MetricsFetcher include MetricsFetcher
EVENTS_CONFIG = {
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[:created_at], user_table[:name]],
project: @project,
from: @from
},
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]]
},
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[:created_at], user_table[:name]],
order: mr_table[:created_at]
},
test: {
start_time_attrs: mr_metrics_table[:latest_build_started_at],
end_time_attrs: mr_metrics_table[:latest_build_finished_at],
projections: mr_metrics_table[:ci_commit_id],
order: mr_table[:created_at]
},
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[:created_at], user_table[:name]]
},
staging: {
start_time_attrs: mr_metrics_table[:merged_at],
end_time_attrs: mr_metrics_table[:first_deployed_to_production_at],
projections: mr_metrics_table[:ci_commit_id]
}
}.freeze
def initialize(project:, from:) def initialize(project:, from:)
@project = project @project = project
@from = from @from = from
@query = EventsQuery.new(project: project, from: from)
end end
def fetch_issue_events def fetch(stage:)
base_query = base_query_for(:issue) custom_query = "#{stage}_custom_query".to_sym
diff_fn = subtract_datetimes_diff(base_query, issue_table[:created_at], issue_attributes)
query = base_query.join(user_table).on(issue_table[:author_id].eq(user_table[:id])).
project(extract_epoch(diff_fn).as('total_time'), *issue_projections).
order(issue_table[:created_at].desc)
execute(query)
end
def fetch_plan_events
base_query = base_query_for(:plan)
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_metrics_table[:first_associated_with_milestone_at].desc)
execute(query) @query.execute(stage, EVENTS_CONFIG[stage]) do |base_query|
public_send(custom_query, base_query) if self.respond_to?(custom_query)
end 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],
mr_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 end
def fetch_test_events def issue_custom_query(base_query)
base_query = base_query_for(:test) base_query.join(user_table).on(issue_table[:author_id].eq(user_table[:id]))
diff_fn = subtract_datetimes_diff(base_query,
mr_metrics_table[:latest_build_started_at],
mr_metrics_table[:latest_build_finished_at])
query = base_query.project(extract_epoch(diff_fn).as('total_time'), mr_metrics_table[:ci_commit_id]).
order(mr_table[:created_at].desc)
execute(query)
end end
def fetch_review_events def plan_custom_query(base_query)
base_query = base_query_for(:review) base_query.join(mr_diff_table).on(mr_diff_table[:merge_request_id].eq(mr_table[:id]))
diff_fn = subtract_datetimes_diff(base_query,
mr_table[:created_at],
mr_metrics_table[:merged_at])
query = base_query.join(user_table).on(mr_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 end
def fetch_staging_events def code_custom_query(base_query)
base_query = base_query_for(:staging) base_query.join(user_table).on(issue_table[:author_id].eq(user_table[:id]))
diff_fn = subtract_datetimes_diff(base_query,
mr_metrics_table[:merged_at],
mr_metrics_table[:first_deployed_to_production_at])
query = base_query.project(extract_epoch(diff_fn).as('total_time'), mr_metrics_table[:ci_commit_id]).
order(mr_table[:created_at].desc)
execute(query)
end
private
def issue_attributes
[issue_metrics_table[:first_associated_with_milestone_at],
issue_metrics_table[:first_added_to_board_at]]
end
def plan_attributes
[issue_metrics_table[:first_added_to_board_at],
issue_metrics_table[:first_mentioned_in_commit_at]]
end
def issue_projections
[issue_table[:title], issue_table[:iid], issue_table[:created_at], User.arel_table[:name]]
end
def plan_projections
[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
def extract_epoch(arel_attribute)
return arel_attribute unless Gitlab::Database.postgresql?
Arel.sql(%Q{EXTRACT(EPOCH FROM (#{arel_attribute.to_sql}))})
end end
def execute(query) def review_custom_query(base_query)
ActiveRecord::Base.connection.execute(query.to_sql).to_a base_query.join(user_table).on(mr_table[:author_id].eq(user_table[:id]))
end end
end end
end end
......
module Gitlab
module CycleAnalytics
class EventsQuery
include MetricsFetcher
def initialize(project:, from:)
@project = project
@from = from
end
def execute(stage, config, &block)
@stage = stage
@config = config
query = build_query(&block)
ActiveRecord::Base.connection.execute(query.to_sql).to_a
end
private
def build_query
base_query = base_query_for(@stage)
diff_fn = subtract_datetimes_diff(@config[:base_query], @config[:start_time_attrs], @config[:end_time_attrs])
yield base_query if block_given?
base_query.project(extract_epoch(diff_fn).as('total_time'), *@config[:projections]).order(order.desc)
end
def order
@config[:order] || @config[:start_time_attrs]
end
def extract_epoch(arel_attribute)
return arel_attribute unless Gitlab::Database.postgresql?
Arel.sql(%Q{EXTRACT(EPOCH FROM (#{arel_attribute.to_sql}))})
end
end
end
end
...@@ -6,6 +6,10 @@ module Gitlab ...@@ -6,6 +6,10 @@ module Gitlab
DEPLOYMENT_METRIC_STAGES = %i[production staging] DEPLOYMENT_METRIC_STAGES = %i[production staging]
def self.included(klass)
klass.extend self
end
private private
def calculate_metric(name, start_time_attrs, end_time_attrs) def calculate_metric(name, start_time_attrs, end_time_attrs)
...@@ -71,6 +75,10 @@ module Gitlab ...@@ -71,6 +75,10 @@ module Gitlab
def issue_metrics_table def issue_metrics_table
Issue::Metrics.arel_table Issue::Metrics.arel_table
end end
def user_table
User.arel_table
end
end end
end end
end end
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