Commit cef34757 authored by Dmytro Zaporozhets's avatar Dmytro Zaporozhets

Merge branch 'ag-lead-time-cycle-time' into 'master'

Add lead time to Value Stream Analytics Summary

See merge request gitlab-org/gitlab!28201
parents d1f44c65 1ef34153
......@@ -14,7 +14,7 @@ module Analytics
def summary
@summary ||=
Gitlab::Analytics::CycleAnalytics::GroupStageSummary
Gitlab::Analytics::CycleAnalytics::Summary::Group::StageSummary
.new(group, options: options)
.data
end
......
# frozen_string_literal: true
module Gitlab
module Analytics
module CycleAnalytics
class GroupStageSummary
attr_reader :group, :current_user, :options
def initialize(group, options:)
@group = group
@current_user = options[:current_user]
@options = options
end
def data
[issue_stats,
deploy_stats,
deployment_frequency_stats]
end
private
def issue_stats
serialize(
Summary::Group::Issue.new(
group: group, current_user: current_user, options: options)
)
end
def deployments_summary
@deployments_summary ||=
Summary::Group::Deploy.new(group: group, options: options)
end
def deploy_stats
serialize deployments_summary
end
def deployment_frequency_stats
serialize(
Summary::Group::DeploymentFrequency.new(
deployments: deployments_summary.value,
group: group,
options: options),
with_unit: true
)
end
def serialize(summary_object, with_unit: false)
AnalyticsSummarySerializer.new.represent(
summary_object, with_unit: with_unit)
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Analytics
module CycleAnalytics
module Summary
module Group
class BaseTime < Group::Base
def initialize(group:, current_user:, options:)
@group = group
@current_user = current_user
@options = options
end
def value
@value ||= data_collector.median.days&.round(1)
end
def unit
n_('day', 'days', value)
end
def start_event_identifier
raise NotImplementedError.new("Expected #{self.name} to implement start_event_identifier")
end
def end_event_identifier
raise NotImplementedError.new("Expected #{self.name} to implement end_event_identifier")
end
private
def stage
::Analytics::CycleAnalytics::GroupStage.new(
group: @group,
start_event_identifier: start_event_identifier,
end_event_identifier: end_event_identifier)
end
def data_collector
Gitlab::Analytics::CycleAnalytics::DataCollector.new(
stage: stage,
params: {
from: @options[:from],
to: @options[:to] || DateTime.now,
project_ids: @options[:projects]
}
)
end
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Analytics
module CycleAnalytics
module Summary
module Group
class LeadTime < BaseTime
def title
_('Lead Time')
end
def start_event_identifier
:issue_created
end
def end_event_identifier
:issue_closed
end
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Analytics
module CycleAnalytics
module Summary
module Group
class StageSummary
attr_reader :group, :current_user, :options
def initialize(group, options:)
@group = group
@current_user = options[:current_user]
@options = options
end
def data
[issue_stats,
deploy_stats,
deployment_frequency_stats]
end
private
def issue_stats
serialize(
Summary::Group::Issue.new(
group: group, current_user: current_user, options: options)
)
end
def deployments_summary
@deployments_summary ||=
Summary::Group::Deploy.new(group: group, options: options)
end
def deploy_stats
serialize deployments_summary
end
def deployment_frequency_stats
serialize(
Summary::Group::DeploymentFrequency.new(
deployments: deployments_summary.value,
group: group,
options: options),
with_unit: true
)
end
def serialize(summary_object, with_unit: false)
AnalyticsSummarySerializer.new.represent(
summary_object, with_unit: with_unit)
end
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Analytics
module CycleAnalytics
module Summary
module Group
class StageTimeSummary
attr_reader :group, :current_user, :options
def initialize(group, options:)
@group = group
@current_user = options[:current_user]
@options = options
end
def data
[lead_time]
end
private
def lead_time
serialize(
Summary::Group::LeadTime.new(
group: group, current_user: current_user, options: options
),
with_unit: true
)
end
def serialize(summary_object, with_unit: false)
AnalyticsSummarySerializer.new.represent(
summary_object, with_unit: with_unit)
end
end
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Analytics::CycleAnalytics::GroupStageSummary do
describe Gitlab::Analytics::CycleAnalytics::Summary::Group::StageSummary do
let(:group) { create(:group) }
let(:project) { create(:project, :repository, namespace: group) }
let(:project_2) { create(:project, :repository, namespace: group) }
......
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Analytics::CycleAnalytics::Summary::Group::StageTimeSummary do
let(:group) { create(:group) }
let(:project) { create(:project, :repository, namespace: group) }
let(:project_2) { create(:project, :repository, namespace: group) }
let(:from) { 1.day.ago }
let(:to) { nil }
let(:user) { create(:user, :admin) }
subject { described_class.new(group, options: { from: from, to: to, current_user: user }).data }
around do |example|
Timecop.freeze { example.run }
end
describe '#lead_time' do
context 'with `from` date' do
let(:from) { 6.days.ago }
before do
create(:closed_issue, project: project, created_at: 1.day.ago, closed_at: Time.now)
create(:closed_issue, project: project, created_at: 2.days.ago, closed_at: Time.now)
create(:closed_issue, project: project_2, created_at: 4.days.ago, closed_at: Time.now)
end
it 'finds the lead time of issues created after it' do
expect(subject.first[:value]).to eq('2.0')
end
context 'with subgroups' do
let(:subgroup) { create(:group, parent: group) }
let(:project_3) { create(:project, namespace: subgroup) }
before do
create(:closed_issue, created_at: 3.days.ago, closed_at: Time.now, project: project_3)
create(:closed_issue, created_at: 5.days.ago, closed_at: Time.now, project: project_3)
end
it 'finds the lead time of issues from them' do
expect(subject.first[:value]).to eq('3.0')
end
end
context 'with projects specified in options' do
before do
create(:closed_issue, created_at: 3.days.ago, closed_at: Time.now, project: create(:project, namespace: group))
end
subject { described_class.new(group, options: { from: from, current_user: user, projects: [project.id, project_2.id] }).data }
it 'finds the lead time of issues from those projects' do
# Median of 1, 2, 4, not including new issue
expect(subject.first[:value]).to eq('2.0')
end
end
context 'when `from` and `to` parameters are provided' do
let(:from) { 3.days.ago }
let(:to) { Time.now }
it 'finds the lead time of issues from 3 days ago' do
expect(subject.first[:value]).to eq('1.5')
end
end
end
context 'with other projects' do
let(:from) { 4.days.ago }
before do
create(:closed_issue, created_at: 1.day.ago, closed_at: Time.now, project: create(:project, namespace: create(:group)))
create(:closed_issue, created_at: 2.days.ago, closed_at: Time.now, project: project)
create(:closed_issue, created_at: 3.days.ago, closed_at: Time.now, project: project_2)
end
it 'does not find the lead time of issues from them' do
# Median of 2, 3, not including first issue
expect(subject.first[:value]).to eq('2.5')
end
end
end
end
......@@ -18,6 +18,10 @@ module Gitlab
result['median'] ? result['median'].to_i : nil
end
def days
seconds ? seconds.fdiv(1.day) : nil
end
private
attr_reader :stage
......
......@@ -12058,6 +12058,9 @@ msgstr ""
msgid "Lead"
msgstr ""
msgid "Lead Time"
msgstr ""
msgid "Learn GitLab"
msgstr ""
......
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