Commit 3006fcb9 authored by Peter Leitzen's avatar Peter Leitzen

Merge branch 'ag-add-per-period-deploy-frequency' into 'master'

Add deploy frequency to project-level VSA Summary

See merge request gitlab-org/gitlab!28772
parents 9ebb85f4 83f9eca0
......@@ -59,16 +59,10 @@ export default () => {
service: this.createCycleAnalyticsService(cycleAnalyticsEl.dataset.requestPath),
};
},
defaultNumberOfSummaryItems: 3,
computed: {
currentStage() {
return this.store.currentActiveStage();
},
summaryTableColumnClass() {
return this.state.summary.length === this.$options.defaultNumberOfSummaryItems
? 'col-sm-3'
: 'col-sm-4';
},
},
created() {
// Conditional check placed here to prevent this method from being called on the
......
......@@ -105,10 +105,6 @@
}
}
.js-ca-dropdown {
top: $gl-padding-top;
}
.stage-panel-body {
display: flex;
flex-wrap: wrap;
......
......@@ -4,4 +4,12 @@ class AnalyticsSummaryEntity < Grape::Entity
expose :value, safe: true
expose :title
expose :unit, if: { with_unit: true }
private
def value
return object.value if object.value.is_a? String
object.value&.nonzero? ? object.value.to_s : '-'
end
end
......@@ -10,27 +10,25 @@
.card
.card-header
{{ __('Recent Project Activity') }}
.content-block
.container-fluid
.row
.col-12.column{ "v-for" => "item in state.summary", ":class" => "summaryTableColumnClass" }
%h3.header {{ item.value }}
%p.text {{ item.title }}
.col-12.column{ ":class" => "summaryTableColumnClass" }
.dropdown.inline.js-ca-dropdown
%button.dropdown-menu-toggle{ "data-toggle" => "dropdown", :type => "button" }
%span.dropdown-label {{ n__('Last %d day', 'Last %d days', 30) }}
%i.fa.fa-chevron-down
%ul.dropdown-menu.dropdown-menu-right
%li
%a{ "href" => "#", "data-value" => "7" }
{{ n__('Last %d day', 'Last %d days', 7) }}
%li
%a{ "href" => "#", "data-value" => "30" }
{{ n__('Last %d day', 'Last %d days', 30) }}
%li
%a{ "href" => "#", "data-value" => "90" }
{{ n__('Last %d day', 'Last %d days', 90) }}
.d-flex.justify-content-between
.flex-grow.text-center{ "v-for" => "item in state.summary" }
%h3.header {{ item.value }}
%p.text {{ item.title }}
.flex-grow.align-self-center.text-center
.dropdown.inline.js-ca-dropdown
%button.dropdown-menu-toggle{ "data-toggle" => "dropdown", :type => "button" }
%span.dropdown-label {{ n__('Last %d day', 'Last %d days', 30) }}
%i.fa.fa-chevron-down
%ul.dropdown-menu.dropdown-menu-right
%li
%a{ "href" => "#", "data-value" => "7" }
{{ n__('Last %d day', 'Last %d days', 7) }}
%li
%a{ "href" => "#", "data-value" => "30" }
{{ n__('Last %d day', 'Last %d days', 30) }}
%li
%a{ "href" => "#", "data-value" => "90" }
{{ n__('Last %d day', 'Last %d days', 90) }}
.stage-panel-container
.card.stage-panel
.card-header.border-bottom-0
......
---
title: Add deployment frequency to Project level Value Stream Analytics summary
merge_request: 28772
author:
type: added
......@@ -7,7 +7,7 @@
"required": ["value", "title"],
"properties": {
"value": {
"type": "number"
"type": "string"
},
"title": {
"type": "string"
......
......@@ -14,6 +14,7 @@ module Gitlab
summary = [issue_stats]
summary << commit_stats if user_has_sufficient_access?
summary << deploy_stats
summary << deployment_frequency_stats
end
private
......@@ -26,16 +27,32 @@ module Gitlab
serialize(Summary::Commit.new(project: @project, from: @from, to: @to))
end
def deployments_summary
@deployments_summary ||=
Summary::Deploy.new(project: @project, from: @from, to: @to)
end
def deploy_stats
serialize(Summary::Deploy.new(project: @project, from: @from, to: @to))
serialize deployments_summary
end
def deployment_frequency_stats
serialize(
Summary::DeploymentFrequency.new(
deployments: deployments_summary.value,
from: @from,
to: @to),
with_unit: true
)
end
def user_has_sufficient_access?
@project.team.member?(@current_user, Gitlab::Access::REPORTER)
end
def serialize(summary_object)
AnalyticsSummarySerializer.new.represent(summary_object)
def serialize(summary_object, with_unit: false)
AnalyticsSummarySerializer.new.represent(
summary_object, with_unit: with_unit)
end
end
end
......
# frozen_string_literal: true
module Gitlab
module CycleAnalytics
module Summary
class DeploymentFrequency < Base
include SummaryHelper
def initialize(deployments:, from:, to: nil, project: nil)
@deployments = deployments
super(project: project, from: from, to: to)
end
def title
_('Deployment Frequency')
end
def value
@value ||=
frequency(@deployments, @from, @to || Time.now)
end
def unit
_('per day')
end
end
end
end
end
......@@ -4,11 +4,14 @@ module Gitlab
module CycleAnalytics
module SummaryHelper
def frequency(count, from, to)
(count / days(from, to)).round(1)
return count if count.zero?
freq = (count / days(from, to)).round(1)
freq.zero? ? '0' : freq
end
def days(from, to)
[(to.end_of_day - from.beginning_of_day) / (24 * 60 * 60), 1].max
[(to.end_of_day - from.beginning_of_day).fdiv(1.day), 1].max
end
end
end
......
......@@ -30,6 +30,7 @@ describe 'Value Stream Analytics', :js do
expect(new_issues_counter).to have_content('-')
expect(commits_counter).to have_content('-')
expect(deploys_counter).to have_content('-')
expect(deployment_frequency_counter).to have_content('-')
end
it 'shows active stage with empty message' do
......@@ -53,6 +54,7 @@ describe 'Value Stream Analytics', :js do
expect(new_issues_counter).to have_content('1')
expect(commits_counter).to have_content('2')
expect(deploys_counter).to have_content('1')
expect(deployment_frequency_counter).to have_content('0')
end
it 'shows data on each stage', :sidekiq_might_not_need_inline do
......@@ -134,7 +136,15 @@ describe 'Value Stream Analytics', :js do
end
def deploys_counter
find(:xpath, "//p[contains(text(),'Deploy')]/preceding-sibling::h3")
find(:xpath, "//p[contains(text(),'Deploy')]/preceding-sibling::h3", match: :first)
end
def deployment_frequency_counter_selector
"//p[contains(text(),'Deployment Frequency')]/preceding-sibling::h3"
end
def deployment_frequency_counter
find(:xpath, deployment_frequency_counter_selector)
end
def expect_issue_to_be_present
......
......@@ -20,7 +20,7 @@ describe Gitlab::CycleAnalytics::GroupStageSummary do
end
it "finds the number of issues created after it" do
expect(subject.first[:value]).to eq(2)
expect(subject.first[:value]).to eq('2')
end
context 'with subgroups' do
......@@ -29,7 +29,7 @@ describe Gitlab::CycleAnalytics::GroupStageSummary do
end
it "finds issues from them" do
expect(subject.first[:value]).to eq(3)
expect(subject.first[:value]).to eq('3')
end
end
......@@ -41,7 +41,7 @@ describe Gitlab::CycleAnalytics::GroupStageSummary do
subject { described_class.new(group, options: { from: Time.now, current_user: user, projects: [project.id, project_2.id] }).data }
it 'finds issues from those projects' do
expect(subject.first[:value]).to eq(2)
expect(subject.first[:value]).to eq('2')
end
end
......@@ -49,7 +49,7 @@ describe Gitlab::CycleAnalytics::GroupStageSummary do
subject { described_class.new(group, options: { from: 10.days.ago, to: Time.now, current_user: user }).data }
it 'finds issues from 5 days ago' do
expect(subject.first[:value]).to eq(2)
expect(subject.first[:value]).to eq('2')
end
end
end
......@@ -62,7 +62,7 @@ describe Gitlab::CycleAnalytics::GroupStageSummary do
end
it "doesn't find issues from them" do
expect(subject.first[:value]).to eq(2)
expect(subject.first[:value]).to eq('2')
end
end
end
......@@ -77,7 +77,7 @@ describe Gitlab::CycleAnalytics::GroupStageSummary do
end
it "finds the number of deploys made created after it" do
expect(subject.second[:value]).to eq(2)
expect(subject.second[:value]).to eq('2')
end
context 'with subgroups' do
......@@ -88,7 +88,7 @@ describe Gitlab::CycleAnalytics::GroupStageSummary do
end
it "finds deploys from them" do
expect(subject.second[:value]).to eq(3)
expect(subject.second[:value]).to eq('3')
end
end
......@@ -102,7 +102,7 @@ describe Gitlab::CycleAnalytics::GroupStageSummary do
subject { described_class.new(group, options: { from: Time.now, current_user: user, projects: [project.id, project_2.id] }).data }
it 'shows deploys from those projects' do
expect(subject.second[:value]).to eq(2)
expect(subject.second[:value]).to eq('2')
end
end
......@@ -110,7 +110,7 @@ describe Gitlab::CycleAnalytics::GroupStageSummary do
subject { described_class.new(group, options: { from: 10.days.ago, to: Time.now, current_user: user }).data }
it 'finds deployments from 5 days ago' do
expect(subject.second[:value]).to eq(2)
expect(subject.second[:value]).to eq('2')
end
end
end
......@@ -123,7 +123,7 @@ describe Gitlab::CycleAnalytics::GroupStageSummary do
end
it "doesn't find deploys from them" do
expect(subject.second[:value]).to eq(0)
expect(subject.second[:value]).to eq('-')
end
end
end
......@@ -153,7 +153,7 @@ describe Gitlab::CycleAnalytics::GroupStageSummary do
context 'when `to` is nil' do
it 'includes range until now' do
# 1 deployment over 7 days
expect(subject[:value]).to eq(0.1)
expect(subject[:value]).to eq('0.1')
end
end
......@@ -169,7 +169,7 @@ describe Gitlab::CycleAnalytics::GroupStageSummary do
it 'returns deployment frequency within `from` and `to` range' do
# 2 deployments over 20 days
expect(subject[:value]).to eq(0.1)
expect(subject[:value]).to eq('0.1')
end
end
end
......
......@@ -20,13 +20,13 @@ describe Gitlab::CycleAnalytics::StageSummary do
Timecop.freeze(5.days.ago) { create(:issue, project: project) }
Timecop.freeze(5.days.from_now) { create(:issue, project: project) }
expect(subject).to eq(1)
expect(subject).to eq('1')
end
it "doesn't find issues from other projects" do
Timecop.freeze(5.days.from_now) { create(:issue, project: create(:project)) }
expect(subject).to eq(0)
expect(subject).to eq('-')
end
context 'when `to` parameter is given' do
......@@ -38,14 +38,14 @@ describe Gitlab::CycleAnalytics::StageSummary do
it "doesn't find any record" do
options[:to] = Time.now
expect(subject).to eq(0)
expect(subject).to eq('-')
end
it "finds records created between `from` and `to` range" do
options[:from] = 10.days.ago
options[:to] = 10.days.from_now
expect(subject).to eq(2)
expect(subject).to eq('2')
end
end
end
......@@ -57,19 +57,19 @@ describe Gitlab::CycleAnalytics::StageSummary do
Timecop.freeze(5.days.ago) { create_commit("Test message", project, user, 'master') }
Timecop.freeze(5.days.from_now) { create_commit("Test message", project, user, 'master') }
expect(subject).to eq(1)
expect(subject).to eq('1')
end
it "doesn't find commits from other projects" do
Timecop.freeze(5.days.from_now) { create_commit("Test message", create(:project, :repository), user, 'master') }
expect(subject).to eq(0)
expect(subject).to eq('-')
end
it "finds a large (> 100) snumber of commits if present" do
it "finds a large (> 100) number of commits if present" do
Timecop.freeze(5.days.from_now) { create_commit("Test message", project, user, 'master', count: 100) }
expect(subject).to eq(100)
expect(subject).to eq('100')
end
context 'when `to` parameter is given' do
......@@ -81,14 +81,14 @@ describe Gitlab::CycleAnalytics::StageSummary do
it "doesn't find any record" do
options[:to] = Time.now
expect(subject).to eq(0)
expect(subject).to eq('-')
end
it "finds records created between `from` and `to` range" do
options[:from] = 10.days.ago
options[:to] = 10.days.from_now
expect(subject).to eq(2)
expect(subject).to eq('2')
end
end
......@@ -118,7 +118,7 @@ describe Gitlab::CycleAnalytics::StageSummary do
Timecop.freeze(5.days.ago) { create(:deployment, :success, project: project) }
Timecop.freeze(5.days.from_now) { create(:deployment, :success, project: project) }
expect(subject).to eq(1)
expect(subject).to eq('1')
end
it "doesn't find commits from other projects" do
......@@ -126,7 +126,7 @@ describe Gitlab::CycleAnalytics::StageSummary do
create(:deployment, :success, project: create(:project, :repository))
end
expect(subject).to eq(0)
expect(subject).to eq('-')
end
context 'when `to` parameter is given' do
......@@ -138,14 +138,76 @@ describe Gitlab::CycleAnalytics::StageSummary do
it "doesn't find any record" do
options[:to] = Time.now
expect(subject).to eq(0)
expect(subject).to eq('-')
end
it "finds records created between `from` and `to` range" do
options[:from] = 10.days.ago
options[:to] = 10.days.from_now
expect(subject).to eq(2)
expect(subject).to eq('2')
end
end
end
describe '#deployment_frequency' do
subject { stage_summary.fourth[:value] }
it 'includes the unit: `per day`' do
expect(stage_summary.fourth[:unit]).to eq _('per day')
end
before do
Timecop.freeze(5.days.ago) { create(:deployment, :success, project: project) }
end
it 'returns 0.0 when there were deploys but the frequency was too low' do
options[:from] = 30.days.ago
# 1 deployment over 30 days
# frequency of 0.03, rounded off to 0.0
expect(subject).to eq('0')
end
it 'returns `-` when there were no deploys' do
options[:from] = 4.days.ago
# 0 deployment in the last 4 days
expect(subject).to eq('-')
end
context 'when `to` is nil' do
it 'includes range until now' do
options[:from] = 6.days.ago
options[:to] = nil
# 1 deployment over 7 days
expect(subject).to eq('0.1')
end
end
context 'when `to` is given' do
before do
Timecop.freeze(5.days.from_now) { create(:deployment, :success, project: project) }
end
it 'finds records created between `from` and `to` range' do
options[:from] = 10.days.ago
options[:to] = 10.days.from_now
# 2 deployments over 20 days
expect(subject).to eq('0.1')
end
context 'when `from` and `to` are within a day' do
it 'returns the number of deployments made on that day' do
Timecop.freeze(Time.now) do
create(:deployment, :success, project: project)
options[:from] = options[:to] = Time.now
expect(subject).to eq('1')
end
end
end
end
end
......
......@@ -38,7 +38,7 @@ describe CycleAnalytics::GroupLevel do
end
it 'returns medians for each stage for a specific group' do
expect(subject.summary.map { |summary| summary[:value] }).to contain_exactly(0.1, 1, 1)
expect(subject.summary.map { |summary| summary[:value] }).to contain_exactly('0.1', '1', '1')
end
end
end
......@@ -34,7 +34,10 @@ describe AnalyticsSummarySerializer do
end
context 'when representing with unit' do
let(:resource) { { title: 'frequency', value: 1.12, unit: 'per day' } }
let(:resource) do
Gitlab::CycleAnalytics::Summary::DeploymentFrequency
.new(deployments: 10, from: 1.day.ago)
end
subject { described_class.new.represent(resource, with_unit: true) }
......
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