Commit 3e29c893 authored by Adam Hegyi's avatar Adam Hegyi Committed by Sean McGivern

Expose average durations for the VSA chart

parent 7519a149
......@@ -9,7 +9,7 @@ module Groups
before_action :load_group
before_action :load_value_stream
before_action :validate_params, only: %i[median average records duration_chart]
before_action :validate_params, only: %i[median average records duration_chart average_duration_chart]
def index
return render_403 unless can?(current_user, :read_group_cycle_analytics, @group)
......@@ -69,6 +69,12 @@ module Groups
render json: ::Analytics::CycleAnalytics::DurationChartItemEntity.represent(data_collector.duration_chart_data)
end
def average_duration_chart
return render_403 unless can?(current_user, :read_group_stage, @group)
render json: ::Analytics::CycleAnalytics::DurationChartAverageItemEntity.represent(data_collector.duration_chart_average_data)
end
private
def data_collector
......
# frozen_string_literal: true
module Analytics
module CycleAnalytics
class DurationChartAverageItemEntity < Grape::Entity
expose :date
expose :average_duration_in_seconds
end
end
end
......@@ -27,6 +27,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
scope module: :cycle_analytics, as: 'cycle_analytics', path: 'value_stream_analytics' do
resources :stages, only: [:index, :create, :update, :destroy] do
member do
get :average_duration_chart
get :duration_chart
get :median
get :average
......@@ -36,6 +37,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
resources :value_streams, only: [:index, :create, :update, :destroy] do
resources :stages, only: [:index, :create, :update, :destroy] do
member do
get :average_duration_chart
get :duration_chart
get :median
get :average
......
......@@ -5,11 +5,24 @@ module EE
module Analytics
module CycleAnalytics
module DataCollector
# Deprecated, will be removed in the next milestone: https://gitlab.com/gitlab-org/gitlab/-/issues/328219
def duration_chart_data
strong_memoize(:duration_chart) do
::Gitlab::Analytics::CycleAnalytics::DataForDurationChart.new(stage: stage, params: params, query: query).load
duration_chart.load
end
end
def duration_chart_average_data
strong_memoize(:duration_chart_average_data) do
duration_chart.average_by_day
end
end
private
def duration_chart
@duration_chart ||= ::Gitlab::Analytics::CycleAnalytics::DataForDurationChart.new(stage: stage, params: params, query: query)
end
end
end
end
......
......@@ -22,6 +22,18 @@ module Gitlab
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def average_by_day
date = Arel::Nodes::NamedFunction.new('DATE', [stage.end_event.timestamp_projection])
average = round_duration_to_seconds.average
@query
.reorder(nil)
.group(date)
.select(date.dup.as('date'), average.as('average_duration_in_seconds'))
end
# rubocop: enable CodeReuse/ActiveRecord
private
attr_reader :stage, :query, :params
......
{
"type": "array",
"items": {
"type": "object",
"required": ["average_duration_in_seconds", "date"],
"properties": {
"average_duration_in_seconds": {
"type": "integer"
},
"date": {
"type": "string"
}
},
"additionalProperties": false
}
}
......@@ -21,6 +21,9 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::DataCollector do
shared_examples 'custom Value Stream Analytics Stage' do
let(:params) { { from: Time.new(2019), to: Time.new(2020), current_user: user } }
let(:data_collector) { described_class.new(stage: stage, params: params) }
let(:resource_1_end_time) { Time.new(2019, 3, 15) }
let(:resource_2_end_time) { Time.new(2019, 3, 10) }
let(:resource_3_end_time) { Time.new(2019, 3, 20) }
let!(:resource1) do
# takes 10 days
......@@ -28,7 +31,7 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::DataCollector do
create_data_for_start_event(self)
end
travel_to(Time.new(2019, 3, 15)) do
travel_to(resource_1_end_time) do
create_data_for_end_event(resource, self)
end
......@@ -41,7 +44,7 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::DataCollector do
create_data_for_start_event(self)
end
travel_to(Time.new(2019, 3, 10)) do
travel_to(resource_2_end_time) do
create_data_for_end_event(resource, self)
end
......@@ -54,7 +57,7 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::DataCollector do
create_data_for_start_event(self)
end
travel_to(Time.new(2019, 3, 20)) do
travel_to(resource_3_end_time) do
create_data_for_end_event(resource, self)
end
......@@ -93,6 +96,20 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::DataCollector do
expect(days).to eq([15, 10, 5])
end
end
describe '#duration_chart_average_data' do
subject { data_collector.duration_chart_average_data }
it 'loads data ordered by event time' do
data = subject.map { |item| [item.date, round_to_days(item.average_duration_in_seconds)] }
expect(Hash[data]).to eq({
resource_1_end_time.utc.to_date => 10,
resource_2_end_time.utc.to_date => 5,
resource_3_end_time.utc.to_date => 15
})
end
end
end
shared_examples 'test various start and end event combinations' do
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Analytics::CycleAnalytics::DataForDurationChart do
describe '#average_by_day' do
let_it_be(:project) { create(:project, :repository) }
let(:query) { MergeRequest.joins(:metrics) }
let(:merge_time) { 2.days.ago }
let(:stage) do
build(
:cycle_analytics_project_stage,
start_event_identifier: Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestCreated.identifier,
end_event_identifier: Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestMerged.identifier,
project: project
)
end
subject(:averages) { described_class.new(stage: stage, params: {}, query: query).average_by_day }
it 'returns average duration by day' do
merge_request1 = create(:merge_request, source_branch: '1', target_project: project, source_project: project, created_at: merge_time - 5.minutes)
merge_request2 = create(:merge_request, source_branch: '2', target_project: project, source_project: project, created_at: merge_time - 10.minutes)
merge_request1.metrics.update!(merged_at: merge_time)
merge_request2.metrics.update!(merged_at: merge_time)
average = averages.first
expect(average.date).to eq(merge_time.utc.to_date)
expect(average.average_duration_in_seconds.to_i).to eq(7.5.minutes)
end
end
end
......@@ -264,6 +264,22 @@ RSpec.shared_examples 'Value Stream Analytics Stages controller' do
include_examples 'Value Stream Analytics data endpoint examples'
include_examples 'group permission check on the controller level'
end
describe 'GET #duration_chart' do
subject { get :average_duration_chart, params: params }
it 'matches the response schema' do
fake_result = [double(MergeRequest, average_duration_in_seconds: 10, date: Time.current.to_date)]
expect_any_instance_of(Gitlab::Analytics::CycleAnalytics::DataForDurationChart).to receive(:average_by_day).and_return(fake_result)
subject
expect(response).to match_response_schema('analytics/cycle_analytics/average_duration_chart', dir: 'ee')
end
include_examples 'Value Stream Analytics data endpoint examples'
include_examples 'group permission check on the controller level'
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