Commit beaa6353 authored by Małgorzata Ksionek's avatar Małgorzata Ksionek

Add class for group level analytics

Add specs for group level

Update entities

Update base classes

Add groups-centric changes

Update plan and review stage

Add summary classes

Add summary spec

Update specs files

Add to specs test cases for group

Add changelog entry

Add group serializer

Fix typo

Fix typo

Add fetching namespace in sql query

Update specs

Add rubocop fix

Add rubocop fix

Modify method to be in sync with code review

Add counting deploys from subgroup

To group summary stage

Add subgroups handling

In group stage summary

Add additional spec

Add additional specs

Add more precise inheritance

Add attr reader to group level

Fix rubocop offence

Fix problems with specs

Add cr remarks

Renaming median method and a lot of calls in specs

Move spec setup

Rename method in specs

Add code review remarks regarding module

Add proper module name
parent 0e8af252
# frozen_string_literal: true # frozen_string_literal: true
module CycleAnalytics module CycleAnalytics
class Base module BaseMethods
STAGES = %i[issue plan code test review staging production].freeze STAGES = %i[issue plan code test review staging production].freeze
def all_medians_by_stage def all_medians_by_stage
STAGES.each_with_object({}) do |stage_name, medians_per_stage| STAGES.each_with_object({}) do |stage_name, medians_per_stage|
medians_per_stage[stage_name] = self[stage_name].median medians_per_stage[stage_name] = self[stage_name].project_median
end end
end end
...@@ -21,7 +21,7 @@ module CycleAnalytics ...@@ -21,7 +21,7 @@ module CycleAnalytics
end end
def [](stage_name) def [](stage_name)
Gitlab::CycleAnalytics::Stage[stage_name].new(project: @project, options: @options) Gitlab::CycleAnalytics::Stage[stage_name].new(options: options)
end end
end end
end end
# frozen_string_literal: true
module CycleAnalytics
class GroupLevel
include BaseMethods
attr_reader :options
def initialize(options:)
@options = options
end
def summary
@summary ||= ::Gitlab::CycleAnalytics::GroupStageSummary.new(options[:group],
from: options[:from],
current_user: options[:current_user]).data
end
def permissions(user: nil)
STAGES.each_with_object({}) do |stage, obj|
obj[stage] = true
end
end
def stats
@stats ||= STAGES.map do |stage_name|
self[stage_name].as_json(serializer: GroupAnalyticsStageSerializer)
end
end
end
end
# frozen_string_literal: true # frozen_string_literal: true
module CycleAnalytics module CycleAnalytics
class ProjectLevel < Base class ProjectLevel
include BaseMethods
attr_reader :project, :options attr_reader :project, :options
def initialize(project, options:) def initialize(project, options:)
@project = project @project = project
@options = options @options = options.merge(project: project)
end end
def summary def summary
......
...@@ -20,12 +20,12 @@ class AnalyticsIssueEntity < Grape::Entity ...@@ -20,12 +20,12 @@ class AnalyticsIssueEntity < Grape::Entity
end end
expose :url do |object| expose :url do |object|
url_to(:namespace_project_issue, id: object[:iid].to_s) url_to(:namespace_project_issue, object)
end end
private private
def url_to(route, id) def url_to(route, object)
public_send("#{route}_url", request.project.namespace, request.project, id) # rubocop:disable GitlabSecurity/PublicSend public_send("#{route}_url", object[:path], object[:name], object[:iid].to_s) # rubocop:disable GitlabSecurity/PublicSend
end end
end end
...@@ -4,6 +4,6 @@ class AnalyticsMergeRequestEntity < AnalyticsIssueEntity ...@@ -4,6 +4,6 @@ class AnalyticsMergeRequestEntity < AnalyticsIssueEntity
expose :state expose :state
expose :url do |object| expose :url do |object|
url_to(:namespace_project_merge_request, id: object[:iid].to_s) url_to(:namespace_project_merge_request, object)
end end
end end
...@@ -8,9 +8,9 @@ class AnalyticsStageEntity < Grape::Entity ...@@ -8,9 +8,9 @@ class AnalyticsStageEntity < Grape::Entity
expose :legend expose :legend
expose :description expose :description
expose :median, as: :value do |stage| expose :project_median, as: :value do |stage|
# median returns a BatchLoader instance which we first have to unwrap by using to_f # median returns a BatchLoader instance which we first have to unwrap by using to_f
# we use to_f to make sure results below 1 are presented to the end-user # we use to_f to make sure results below 1 are presented to the end-user
stage.median.to_f.nonzero? ? distance_of_time_in_words(stage.median) : nil stage.project_median.to_f.nonzero? ? distance_of_time_in_words(stage.project_median) : nil
end end
end end
# frozen_string_literal: true
class GroupAnalyticsStageEntity < Grape::Entity
include EntityDateHelper
expose :title
expose :name
expose :legend
expose :description
expose :group_median, as: :value do |stage|
# median returns a BatchLoader instance which we first have to unwrap by using to_f
# we use to_f to make sure results below 1 are presented to the end-user
stage.group_median.to_f.nonzero? ? distance_of_time_in_words(stage.group_median) : nil
end
end
# frozen_string_literal: true
class GroupAnalyticsStageSerializer < BaseSerializer
entity GroupAnalyticsStageEntity
end
---
title: Adjust cycle analytics to group level
merge_request: 30391
author:
type: added
...@@ -5,12 +5,11 @@ module Gitlab ...@@ -5,12 +5,11 @@ module Gitlab
class BaseEventFetcher class BaseEventFetcher
include BaseQuery include BaseQuery
attr_reader :projections, :query, :stage, :order, :project, :options attr_reader :projections, :query, :stage, :order, :options
MAX_EVENTS = 50 MAX_EVENTS = 50
def initialize(project: nil, stage:, options:) def initialize(stage:, options:)
@project = project
@stage = stage @stage = stage
@options = options @options = options
end end
...@@ -68,11 +67,23 @@ module Gitlab ...@@ -68,11 +67,23 @@ module Gitlab
end end
def allowed_ids_source def allowed_ids_source
{ project_id: project.id } group ? { group_id: group.id, include_subgroups: true } : { project_id: project.id }
end
def serialization_context
{}
end end
def projects def projects
[project] group ? Project.inside_path(group.full_path) : [project]
end
def group
@group ||= options.fetch(:group, nil)
end
def project
@project ||= options.fetch(:project, nil)
end end
end end
end end
......
...@@ -16,17 +16,25 @@ module Gitlab ...@@ -16,17 +16,25 @@ module Gitlab
def stage_query(project_ids) def stage_query(project_ids)
query = mr_closing_issues_table.join(issue_table).on(issue_table[:id].eq(mr_closing_issues_table[:issue_id])) query = mr_closing_issues_table.join(issue_table).on(issue_table[:id].eq(mr_closing_issues_table[:issue_id]))
.join(issue_metrics_table).on(issue_table[:id].eq(issue_metrics_table[:issue_id])) .join(issue_metrics_table).on(issue_table[:id].eq(issue_metrics_table[:issue_id]))
.join(projects_table).on(issue_table[:project_id].eq(projects_table[:id]))
.join(routes_table).on(projects_table[:namespace_id].eq(routes_table[:source_id]))
.project(issue_table[:project_id].as("project_id")) .project(issue_table[:project_id].as("project_id"))
.where(issue_table[:project_id].in(project_ids)) .where(issue_table[:project_id].in(project_ids))
.where(routes_table[:source_type].eq('Namespace'))
.where(issue_table[:created_at].gteq(options[:from])) .where(issue_table[:created_at].gteq(options[:from]))
# Load merge_requests # Load merge_requests
query = query.join(mr_table, Arel::Nodes::OuterJoin)
query = load_merge_requests(query)
query
end
def load_merge_requests(query)
query.join(mr_table, Arel::Nodes::OuterJoin)
.on(mr_table[:id].eq(mr_closing_issues_table[:merge_request_id])) .on(mr_table[:id].eq(mr_closing_issues_table[:merge_request_id]))
.join(mr_metrics_table) .join(mr_metrics_table)
.on(mr_table[:id].eq(mr_metrics_table[:merge_request_id])) .on(mr_table[:id].eq(mr_metrics_table[:merge_request_id]))
query
end end
end end
end end
......
...@@ -5,10 +5,9 @@ module Gitlab ...@@ -5,10 +5,9 @@ module Gitlab
class BaseStage class BaseStage
include BaseQuery include BaseQuery
attr_reader :project, :options attr_reader :options
def initialize(project: nil, options:) def initialize(options:)
@project = project
@options = options @options = options
end end
...@@ -24,7 +23,7 @@ module Gitlab ...@@ -24,7 +23,7 @@ module Gitlab
raise NotImplementedError.new("Expected #{self.name} to implement title") raise NotImplementedError.new("Expected #{self.name} to implement title")
end end
def median def project_median
return if project.nil? return if project.nil?
BatchLoader.for(project.id).batch(key: name) do |project_ids, loader| BatchLoader.for(project.id).batch(key: name) do |project_ids, loader|
...@@ -42,6 +41,10 @@ module Gitlab ...@@ -42,6 +41,10 @@ module Gitlab
end end
end end
def group_median
median_query(projects.map(&:id))
end
def median_query(project_ids) def median_query(project_ids)
# Build a `SELECT` query. We find the first of the `end_time_attrs` that isn't `NULL` (call this end_time). # Build a `SELECT` query. We find the first of the `end_time_attrs` that isn't `NULL` (call this end_time).
# Next, we find the first of the start_time_attrs that isn't `NULL` (call this start_time). # Next, we find the first of the start_time_attrs that isn't `NULL` (call this start_time).
...@@ -67,8 +70,7 @@ module Gitlab ...@@ -67,8 +70,7 @@ module Gitlab
private private
def event_fetcher def event_fetcher
@event_fetcher ||= Gitlab::CycleAnalytics::EventFetcher[name].new(project: project, @event_fetcher ||= Gitlab::CycleAnalytics::EventFetcher[name].new(stage: name,
stage: name,
options: event_options) options: event_options)
end end
...@@ -77,7 +79,15 @@ module Gitlab ...@@ -77,7 +79,15 @@ module Gitlab
end end
def projects def projects
[project] group ? Project.inside_path(group.full_path) : [project]
end
def group
@group ||= options.fetch(:group, nil)
end
def project
@project ||= options.fetch(:project, nil)
end end
end end
end end
......
...@@ -11,7 +11,9 @@ module Gitlab ...@@ -11,7 +11,9 @@ module Gitlab
mr_table[:id], mr_table[:id],
mr_table[:created_at], mr_table[:created_at],
mr_table[:state], mr_table[:state],
mr_table[:author_id]] mr_table[:author_id],
projects_table[:name],
routes_table[:path]]
@order = mr_table[:created_at] @order = mr_table[:created_at]
super(*args) super(*args)
...@@ -20,7 +22,7 @@ module Gitlab ...@@ -20,7 +22,7 @@ module Gitlab
private private
def serialize(event) def serialize(event)
AnalyticsMergeRequestSerializer.new(project: project).represent(event) AnalyticsMergeRequestSerializer.new(serialization_context).represent(event)
end end
def allowed_ids_finder_class def allowed_ids_finder_class
......
# frozen_string_literal: true
module Gitlab
module CycleAnalytics
class GroupStageSummary
def initialize(group, from:, current_user:)
@group = group
@from = from
@current_user = current_user
end
def data
[serialize(Summary::Group::Issue.new(group: @group, from: @from, current_user: @current_user)),
serialize(Summary::Group::Deploy.new(group: @group, from: @from))]
end
private
def serialize(summary_object)
AnalyticsSummarySerializer.new.represent(summary_object)
end
end
end
end
...@@ -10,7 +10,9 @@ module Gitlab ...@@ -10,7 +10,9 @@ module Gitlab
issue_table[:iid], issue_table[:iid],
issue_table[:id], issue_table[:id],
issue_table[:created_at], issue_table[:created_at],
issue_table[:author_id]] issue_table[:author_id],
projects_table[:name],
routes_table[:path]]
super(*args) super(*args)
end end
...@@ -18,7 +20,7 @@ module Gitlab ...@@ -18,7 +20,7 @@ module Gitlab
private private
def serialize(event) def serialize(event)
AnalyticsIssueSerializer.new(project: project).represent(event) AnalyticsIssueSerializer.new(serialization_context).represent(event)
end end
def allowed_ids_finder_class def allowed_ids_finder_class
......
...@@ -5,8 +5,11 @@ module Gitlab ...@@ -5,8 +5,11 @@ module Gitlab
module IssueHelper module IssueHelper
def stage_query(project_ids) def stage_query(project_ids)
query = issue_table.join(issue_metrics_table).on(issue_table[:id].eq(issue_metrics_table[:issue_id])) query = issue_table.join(issue_metrics_table).on(issue_table[:id].eq(issue_metrics_table[:issue_id]))
.join(projects_table).on(issue_table[:project_id].eq(projects_table[:id]))
.join(routes_table).on(projects_table[:namespace_id].eq(routes_table[:source_id]))
.project(issue_table[:project_id].as("project_id")) .project(issue_table[:project_id].as("project_id"))
.where(issue_table[:project_id].in(project_ids)) .where(issue_table[:project_id].in(project_ids))
.where(routes_table[:source_type].eq('Namespace'))
.where(issue_table[:created_at].gteq(options[:from])) .where(issue_table[:created_at].gteq(options[:from]))
.where(issue_metrics_table[:first_added_to_board_at].not_eq(nil).or(issue_metrics_table[:first_associated_with_milestone_at].not_eq(nil))) .where(issue_metrics_table[:first_added_to_board_at].not_eq(nil).or(issue_metrics_table[:first_associated_with_milestone_at].not_eq(nil)))
......
...@@ -35,6 +35,14 @@ module Gitlab ...@@ -35,6 +35,14 @@ module Gitlab
User.arel_table User.arel_table
end end
def projects_table
Project.arel_table
end
def routes_table
Route.arel_table
end
def build_table def build_table
::CommitStatus.arel_table ::CommitStatus.arel_table
end end
......
...@@ -23,7 +23,7 @@ module Gitlab ...@@ -23,7 +23,7 @@ module Gitlab
end end
def get def get
::CycleAnalytics::Base::STAGES.each do |stage| ::CycleAnalytics::BaseMethods::STAGES.each do |stage|
@stage_permission_hash[stage] = authorized_stage?(stage) @stage_permission_hash[stage] = authorized_stage?(stage)
end end
......
...@@ -10,7 +10,9 @@ module Gitlab ...@@ -10,7 +10,9 @@ module Gitlab
issue_table[:iid], issue_table[:iid],
issue_table[:id], issue_table[:id],
issue_table[:created_at], issue_table[:created_at],
issue_table[:author_id]] issue_table[:author_id],
projects_table[:name],
routes_table[:path]]
super(*args) super(*args)
end end
...@@ -18,7 +20,7 @@ module Gitlab ...@@ -18,7 +20,7 @@ module Gitlab
private private
def serialize(event) def serialize(event)
AnalyticsIssueSerializer.new(project: project).represent(event) AnalyticsIssueSerializer.new(serialization_context).represent(event)
end end
def allowed_ids_finder_class def allowed_ids_finder_class
......
...@@ -5,14 +5,21 @@ module Gitlab ...@@ -5,14 +5,21 @@ module Gitlab
module PlanHelper module PlanHelper
def stage_query(project_ids) def stage_query(project_ids)
query = issue_table.join(issue_metrics_table).on(issue_table[:id].eq(issue_metrics_table[:issue_id])) query = issue_table.join(issue_metrics_table).on(issue_table[:id].eq(issue_metrics_table[:issue_id]))
.join(projects_table).on(issue_table[:project_id].eq(projects_table[:id]))
.join(routes_table).on(projects_table[:namespace_id].eq(routes_table[:source_id]))
.project(issue_table[:project_id].as("project_id")) .project(issue_table[:project_id].as("project_id"))
.where(issue_table[:project_id].in(project_ids)) .where(issue_table[:project_id].in(project_ids))
.where(issue_table[:created_at].gteq(options[:from])) .where(routes_table[:source_type].eq('Namespace'))
.where(issue_metrics_table[:first_added_to_board_at].not_eq(nil).or(issue_metrics_table[:first_associated_with_milestone_at].not_eq(nil))) query = add_conditions_to_query(query)
.where(issue_metrics_table[:first_mentioned_in_commit_at].not_eq(nil))
query query
end end
def add_conditions_to_query(query)
query.where(issue_table[:created_at].gteq(options[:from]))
.where(issue_metrics_table[:first_added_to_board_at].not_eq(nil).or(issue_metrics_table[:first_associated_with_milestone_at].not_eq(nil)))
.where(issue_metrics_table[:first_mentioned_in_commit_at].not_eq(nil))
end
end end
end end
end end
...@@ -10,7 +10,9 @@ module Gitlab ...@@ -10,7 +10,9 @@ module Gitlab
issue_table[:iid], issue_table[:iid],
issue_table[:id], issue_table[:id],
issue_table[:created_at], issue_table[:created_at],
issue_table[:author_id]] issue_table[:author_id],
projects_table[:name],
routes_table[:path]]
super(*args) super(*args)
end end
...@@ -18,7 +20,7 @@ module Gitlab ...@@ -18,7 +20,7 @@ module Gitlab
private private
def serialize(event) def serialize(event)
AnalyticsIssueSerializer.new(project: project).represent(event) AnalyticsIssueSerializer.new(serialization_context).represent(event)
end end
def allowed_ids_finder_class def allowed_ids_finder_class
......
...@@ -11,7 +11,9 @@ module Gitlab ...@@ -11,7 +11,9 @@ module Gitlab
mr_table[:id], mr_table[:id],
mr_table[:created_at], mr_table[:created_at],
mr_table[:state], mr_table[:state],
mr_table[:author_id]] mr_table[:author_id],
projects_table[:name],
routes_table[:path]]
super(*args) super(*args)
end end
...@@ -19,7 +21,7 @@ module Gitlab ...@@ -19,7 +21,7 @@ module Gitlab
private private
def serialize(event) def serialize(event)
AnalyticsMergeRequestSerializer.new(project: project).represent(event) AnalyticsMergeRequestSerializer.new(serialization_context).represent(event)
end end
def allowed_ids_finder_class def allowed_ids_finder_class
......
# frozen_string_literal: true
module Gitlab
module CycleAnalytics
module Summary
module Group
class Base
def initialize(group:, from:)
@group = group
@from = from
end
def title
raise NotImplementedError.new("Expected #{self.name} to implement title")
end
def value
raise NotImplementedError.new("Expected #{self.name} to implement value")
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module CycleAnalytics
module Summary
module Group
class Deploy < Group::Base
def title
n_('Deploy', 'Deploys', value)
end
def value
@value ||= Deployment.joins(:project)
.where(projects: { id: projects })
.where("deployments.created_at > ?", @from)
.success
.count
end
private
def projects
Project.inside_path(@group.full_path).ids
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module CycleAnalytics
module Summary
module Group
class Issue < Group::Base
def initialize(group:, from:, current_user:)
@group = group
@from = from
@current_user = current_user
end
def title
n_('New Issue', 'New Issues', value)
end
def value
@value ||= IssuesFinder.new(@current_user, group_id: @group.id, include_subgroups: true).execute.created_after(@from).count
end
end
end
end
end
end
...@@ -9,12 +9,12 @@ describe Gitlab::CycleAnalytics::BaseEventFetcher do ...@@ -9,12 +9,12 @@ describe Gitlab::CycleAnalytics::BaseEventFetcher do
let(:options) do let(:options) do
{ start_time_attrs: start_time_attrs, { start_time_attrs: start_time_attrs,
end_time_attrs: end_time_attrs, end_time_attrs: end_time_attrs,
from: 30.days.ago } from: 30.days.ago,
project: project }
end end
subject do subject do
described_class.new(project: project, described_class.new(stage: :issue,
stage: :issue,
options: options).fetch options: options).fetch
end end
......
...@@ -5,31 +5,31 @@ describe Gitlab::CycleAnalytics::CodeStage do ...@@ -5,31 +5,31 @@ describe Gitlab::CycleAnalytics::CodeStage do
let(:stage_name) { :code } let(:stage_name) { :code }
let(:project) { create(:project) } let(:project) { create(:project) }
let!(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) } let(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) }
let!(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) } let(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) }
let!(:issue_3) { create(:issue, project: project, created_at: 60.minutes.ago) } let(:issue_3) { create(:issue, project: project, created_at: 60.minutes.ago) }
let!(:mr_1) { create(:merge_request, source_project: project, created_at: 15.minutes.ago) } let(:mr_1) { create(:merge_request, source_project: project, created_at: 15.minutes.ago) }
let!(:mr_2) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'A') } let(:mr_2) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'A') }
let!(:mr_3) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'B') } let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: project.creator, project: project }) }
let(:stage) { described_class.new(project: project, options: { from: 2.days.ago, current_user: project.creator }) }
before do before do
issue_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago, first_mentioned_in_commit_at: 45.minutes.ago) issue_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago, first_mentioned_in_commit_at: 45.minutes.ago)
issue_2.metrics.update!(first_added_to_board_at: 60.minutes.ago, first_mentioned_in_commit_at: 40.minutes.ago) issue_2.metrics.update!(first_added_to_board_at: 60.minutes.ago, first_mentioned_in_commit_at: 40.minutes.ago)
issue_3.metrics.update!(first_added_to_board_at: 60.minutes.ago, first_mentioned_in_commit_at: 40.minutes.ago) issue_3.metrics.update!(first_added_to_board_at: 60.minutes.ago, first_mentioned_in_commit_at: 40.minutes.ago)
create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'B')
create(:merge_requests_closing_issues, merge_request: mr_1, issue: issue_1) create(:merge_requests_closing_issues, merge_request: mr_1, issue: issue_1)
create(:merge_requests_closing_issues, merge_request: mr_2, issue: issue_2) create(:merge_requests_closing_issues, merge_request: mr_2, issue: issue_2)
end end
it_behaves_like 'base stage' it_behaves_like 'base stage'
describe '#median' do describe '#project_median' do
around do |example| around do |example|
Timecop.freeze { example.run } Timecop.freeze { example.run }
end end
it 'counts median from issues with metrics' do it 'counts median from issues with metrics' do
expect(stage.median).to eq(ISSUES_MEDIAN) expect(stage.project_median).to eq(ISSUES_MEDIAN)
end end
end end
...@@ -41,4 +41,80 @@ describe Gitlab::CycleAnalytics::CodeStage do ...@@ -41,4 +41,80 @@ describe Gitlab::CycleAnalytics::CodeStage do
expect(result.map { |event| event[:title] }).to contain_exactly(mr_1.title, mr_2.title) expect(result.map { |event| event[:title] }).to contain_exactly(mr_1.title, mr_2.title)
end end
end end
context 'when group is given' do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:project_2) { create(:project, group: group) }
let(:project_3) { create(:project, group: group) }
let(:issue_2_1) { create(:issue, project: project_2, created_at: 90.minutes.ago) }
let(:issue_2_2) { create(:issue, project: project_3, created_at: 60.minutes.ago) }
let(:issue_2_3) { create(:issue, project: project_2, created_at: 60.minutes.ago) }
let(:mr_2_1) { create(:merge_request, source_project: project_2, created_at: 15.minutes.ago) }
let(:mr_2_2) { create(:merge_request, source_project: project_3, created_at: 10.minutes.ago, source_branch: 'A') }
let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: user, group: group }) }
before do
group.add_owner(user)
issue_2_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago, first_mentioned_in_commit_at: 45.minutes.ago)
issue_2_2.metrics.update!(first_added_to_board_at: 60.minutes.ago, first_mentioned_in_commit_at: 40.minutes.ago)
issue_2_3.metrics.update!(first_added_to_board_at: 60.minutes.ago, first_mentioned_in_commit_at: 40.minutes.ago)
create(:merge_requests_closing_issues, merge_request: mr_2_1, issue: issue_2_1)
create(:merge_requests_closing_issues, merge_request: mr_2_2, issue: issue_2_2)
end
describe '#group_median' do
around do |example|
Timecop.freeze { example.run }
end
it 'counts median from issues with metrics' do
expect(stage.group_median).to eq(ISSUES_MEDIAN)
end
end
describe '#events' do
it 'exposes merge requests that close issues' do
result = stage.events
expect(result.count).to eq(2)
expect(result.map { |event| event[:title] }).to contain_exactly(mr_2_1.title, mr_2_2.title)
end
end
context 'when subgroup is given' do
let(:subgroup) { create(:group, parent: group) }
let(:project_4) { create(:project, group: subgroup) }
let(:project_5) { create(:project, group: subgroup) }
let(:issue_3_1) { create(:issue, project: project_4, created_at: 90.minutes.ago) }
let(:issue_3_2) { create(:issue, project: project_5, created_at: 60.minutes.ago) }
let(:issue_3_3) { create(:issue, project: project_5, created_at: 60.minutes.ago) }
let(:mr_3_1) { create(:merge_request, source_project: project_4, created_at: 15.minutes.ago) }
let(:mr_3_2) { create(:merge_request, source_project: project_5, created_at: 10.minutes.ago, source_branch: 'A') }
before do
issue_3_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago, first_mentioned_in_commit_at: 45.minutes.ago)
issue_3_2.metrics.update!(first_added_to_board_at: 60.minutes.ago, first_mentioned_in_commit_at: 40.minutes.ago)
issue_3_3.metrics.update!(first_added_to_board_at: 60.minutes.ago, first_mentioned_in_commit_at: 40.minutes.ago)
create(:merge_requests_closing_issues, merge_request: mr_3_1, issue: issue_3_1)
create(:merge_requests_closing_issues, merge_request: mr_3_2, issue: issue_3_2)
end
describe '#events' do
it 'exposes merge requests that close issues' do
result = stage.events
expect(result.count).to eq(4)
expect(result.map { |event| event[:title] }).to contain_exactly(mr_2_1.title, mr_2_2.title, mr_3_1.title, mr_3_2.title)
end
it 'exposes merge requests that close issues with full path for subgroup' do
result = stage.events
expect(result.count).to eq(4)
expect(result.find { |event| event[:title] == mr_3_1.title }[:url]).to include("#{subgroup.full_path}")
end
end
end
end
end end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::CycleAnalytics::GroupStageSummary 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(:user) { create(:user, :admin) }
subject { described_class.new(group, from: Time.now, current_user: user).data }
describe "#new_issues" do
it "finds the number of issues created after the 'from date'" do
Timecop.freeze(5.days.ago) { create(:issue, project: project) }
Timecop.freeze(5.days.ago) { create(:issue, project: project_2) }
Timecop.freeze(5.days.from_now) { create(:issue, project: project) }
Timecop.freeze(5.days.from_now) { create(:issue, project: project_2) }
expect(subject.first[:value]).to eq(2)
end
it "doesn't find issues from other projects" do
Timecop.freeze(5.days.from_now) { create(:issue, project: create(:project, namespace: create(:group))) }
Timecop.freeze(5.days.from_now) { create(:issue, project: project) }
Timecop.freeze(5.days.from_now) { create(:issue, project: project_2) }
expect(subject.first[:value]).to eq(2)
end
it "finds issues from subgroups" do
Timecop.freeze(5.days.from_now) { create(:issue, project: create(:project, namespace: create(:group, parent: group))) }
Timecop.freeze(5.days.from_now) { create(:issue, project: project) }
Timecop.freeze(5.days.from_now) { create(:issue, project: project_2) }
expect(subject.first[:value]).to eq(3)
end
end
describe "#deploys" do
it "finds the number of deploys made created after the 'from date'" do
Timecop.freeze(5.days.ago) { create(:deployment, :success, project: project) }
Timecop.freeze(5.days.from_now) { create(:deployment, :success, project: project) }
Timecop.freeze(5.days.ago) { create(:deployment, :success, project: project_2) }
Timecop.freeze(5.days.from_now) { create(:deployment, :success, project: project_2) }
expect(subject.second[:value]).to eq(2)
end
it "doesn't find deploys from other projects" do
Timecop.freeze(5.days.from_now) do
create(:deployment, :success, project: create(:project, :repository, namespace: create(:group)))
end
expect(subject.second[:value]).to eq(0)
end
it "finds deploys from subgroups" do
Timecop.freeze(5.days.from_now) do
create(:deployment, :success, project: create(:project, :repository, namespace: create(:group, parent: group)))
end
expect(subject.second[:value]).to eq(1)
end
end
end
...@@ -4,11 +4,11 @@ require 'lib/gitlab/cycle_analytics/shared_stage_spec' ...@@ -4,11 +4,11 @@ require 'lib/gitlab/cycle_analytics/shared_stage_spec'
describe Gitlab::CycleAnalytics::IssueStage do describe Gitlab::CycleAnalytics::IssueStage do
let(:stage_name) { :issue } let(:stage_name) { :issue }
let(:project) { create(:project) } let(:project) { create(:project) }
let!(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) } let(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) }
let!(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) } let(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) }
let!(:issue_3) { create(:issue, project: project, created_at: 30.minutes.ago) } let(:issue_3) { create(:issue, project: project, created_at: 30.minutes.ago) }
let!(:issue_without_milestone) { create(:issue, project: project, created_at: 1.minute.ago) } let!(:issue_without_milestone) { create(:issue, project: project, created_at: 1.minute.ago) }
let(:stage) { described_class.new(project: project, options: { from: 2.days.ago, current_user: project.creator }) } let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: project.creator, project: project }) }
before do before do
issue_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago ) issue_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago )
...@@ -24,7 +24,7 @@ describe Gitlab::CycleAnalytics::IssueStage do ...@@ -24,7 +24,7 @@ describe Gitlab::CycleAnalytics::IssueStage do
end end
it 'counts median from issues with metrics' do it 'counts median from issues with metrics' do
expect(stage.median).to eq(ISSUES_MEDIAN) expect(stage.project_median).to eq(ISSUES_MEDIAN)
end end
end end
...@@ -36,4 +36,69 @@ describe Gitlab::CycleAnalytics::IssueStage do ...@@ -36,4 +36,69 @@ describe Gitlab::CycleAnalytics::IssueStage do
expect(result.map { |event| event[:title] }).to contain_exactly(issue_1.title, issue_2.title, issue_3.title) expect(result.map { |event| event[:title] }).to contain_exactly(issue_1.title, issue_2.title, issue_3.title)
end end
end end
context 'when group is given' do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:project_2) { create(:project, group: group) }
let(:project_3) { create(:project, group: group) }
let(:issue_2_1) { create(:issue, project: project_2, created_at: 90.minutes.ago) }
let(:issue_2_2) { create(:issue, project: project_3, created_at: 60.minutes.ago) }
let(:issue_2_3) { create(:issue, project: project_2, created_at: 60.minutes.ago) }
let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: user, group: group }) }
before do
group.add_owner(user)
issue_2_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago)
issue_2_2.metrics.update!(first_added_to_board_at: 30.minutes.ago)
end
describe '#group_median' do
around do |example|
Timecop.freeze { example.run }
end
it 'counts median from issues with metrics' do
expect(stage.group_median).to eq(ISSUES_MEDIAN)
end
end
describe '#events' do
it 'exposes merge requests that close issues' do
result = stage.events
expect(result.count).to eq(2)
expect(result.map { |event| event[:title] }).to contain_exactly(issue_2_1.title, issue_2_2.title)
end
end
context 'when subgroup is given' do
let(:subgroup) { create(:group, parent: group) }
let(:project_4) { create(:project, group: subgroup) }
let(:project_5) { create(:project, group: subgroup) }
let(:issue_3_1) { create(:issue, project: project_4, created_at: 90.minutes.ago) }
let(:issue_3_2) { create(:issue, project: project_5, created_at: 60.minutes.ago) }
let(:issue_3_3) { create(:issue, project: project_5, created_at: 60.minutes.ago) }
before do
issue_3_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago)
issue_3_2.metrics.update!(first_added_to_board_at: 30.minutes.ago)
end
describe '#events' do
it 'exposes merge requests that close issues' do
result = stage.events
expect(result.count).to eq(4)
expect(result.map { |event| event[:title] }).to contain_exactly(issue_2_1.title, issue_2_2.title, issue_3_1.title, issue_3_2.title)
end
it 'exposes merge requests that close issues with full path for subgroup' do
result = stage.events
expect(result.count).to eq(4)
expect(result.find { |event| event[:title] == issue_3_1.title }[:url]).to include("#{subgroup.full_path}")
end
end
end
end
end end
...@@ -8,7 +8,7 @@ describe Gitlab::CycleAnalytics::PlanStage do ...@@ -8,7 +8,7 @@ describe Gitlab::CycleAnalytics::PlanStage do
let!(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) } let!(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) }
let!(:issue_3) { create(:issue, project: project, created_at: 30.minutes.ago) } let!(:issue_3) { create(:issue, project: project, created_at: 30.minutes.ago) }
let!(:issue_without_milestone) { create(:issue, project: project, created_at: 1.minute.ago) } let!(:issue_without_milestone) { create(:issue, project: project, created_at: 1.minute.ago) }
let(:stage) { described_class.new(project: project, options: { from: 2.days.ago, current_user: project.creator }) } let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: project.creator, project: project }) }
before do before do
issue_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago, first_mentioned_in_commit_at: 10.minutes.ago) issue_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago, first_mentioned_in_commit_at: 10.minutes.ago)
...@@ -18,13 +18,13 @@ describe Gitlab::CycleAnalytics::PlanStage do ...@@ -18,13 +18,13 @@ describe Gitlab::CycleAnalytics::PlanStage do
it_behaves_like 'base stage' it_behaves_like 'base stage'
describe '#median' do describe '#project_median' do
around do |example| around do |example|
Timecop.freeze { example.run } Timecop.freeze { example.run }
end end
it 'counts median from issues with metrics' do it 'counts median from issues with metrics' do
expect(stage.median).to eq(ISSUES_MEDIAN) expect(stage.project_median).to eq(ISSUES_MEDIAN)
end end
end end
...@@ -36,4 +36,72 @@ describe Gitlab::CycleAnalytics::PlanStage do ...@@ -36,4 +36,72 @@ describe Gitlab::CycleAnalytics::PlanStage do
expect(result.map { |event| event[:title] }).to contain_exactly(issue_1.title, issue_2.title) expect(result.map { |event| event[:title] }).to contain_exactly(issue_1.title, issue_2.title)
end end
end end
context 'when group is given' do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:project_2) { create(:project, group: group) }
let(:project_3) { create(:project, group: group) }
let(:issue_2_1) { create(:issue, project: project_2, created_at: 90.minutes.ago) }
let(:issue_2_2) { create(:issue, project: project_3, created_at: 60.minutes.ago) }
let(:issue_2_3) { create(:issue, project: project_2, created_at: 60.minutes.ago) }
let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: user, group: group }) }
before do
group.add_owner(user)
issue_2_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago, first_mentioned_in_commit_at: 10.minutes.ago)
issue_2_2.metrics.update!(first_added_to_board_at: 30.minutes.ago, first_mentioned_in_commit_at: 20.minutes.ago)
issue_2_3.metrics.update!(first_added_to_board_at: 15.minutes.ago)
end
describe '#group_median' do
around do |example|
Timecop.freeze { example.run }
end
it 'counts median from issues with metrics' do
expect(stage.group_median).to eq(ISSUES_MEDIAN)
end
end
describe '#events' do
it 'exposes merge requests that close issues' do
result = stage.events
expect(result.count).to eq(2)
expect(result.map { |event| event[:title] }).to contain_exactly(issue_2_1.title, issue_2_2.title)
end
end
context 'when subgroup is given' do
let(:subgroup) { create(:group, parent: group) }
let(:project_4) { create(:project, group: subgroup) }
let(:project_5) { create(:project, group: subgroup) }
let(:issue_3_1) { create(:issue, project: project_4, created_at: 90.minutes.ago) }
let(:issue_3_2) { create(:issue, project: project_5, created_at: 60.minutes.ago) }
let(:issue_3_3) { create(:issue, project: project_5, created_at: 60.minutes.ago) }
before do
issue_3_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago, first_mentioned_in_commit_at: 10.minutes.ago)
issue_3_2.metrics.update!(first_added_to_board_at: 30.minutes.ago, first_mentioned_in_commit_at: 20.minutes.ago)
issue_3_3.metrics.update!(first_added_to_board_at: 15.minutes.ago)
end
describe '#events' do
it 'exposes merge requests that close issues' do
result = stage.events
expect(result.count).to eq(4)
expect(result.map { |event| event[:title] }).to contain_exactly(issue_2_1.title, issue_2_2.title, issue_3_1.title, issue_3_2.title)
end
it 'exposes merge requests that close issues with full path for subgroup' do
result = stage.events
expect(result.count).to eq(4)
expect(result.find { |event| event[:title] == issue_3_1.title }[:url]).to include("#{subgroup.full_path}")
end
end
end
end
end end
...@@ -4,14 +4,14 @@ require 'lib/gitlab/cycle_analytics/shared_stage_spec' ...@@ -4,14 +4,14 @@ require 'lib/gitlab/cycle_analytics/shared_stage_spec'
describe Gitlab::CycleAnalytics::ReviewStage do describe Gitlab::CycleAnalytics::ReviewStage do
let(:stage_name) { :review } let(:stage_name) { :review }
let(:project) { create(:project) } let(:project) { create(:project) }
let!(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) } let(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) }
let!(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) } let(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) }
let!(:issue_3) { create(:issue, project: project, created_at: 60.minutes.ago) } let(:issue_3) { create(:issue, project: project, created_at: 60.minutes.ago) }
let!(:mr_1) { create(:merge_request, :closed, source_project: project, created_at: 60.minutes.ago) } let(:mr_1) { create(:merge_request, :closed, source_project: project, created_at: 60.minutes.ago) }
let!(:mr_2) { create(:merge_request, :closed, source_project: project, created_at: 40.minutes.ago, source_branch: 'A') } let(:mr_2) { create(:merge_request, :closed, source_project: project, created_at: 40.minutes.ago, source_branch: 'A') }
let!(:mr_3) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'B') } let(:mr_3) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'B') }
let!(:mr_4) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'C') } let!(:mr_4) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'C') }
let(:stage) { described_class.new(project: project, options: { from: 2.days.ago, current_user: project.creator }) } let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: project.creator, project: project }) }
before do before do
mr_1.metrics.update!(merged_at: 30.minutes.ago) mr_1.metrics.update!(merged_at: 30.minutes.ago)
...@@ -24,13 +24,13 @@ describe Gitlab::CycleAnalytics::ReviewStage do ...@@ -24,13 +24,13 @@ describe Gitlab::CycleAnalytics::ReviewStage do
it_behaves_like 'base stage' it_behaves_like 'base stage'
describe '#median' do describe '#project_median' do
around do |example| around do |example|
Timecop.freeze { example.run } Timecop.freeze { example.run }
end end
it 'counts median from issues with metrics' do it 'counts median from issues with metrics' do
expect(stage.median).to eq(ISSUES_MEDIAN) expect(stage.project_median).to eq(ISSUES_MEDIAN)
end end
end end
...@@ -42,4 +42,47 @@ describe Gitlab::CycleAnalytics::ReviewStage do ...@@ -42,4 +42,47 @@ describe Gitlab::CycleAnalytics::ReviewStage do
expect(result.map { |event| event[:title] }).to contain_exactly(mr_1.title, mr_2.title) expect(result.map { |event| event[:title] }).to contain_exactly(mr_1.title, mr_2.title)
end end
end end
context 'when group is given' do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:project_2) { create(:project, group: group) }
let(:project_3) { create(:project, group: group) }
let(:issue_2_1) { create(:issue, project: project_2, created_at: 90.minutes.ago) }
let(:issue_2_2) { create(:issue, project: project_3, created_at: 60.minutes.ago) }
let(:issue_2_3) { create(:issue, project: project_2, created_at: 60.minutes.ago) }
let(:mr_2_1) { create(:merge_request, :closed, source_project: project_2, created_at: 60.minutes.ago) }
let(:mr_2_2) { create(:merge_request, :closed, source_project: project_3, created_at: 40.minutes.ago, source_branch: 'A') }
let(:mr_2_3) { create(:merge_request, source_project: project_2, created_at: 10.minutes.ago, source_branch: 'B') }
let!(:mr_2_4) { create(:merge_request, source_project: project_3, created_at: 10.minutes.ago, source_branch: 'C') }
let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: user, group: group }) }
before do
group.add_owner(user)
mr_2_1.metrics.update!(merged_at: 30.minutes.ago)
mr_2_2.metrics.update!(merged_at: 10.minutes.ago)
create(:merge_requests_closing_issues, merge_request: mr_2_1, issue: issue_2_1)
create(:merge_requests_closing_issues, merge_request: mr_2_2, issue: issue_2_2)
create(:merge_requests_closing_issues, merge_request: mr_2_3, issue: issue_2_3)
end
describe '#group_median' do
around do |example|
Timecop.freeze { example.run }
end
it 'counts median from issues with metrics' do
expect(stage.group_median).to eq(ISSUES_MEDIAN)
end
end
describe '#events' do
it 'exposes merge requests that close issues' do
result = stage.events
expect(result.count).to eq(2)
expect(result.map { |event| event[:title] }).to contain_exactly(mr_2_1.title, mr_2_2.title)
end
end
end
end end
...@@ -2,7 +2,7 @@ require 'spec_helper' ...@@ -2,7 +2,7 @@ require 'spec_helper'
shared_examples 'default query config' do shared_examples 'default query config' do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:event) { described_class.new(project: project, stage: stage_name, options: { from: 1.day.ago }) } let(:event) { described_class.new(stage: stage_name, options: { from: 1.day.ago, project: project }) }
it 'has the stage attribute' do it 'has the stage attribute' do
expect(event.stage).not_to be_nil expect(event.stage).not_to be_nil
......
...@@ -3,10 +3,10 @@ require 'spec_helper' ...@@ -3,10 +3,10 @@ require 'spec_helper'
shared_examples 'base stage' do shared_examples 'base stage' do
ISSUES_MEDIAN = 30.minutes.to_i ISSUES_MEDIAN = 30.minutes.to_i
let(:stage) { described_class.new(project: double, options: {}) } let(:stage) { described_class.new(options: { project: double }) }
before do before do
allow(stage).to receive(:median).and_return(1.12) allow(stage).to receive(:project_median).and_return(1.12)
allow_any_instance_of(Gitlab::CycleAnalytics::BaseEventFetcher).to receive(:event_result).and_return({}) allow_any_instance_of(Gitlab::CycleAnalytics::BaseEventFetcher).to receive(:event_result).and_return({})
end end
......
...@@ -5,16 +5,16 @@ describe Gitlab::CycleAnalytics::StagingStage do ...@@ -5,16 +5,16 @@ describe Gitlab::CycleAnalytics::StagingStage do
let(:stage_name) { :staging } let(:stage_name) { :staging }
let(:project) { create(:project) } let(:project) { create(:project) }
let!(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) } let(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) }
let!(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) } let(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) }
let!(:issue_3) { create(:issue, project: project, created_at: 60.minutes.ago) } let(:issue_3) { create(:issue, project: project, created_at: 60.minutes.ago) }
let!(:mr_1) { create(:merge_request, :closed, source_project: project, created_at: 60.minutes.ago) } let(:mr_1) { create(:merge_request, :closed, source_project: project, created_at: 60.minutes.ago) }
let!(:mr_2) { create(:merge_request, :closed, source_project: project, created_at: 40.minutes.ago, source_branch: 'A') } let(:mr_2) { create(:merge_request, :closed, source_project: project, created_at: 40.minutes.ago, source_branch: 'A') }
let!(:mr_3) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'B') } let(:mr_3) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'B') }
let(:build_1) { create(:ci_build, project: project) } let(:build_1) { create(:ci_build, project: project) }
let(:build_2) { create(:ci_build, project: project) } let(:build_2) { create(:ci_build, project: project) }
let(:stage) { described_class.new(project: project, options: { from: 2.days.ago, current_user: project.creator }) } let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: project.creator, project: project }) }
before do before do
mr_1.metrics.update!(merged_at: 80.minutes.ago, first_deployed_to_production_at: 50.minutes.ago, pipeline_id: build_1.commit_id) mr_1.metrics.update!(merged_at: 80.minutes.ago, first_deployed_to_production_at: 50.minutes.ago, pipeline_id: build_1.commit_id)
...@@ -28,13 +28,13 @@ describe Gitlab::CycleAnalytics::StagingStage do ...@@ -28,13 +28,13 @@ describe Gitlab::CycleAnalytics::StagingStage do
it_behaves_like 'base stage' it_behaves_like 'base stage'
describe '#median' do describe '#project_median' do
around do |example| around do |example|
Timecop.freeze { example.run } Timecop.freeze { example.run }
end end
it 'counts median from issues with metrics' do it 'counts median from issues with metrics' do
expect(stage.median).to eq(ISSUES_MEDIAN) expect(stage.project_median).to eq(ISSUES_MEDIAN)
end end
end end
...@@ -46,4 +46,50 @@ describe Gitlab::CycleAnalytics::StagingStage do ...@@ -46,4 +46,50 @@ describe Gitlab::CycleAnalytics::StagingStage do
expect(result.map { |event| event[:name] }).to contain_exactly(build_1.name, build_2.name) expect(result.map { |event| event[:name] }).to contain_exactly(build_1.name, build_2.name)
end end
end end
context 'when group is given' do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:project_2) { create(:project, group: group) }
let(:project_3) { create(:project, group: group) }
let(:issue_2_1) { create(:issue, project: project_2, created_at: 90.minutes.ago) }
let(:issue_2_2) { create(:issue, project: project_3, created_at: 60.minutes.ago) }
let(:issue_2_3) { create(:issue, project: project_2, created_at: 60.minutes.ago) }
let(:mr_1) { create(:merge_request, :closed, source_project: project_2, created_at: 60.minutes.ago) }
let(:mr_2) { create(:merge_request, :closed, source_project: project_3, created_at: 40.minutes.ago, source_branch: 'A') }
let(:mr_3) { create(:merge_request, source_project: project_2, created_at: 10.minutes.ago, source_branch: 'B') }
let(:build_1) { create(:ci_build, project: project_2) }
let(:build_2) { create(:ci_build, project: project_3) }
let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: user, group: group }) }
before do
group.add_owner(user)
mr_1.metrics.update!(merged_at: 80.minutes.ago, first_deployed_to_production_at: 50.minutes.ago, pipeline_id: build_1.commit_id)
mr_2.metrics.update!(merged_at: 60.minutes.ago, first_deployed_to_production_at: 30.minutes.ago, pipeline_id: build_2.commit_id)
mr_3.metrics.update!(merged_at: 10.minutes.ago, first_deployed_to_production_at: 3.days.ago, pipeline_id: create(:ci_build, project: project_2).commit_id)
create(:merge_requests_closing_issues, merge_request: mr_1, issue: issue_2_1)
create(:merge_requests_closing_issues, merge_request: mr_2, issue: issue_2_2)
create(:merge_requests_closing_issues, merge_request: mr_3, issue: issue_2_3)
end
describe '#group_median' do
around do |example|
Timecop.freeze { example.run }
end
it 'counts median from issues with metrics' do
expect(stage.group_median).to eq(ISSUES_MEDIAN)
end
end
describe '#events' do
it 'exposes merge requests that close issues' do
result = stage.events
expect(result.count).to eq(2)
expect(result.map { |event| event[:name] }).to contain_exactly(build_1.name, build_2.name)
end
end
end
end end
...@@ -34,7 +34,7 @@ describe Gitlab::CycleAnalytics::UsageData do ...@@ -34,7 +34,7 @@ describe Gitlab::CycleAnalytics::UsageData do
expect(result).to have_key(:avg_cycle_analytics) expect(result).to have_key(:avg_cycle_analytics)
CycleAnalytics::Base::STAGES.each do |stage| CycleAnalytics::BaseMethods::STAGES.each do |stage|
expect(result[:avg_cycle_analytics]).to have_key(stage) expect(result[:avg_cycle_analytics]).to have_key(stage)
stage_values = result[:avg_cycle_analytics][stage] stage_values = result[:avg_cycle_analytics][stage]
......
...@@ -38,7 +38,7 @@ describe 'CycleAnalytics#code' do ...@@ -38,7 +38,7 @@ describe 'CycleAnalytics#code' do
merge_merge_requests_closing_issue(user, project, issue) merge_merge_requests_closing_issue(user, project, issue)
deploy_master(user, project) deploy_master(user, project)
expect(subject[:code].median).to be_nil expect(subject[:code].project_median).to be_nil
end end
end end
end end
...@@ -68,7 +68,7 @@ describe 'CycleAnalytics#code' do ...@@ -68,7 +68,7 @@ describe 'CycleAnalytics#code' do
merge_merge_requests_closing_issue(user, project, issue) merge_merge_requests_closing_issue(user, project, issue)
expect(subject[:code].median).to be_nil expect(subject[:code].project_median).to be_nil
end end
end end
end end
......
# frozen_string_literal: true
require 'spec_helper'
describe CycleAnalytics::GroupLevel do
let(:group) { create(:group)}
let(:project) { create(:project, :repository, namespace: group) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
let(:issue) { create(:issue, project: project, created_at: 2.days.ago) }
let(:milestone) { create(:milestone, project: project) }
let(:mr) { create_merge_request_closing_issue(user, project, issue, commit_message: "References #{issue.to_reference}") }
let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: mr.source_branch, sha: mr.source_branch_sha, head_pipeline_of: mr) }
subject { described_class.new(options: { from: from_date, group: group, current_user: user }) }
describe '#permissions' do
it 'returns permissions' do
expect(subject.permissions.values.uniq).to eq([true])
end
end
describe '#stats' do
before do
allow_any_instance_of(Gitlab::ReferenceExtractor).to receive(:issues).and_return([issue])
create_cycle(user, project, issue, mr, milestone, pipeline)
deploy_master(user, project)
end
it 'returns medians for each stage for a specific group' do
expect(subject.no_stats?).to eq(false)
end
end
describe '#summary' do
before do
create_cycle(user, project, issue, mr, milestone, pipeline)
deploy_master(user, project)
end
it 'returns medians for each stage for a specific group' do
expect(subject.summary.map { |summary| summary[:value] }).to contain_exactly(1, 1)
end
end
end
...@@ -43,7 +43,7 @@ describe 'CycleAnalytics#issue' do ...@@ -43,7 +43,7 @@ describe 'CycleAnalytics#issue' do
create_merge_request_closing_issue(user, project, issue) create_merge_request_closing_issue(user, project, issue)
merge_merge_requests_closing_issue(user, project, issue) merge_merge_requests_closing_issue(user, project, issue)
expect(subject[:issue].median).to be_nil expect(subject[:issue].project_median).to be_nil
end end
end end
end end
...@@ -47,7 +47,7 @@ describe 'CycleAnalytics#plan' do ...@@ -47,7 +47,7 @@ describe 'CycleAnalytics#plan' do
create_merge_request_closing_issue(user, project, issue, source_branch: branch_name) create_merge_request_closing_issue(user, project, issue, source_branch: branch_name)
merge_merge_requests_closing_issue(user, project, issue) merge_merge_requests_closing_issue(user, project, issue)
expect(subject[:issue].median).to be_nil expect(subject[:issue].project_median).to be_nil
end end
end end
end end
...@@ -41,7 +41,7 @@ describe 'CycleAnalytics#production' do ...@@ -41,7 +41,7 @@ describe 'CycleAnalytics#production' do
MergeRequests::MergeService.new(project, user).execute(merge_request) MergeRequests::MergeService.new(project, user).execute(merge_request)
deploy_master(user, project) deploy_master(user, project)
expect(subject[:production].median).to be_nil expect(subject[:production].project_median).to be_nil
end end
end end
...@@ -52,7 +52,7 @@ describe 'CycleAnalytics#production' do ...@@ -52,7 +52,7 @@ describe 'CycleAnalytics#production' do
MergeRequests::MergeService.new(project, user).execute(merge_request) MergeRequests::MergeService.new(project, user).execute(merge_request)
deploy_master(user, project, environment: 'staging') deploy_master(user, project, environment: 'staging')
expect(subject[:production].median).to be_nil expect(subject[:production].project_median).to be_nil
end end
end end
end end
...@@ -23,7 +23,7 @@ describe CycleAnalytics::ProjectLevel do ...@@ -23,7 +23,7 @@ describe CycleAnalytics::ProjectLevel do
it 'returns every median for each stage for a specific project' do it 'returns every median for each stage for a specific project' do
values = described_class::STAGES.each_with_object({}) do |stage_name, hsh| values = described_class::STAGES.each_with_object({}) do |stage_name, hsh|
hsh[stage_name] = subject[stage_name].median.presence hsh[stage_name] = subject[stage_name].project_median.presence
end end
expect(subject.all_medians_by_stage).to eq(values) expect(subject.all_medians_by_stage).to eq(values)
......
...@@ -28,7 +28,7 @@ describe 'CycleAnalytics#review' do ...@@ -28,7 +28,7 @@ describe 'CycleAnalytics#review' do
it "returns nil" do it "returns nil" do
MergeRequests::MergeService.new(project, user).execute(create(:merge_request)) MergeRequests::MergeService.new(project, user).execute(create(:merge_request))
expect(subject[:review].median).to be_nil expect(subject[:review].project_median).to be_nil
end end
end end
end end
...@@ -45,7 +45,7 @@ describe 'CycleAnalytics#staging' do ...@@ -45,7 +45,7 @@ describe 'CycleAnalytics#staging' do
MergeRequests::MergeService.new(project, user).execute(merge_request) MergeRequests::MergeService.new(project, user).execute(merge_request)
deploy_master(user, project) deploy_master(user, project)
expect(subject[:staging].median).to be_nil expect(subject[:staging].project_median).to be_nil
end end
end end
...@@ -56,7 +56,7 @@ describe 'CycleAnalytics#staging' do ...@@ -56,7 +56,7 @@ describe 'CycleAnalytics#staging' do
MergeRequests::MergeService.new(project, user).execute(merge_request) MergeRequests::MergeService.new(project, user).execute(merge_request)
deploy_master(user, project, environment: 'staging') deploy_master(user, project, environment: 'staging')
expect(subject[:staging].median).to be_nil expect(subject[:staging].project_median).to be_nil
end end
end end
end end
...@@ -36,7 +36,7 @@ describe 'CycleAnalytics#test' do ...@@ -36,7 +36,7 @@ describe 'CycleAnalytics#test' do
merge_merge_requests_closing_issue(user, project, issue) merge_merge_requests_closing_issue(user, project, issue)
expect(subject[:test].median).to be_nil expect(subject[:test].project_median).to be_nil
end end
end end
...@@ -47,7 +47,7 @@ describe 'CycleAnalytics#test' do ...@@ -47,7 +47,7 @@ describe 'CycleAnalytics#test' do
pipeline.run! pipeline.run!
pipeline.succeed! pipeline.succeed!
expect(subject[:test].median).to be_nil expect(subject[:test].project_median).to be_nil
end end
end end
...@@ -62,7 +62,7 @@ describe 'CycleAnalytics#test' do ...@@ -62,7 +62,7 @@ describe 'CycleAnalytics#test' do
merge_merge_requests_closing_issue(user, project, issue) merge_merge_requests_closing_issue(user, project, issue)
expect(subject[:test].median).to be_nil expect(subject[:test].project_median).to be_nil
end end
end end
...@@ -77,7 +77,7 @@ describe 'CycleAnalytics#test' do ...@@ -77,7 +77,7 @@ describe 'CycleAnalytics#test' do
merge_merge_requests_closing_issue(user, project, issue) merge_merge_requests_closing_issue(user, project, issue)
expect(subject[:test].median).to be_nil expect(subject[:test].project_median).to be_nil
end end
end end
end end
...@@ -9,12 +9,14 @@ describe AnalyticsIssueEntity do ...@@ -9,12 +9,14 @@ describe AnalyticsIssueEntity do
iid: "1", iid: "1",
id: "1", id: "1",
created_at: "2016-11-12 15:04:02.948604", created_at: "2016-11-12 15:04:02.948604",
author: user author: user,
name: project.name,
path: project.namespace
} }
end end
let(:project) { create(:project) } let(:project) { create(:project) }
let(:request) { EntityRequest.new(project: project, entity: :merge_request) } let(:request) { EntityRequest.new(entity: :merge_request) }
let(:entity) do let(:entity) do
described_class.new(entity_hash, request: request, project: project) described_class.new(entity_hash, request: request, project: project)
......
...@@ -3,7 +3,7 @@ require 'spec_helper' ...@@ -3,7 +3,7 @@ require 'spec_helper'
describe AnalyticsIssueSerializer do describe AnalyticsIssueSerializer do
subject do subject do
described_class described_class
.new(project: project, entity: :merge_request) .new(entity: :merge_request)
.represent(resource) .represent(resource)
end end
...@@ -16,7 +16,9 @@ describe AnalyticsIssueSerializer do ...@@ -16,7 +16,9 @@ describe AnalyticsIssueSerializer do
iid: "1", iid: "1",
id: "1", id: "1",
created_at: "2016-11-12 15:04:02.948604", created_at: "2016-11-12 15:04:02.948604",
author: user author: user,
name: project.name,
path: project.namespace
} }
end end
......
...@@ -3,7 +3,7 @@ require 'spec_helper' ...@@ -3,7 +3,7 @@ require 'spec_helper'
describe AnalyticsMergeRequestSerializer do describe AnalyticsMergeRequestSerializer do
subject do subject do
described_class described_class
.new(project: project, entity: :merge_request) .new(entity: :merge_request)
.represent(resource) .represent(resource)
end end
...@@ -17,7 +17,9 @@ describe AnalyticsMergeRequestSerializer do ...@@ -17,7 +17,9 @@ describe AnalyticsMergeRequestSerializer do
id: "1", id: "1",
state: 'open', state: 'open',
created_at: "2016-11-12 15:04:02.948604", created_at: "2016-11-12 15:04:02.948604",
author: user author: user,
name: project.name,
path: project.namespace
} }
end end
......
...@@ -6,11 +6,11 @@ describe AnalyticsStageSerializer do ...@@ -6,11 +6,11 @@ describe AnalyticsStageSerializer do
end end
let(:resource) do let(:resource) do
Gitlab::CycleAnalytics::CodeStage.new(project: double, options: {}) Gitlab::CycleAnalytics::CodeStage.new(options: { project: double })
end end
before do before do
allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:median).and_return(1.12) allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:project_median).and_return(1.12)
allow_any_instance_of(Gitlab::CycleAnalytics::BaseEventFetcher).to receive(:event_result).and_return({}) allow_any_instance_of(Gitlab::CycleAnalytics::BaseEventFetcher).to receive(:event_result).and_return({})
end end
...@@ -24,7 +24,7 @@ describe AnalyticsStageSerializer do ...@@ -24,7 +24,7 @@ describe AnalyticsStageSerializer do
context 'when median is equal 0' do context 'when median is equal 0' do
before do before do
allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:median).and_return(0) allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:project_median).and_return(0)
end end
it 'sets the value to nil' do it 'sets the value to nil' do
...@@ -34,7 +34,7 @@ describe AnalyticsStageSerializer do ...@@ -34,7 +34,7 @@ describe AnalyticsStageSerializer do
context 'when median is below 1' do context 'when median is below 1' do
before do before do
allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:median).and_return(0.12) allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:project_median).and_return(0.12)
end end
it 'sets the value to equal to median' do it 'sets the value to equal to median' do
...@@ -44,7 +44,7 @@ describe AnalyticsStageSerializer do ...@@ -44,7 +44,7 @@ describe AnalyticsStageSerializer do
context 'when median is above 1' do context 'when median is above 1' do
before do before do
allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:median).and_return(60.12) allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:project_median).and_return(60.12)
end end
it 'sets the value to equal to median' do it 'sets the value to equal to median' do
......
...@@ -50,7 +50,7 @@ module CycleAnalyticsHelpers ...@@ -50,7 +50,7 @@ module CycleAnalyticsHelpers
end end
median_time_difference = time_differences.sort[2] median_time_difference = time_differences.sort[2]
expect(subject[phase].median).to be_within(5).of(median_time_difference) expect(subject[phase].project_median).to be_within(5).of(median_time_difference)
end end
context "when the data belongs to another project" do context "when the data belongs to another project" do
...@@ -80,7 +80,7 @@ module CycleAnalyticsHelpers ...@@ -80,7 +80,7 @@ module CycleAnalyticsHelpers
# Turn off the stub before checking assertions # Turn off the stub before checking assertions
allow(self).to receive(:project).and_call_original allow(self).to receive(:project).and_call_original
expect(subject[phase].median).to be_nil expect(subject[phase].project_median).to be_nil
end end
end end
...@@ -103,7 +103,7 @@ module CycleAnalyticsHelpers ...@@ -103,7 +103,7 @@ module CycleAnalyticsHelpers
Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn
expect(subject[phase].median).to be_nil expect(subject[phase].project_median).to be_nil
end end
end end
end end
...@@ -121,7 +121,7 @@ module CycleAnalyticsHelpers ...@@ -121,7 +121,7 @@ module CycleAnalyticsHelpers
Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn
expect(subject[phase].median).to be_nil expect(subject[phase].project_median).to be_nil
end end
end end
end end
...@@ -138,7 +138,7 @@ module CycleAnalyticsHelpers ...@@ -138,7 +138,7 @@ module CycleAnalyticsHelpers
post_fn[self, data] if post_fn post_fn[self, data] if post_fn
expect(subject[phase].median).to be_nil expect(subject[phase].project_median).to be_nil
end end
end end
end end
...@@ -146,7 +146,7 @@ module CycleAnalyticsHelpers ...@@ -146,7 +146,7 @@ module CycleAnalyticsHelpers
context "when none of the start / end conditions are matched" do context "when none of the start / end conditions are matched" do
it "returns nil" do it "returns nil" do
expect(subject[phase].median).to be_nil expect(subject[phase].project_median).to be_nil
end end
end 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