Add the "Plan" Cycle Analytics query.
1. Move from raw SQL to ActiveRecord. 2. Add a non-persisted `CycleAnalytics` model to store all the queries.
This commit is contained in:
parent
8ccea81cba
commit
f932bb8e41
3 changed files with 49 additions and 29 deletions
|
@ -1,32 +1,5 @@
|
||||||
class Projects::CycleAnalyticsController < Projects::ApplicationController
|
class Projects::CycleAnalyticsController < Projects::ApplicationController
|
||||||
def show
|
def show
|
||||||
@metrics = {
|
@cycle_analytics = CycleAnalytics.new
|
||||||
issue: issue
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def issue
|
|
||||||
query = <<-HEREDOC
|
|
||||||
WITH ordered_data AS (
|
|
||||||
SELECT extract(milliseconds FROM (COALESCE(first_associated_with_milestone_at, first_added_to_board_at) - issues.created_at)) AS data_point,
|
|
||||||
row_number() over (order by (COALESCE(first_associated_with_milestone_at, first_added_to_board_at) - issues.created_at)) as row_id
|
|
||||||
FROM issues
|
|
||||||
INNER JOIN issue_metrics ON issue_metrics.issue_id = issues.id
|
|
||||||
WHERE COALESCE(first_associated_with_milestone_at, first_added_to_board_at) IS NOT NULL
|
|
||||||
),
|
|
||||||
|
|
||||||
ct AS (
|
|
||||||
SELECT count(1) AS ct
|
|
||||||
FROM ordered_data
|
|
||||||
)
|
|
||||||
|
|
||||||
SELECT avg(data_point) AS median
|
|
||||||
FROM ordered_data
|
|
||||||
WHERE row_id between (select ct from ct)/2.0 and (select ct from ct)/2.0 + 1;
|
|
||||||
HEREDOC
|
|
||||||
|
|
||||||
ActiveRecord::Base.connection.execute(query).to_a.first['median']
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
40
app/models/cycle_analytics.rb
Normal file
40
app/models/cycle_analytics.rb
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
class CycleAnalytics
|
||||||
|
def issue
|
||||||
|
issues = Issue.includes(:metrics).where("issue_metrics.id IS NOT NULL").references(:issue_metrics).to_a
|
||||||
|
start_time_fn = -> (issue) { issue.created_at }
|
||||||
|
end_time_fn = -> (issue) { issue.metrics.first_associated_with_milestone_at.presence || issue.metrics.first_added_to_board_at.presence }
|
||||||
|
|
||||||
|
calculate_metric(issues, start_time_fn, end_time_fn)
|
||||||
|
end
|
||||||
|
|
||||||
|
def plan
|
||||||
|
issues = Issue.includes(:metrics).where("issue_metrics.id IS NOT NULL").references(:issue_metrics).to_a
|
||||||
|
start_time_fn = -> (issue) { issue.metrics.first_associated_with_milestone_at.presence || issue.metrics.first_added_to_board_at.presence }
|
||||||
|
end_time_fn = lambda do |issue|
|
||||||
|
merge_requests = issue.closed_by_merge_requests
|
||||||
|
merge_requests.map(&:created_at).min if merge_requests.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
calculate_metric(issues, start_time_fn, end_time_fn)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def calculate_metric(data, start_time_fn, end_time_fn)
|
||||||
|
times = data.map do |data_point|
|
||||||
|
start_time = start_time_fn[data_point]
|
||||||
|
end_time = end_time_fn[data_point]
|
||||||
|
|
||||||
|
if start_time.present? && end_time.present?
|
||||||
|
end_time - start_time
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
median(times.compact)
|
||||||
|
end
|
||||||
|
|
||||||
|
def median(coll)
|
||||||
|
size = coll.length
|
||||||
|
(coll[size / 2] + coll[(size - 1) / 2]) / 2.0
|
||||||
|
end
|
||||||
|
end
|
|
@ -1 +1,8 @@
|
||||||
%pre= @metrics
|
%ul.list-group
|
||||||
|
%li.list-group-item
|
||||||
|
Issue:
|
||||||
|
= distance_of_time_in_words @cycle_analytics.issue
|
||||||
|
|
||||||
|
%li.list-group-item
|
||||||
|
Plan:
|
||||||
|
= distance_of_time_in_words @cycle_analytics.plan
|
||||||
|
|
Loading…
Reference in a new issue