Commit a0dce93b authored by Adam Hegyi's avatar Adam Hegyi

Calculate average in VSA

This change adds a new calculation to Value Stream Analytics where
the average durations are calculated.
parent 04da813f
...@@ -9,7 +9,7 @@ module Groups ...@@ -9,7 +9,7 @@ module Groups
before_action :load_group before_action :load_group
before_action :load_value_stream before_action :load_value_stream
before_action :validate_params, only: %i[median records duration_chart] before_action :validate_params, only: %i[median average records duration_chart]
def index def index
return render_403 unless can?(current_user, :read_group_cycle_analytics, @group) return render_403 unless can?(current_user, :read_group_cycle_analytics, @group)
...@@ -47,6 +47,12 @@ module Groups ...@@ -47,6 +47,12 @@ module Groups
render json: { value: data_collector.median.seconds } render json: { value: data_collector.median.seconds }
end end
def average
return render_403 unless can?(current_user, :read_group_stage, @group)
render json: { value: data_collector.average.seconds }
end
def records def records
return render_403 unless can?(current_user, :read_group_stage, @group) return render_403 unless can?(current_user, :read_group_stage, @group)
......
...@@ -26,6 +26,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do ...@@ -26,6 +26,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
member do member do
get :duration_chart get :duration_chart
get :median get :median
get :average
get :records get :records
end end
end end
...@@ -34,6 +35,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do ...@@ -34,6 +35,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
member do member do
get :duration_chart get :duration_chart
get :median get :median
get :average
get :records get :records
end end
end end
......
...@@ -197,7 +197,19 @@ RSpec.shared_examples 'Value Stream Analytics Stages controller' do ...@@ -197,7 +197,19 @@ RSpec.shared_examples 'Value Stream Analytics Stages controller' do
it 'matches the response schema' do it 'matches the response schema' do
subject subject
expect(response).to match_response_schema('analytics/cycle_analytics/median', dir: 'ee') expect(response).to match_response_schema('analytics/cycle_analytics/number_or_nil_value', dir: 'ee')
end
include_examples 'Value Stream Analytics data endpoint examples'
end
describe 'GET #average' do
subject { get :average, params: params }
it 'matches the response schema' do
subject
expect(response).to match_response_schema('analytics/cycle_analytics/number_or_nil_value', dir: 'ee')
end end
include_examples 'Value Stream Analytics data endpoint examples' include_examples 'Value Stream Analytics data endpoint examples'
......
# frozen_string_literal: true
module Gitlab
module Analytics
module CycleAnalytics
class Average
include Gitlab::Utils::StrongMemoize
include StageQueryHelpers
def initialize(stage:, query:)
@stage = stage
@query = query
end
def seconds
select_average ? select_average['average'] : nil
end
def days
seconds ? seconds.fdiv(1.day) : nil
end
private
attr_reader :stage
# rubocop: disable CodeReuse/ActiveRecord
def select_average
strong_memoize(:select_average) do
execute_query(@query.select(average_in_seconds.as('average')).reorder(nil)).first
end
end
# rubocop: enable CodeReuse/ActiveRecord
def average
Arel::Nodes::NamedFunction.new(
'AVG',
[duration]
)
end
def average_in_seconds
Arel::Nodes::Extract.new(average, :epoch)
end
end
end
end
end
...@@ -31,6 +31,12 @@ module Gitlab ...@@ -31,6 +31,12 @@ module Gitlab
end end
end end
def average
strong_memoize(:average) do
Average.new(stage: stage, query: query)
end
end
private private
attr_reader :stage, :params attr_reader :stage, :params
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Analytics::CycleAnalytics::Average do
let_it_be(:project) { create(:project) }
let_it_be(:issue_1) do
# Duration: 10 days
create(:issue, project: project, created_at: 20.days.ago).tap do |issue|
issue.metrics.update!(first_mentioned_in_commit_at: 10.days.ago)
end
end
let_it_be(:issue_2) do
# Duration: 5 days
create(:issue, project: project, created_at: 20.days.ago).tap do |issue|
issue.metrics.update!(first_mentioned_in_commit_at: 15.days.ago)
end
end
let(:stage) do
build(
:cycle_analytics_project_stage,
start_event_identifier: Gitlab::Analytics::CycleAnalytics::StageEvents::IssueCreated.identifier,
end_event_identifier: Gitlab::Analytics::CycleAnalytics::StageEvents::IssueFirstMentionedInCommit.identifier,
project: project
)
end
let(:query) { Issue.joins(:metrics).in_projects(project.id) }
around do |example|
freeze_time { example.run }
end
subject(:average) { described_class.new(stage: stage, query: query) }
describe '#seconds' do
subject(:average_duration_in_seconds) { average.seconds }
context 'when no results' do
let(:query) { Issue.none }
it { is_expected.to eq(nil) }
end
context 'returns the average duration in seconds' do
it { is_expected.to be_within(0.5).of(7.5.days.to_f) }
end
end
describe '#days' do
subject(:average_duration_in_days) { average.days }
context 'when no results' do
let(:query) { Issue.none }
it { is_expected.to eq(nil) }
end
context 'returns the average duration in days' do
it { is_expected.to be_within(0.01).of(7.5) }
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