1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
|
# frozen_string_literal: true
module Gitlab
module Analytics
module CycleAnalytics
class RecordsFetcher
include Gitlab::Utils::StrongMemoize
include StageQueryHelpers
include Gitlab::CycleAnalytics::MetricsTables
MAX_RECORDS = 20
MAPPINGS = {
Issue => {
serializer_class: AnalyticsIssueSerializer,
includes_for_query: { project: { namespace: [:route] }, author: [] },
columns_for_select: %I[title iid id created_at author_id project_id]
},
MergeRequest => {
serializer_class: AnalyticsMergeRequestSerializer,
includes_for_query: { target_project: [:namespace], author: [] },
columns_for_select: %I[title iid id created_at author_id state_id target_project_id]
}
}.freeze
delegate :subject_class, to: :stage
def initialize(stage:, query:, params: {})
@stage = stage
@query = query
@params = params
@sort = params[:sort] || :end_event
@direction = params[:direction] || :desc
@page = params[:page] || 1
@per_page = MAX_RECORDS
end
# rubocop: disable CodeReuse/ActiveRecord
def serialized_records
strong_memoize(:serialized_records) do
# special case (legacy): 'Test' and 'Staging' stages should show Ci::Build records
if default_test_stage? || default_staging_stage?
ci_build_join = mr_metrics_table
.join(build_table)
.on(mr_metrics_table[:pipeline_id].eq(build_table[:commit_id]))
.join_sources
records = ordered_and_limited_query
.joins(ci_build_join)
.select(build_table[:id], *time_columns)
yield records if block_given?
ci_build_records = preload_ci_build_associations(records)
AnalyticsBuildSerializer.new.represent(ci_build_records.map { |e| e['build'] })
else
records = ordered_and_limited_query.select(*columns, *time_columns)
yield records if block_given?
records = preload_associations(records)
records.map do |record|
project = record.project
attributes = record.attributes.merge({
project_path: project.path,
namespace_path: project.namespace.route.path,
author: record.author
})
serializer.represent(attributes)
end
end
end
end
# rubocop: enable CodeReuse/ActiveRecord
private
attr_reader :stage, :query, :params, :sort, :direction, :page, :per_page
def columns
MAPPINGS.fetch(subject_class).fetch(:columns_for_select).map do |column_name|
subject_class.arel_table[column_name]
end
end
def default_test_stage?
stage.matches_with_stage_params?(Gitlab::Analytics::CycleAnalytics::DefaultStages.params_for_test_stage)
end
def default_staging_stage?
stage.matches_with_stage_params?(Gitlab::Analytics::CycleAnalytics::DefaultStages.params_for_staging_stage)
end
def serializer
MAPPINGS.fetch(subject_class).fetch(:serializer_class).new
end
# rubocop: disable CodeReuse/ActiveRecord
def preload_ci_build_associations(records)
results = records.map(&:attributes)
Gitlab::CycleAnalytics::Updater.update!(results, from: 'id', to: 'build', klass: ::Ci::Build.includes({ project: [:namespace], user: [], pipeline: [] }))
end
# rubocop: enable CodeReuse/ActiveRecord
def ordered_and_limited_query
strong_memoize(:ordered_and_limited_query) do
order_by(query, sort, direction, columns).page(page).per(per_page).without_count
end
end
# rubocop: disable CodeReuse/ActiveRecord
def preload_associations(records)
# using preloader instead of includes to avoid AR generating a large column list
ActiveRecord::Associations::Preloader.new.preload(
records,
MAPPINGS.fetch(subject_class).fetch(:includes_for_query)
)
records
end
# rubocop: enable CodeReuse/ActiveRecord
def time_columns
[
stage.start_event.timestamp_projection.as('start_event_timestamp'),
stage.end_event.timestamp_projection.as('end_event_timestamp'),
round_duration_to_seconds.as('total_time')
]
end
end
end
end
end
Gitlab::Analytics::CycleAnalytics::RecordsFetcher.prepend_if_ee('EE::Gitlab::Analytics::CycleAnalytics::RecordsFetcher')
|