Commit d398e8a7 authored by Dmytro Zaporozhets (DZ)'s avatar Dmytro Zaporozhets (DZ)

Merge branch '327570-project-level-vsa-summary' into 'master'

Project level VSA summary endpoint

See merge request gitlab-org/gitlab!62921
parents c216ffad 1b0351c2
# frozen_string_literal: true
class Projects::Analytics::CycleAnalytics::SummaryController < Projects::ApplicationController
include CycleAnalyticsParams
respond_to :json
feature_category :planning_analytics
before_action :authorize_read_cycle_analytics!
def show
render json: project_level.summary
end
private
def project_level
@project_level ||= Analytics::CycleAnalytics::ProjectLevel.new(project: @project, options: options(allowed_params))
end
def allowed_params
params.permit(:created_after, :created_before)
end
end
Projects::Analytics::CycleAnalytics::SummaryController.prepend_mod_with('Projects::Analytics::CycleAnalytics::SummaryController')
...@@ -53,7 +53,7 @@ module Projects ...@@ -53,7 +53,7 @@ module Projects
end end
def cycle_analytics def cycle_analytics
@cycle_analytics ||= ::CycleAnalytics::ProjectLevel.new(project, options: options(cycle_analytics_project_params)) @cycle_analytics ||= ::Analytics::CycleAnalytics::ProjectLevel.new(project: project, options: options(cycle_analytics_project_params))
end end
end end
end end
......
...@@ -14,7 +14,7 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController ...@@ -14,7 +14,7 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController
feature_category :planning_analytics feature_category :planning_analytics
def show def show
@cycle_analytics = ::CycleAnalytics::ProjectLevel.new(@project, options: options(cycle_analytics_project_params)) @cycle_analytics = Analytics::CycleAnalytics::ProjectLevel.new(project: @project, options: options(cycle_analytics_project_params))
respond_to do |format| respond_to do |format|
format.html do format.html do
......
# frozen_string_literal: true
module Analytics
module CycleAnalytics
class ProjectLevel
attr_reader :project, :options
def initialize(project:, options:)
@project = project
@options = options.merge(project: project)
end
def summary
@summary ||= ::Gitlab::CycleAnalytics::StageSummary.new(project,
options: options,
current_user: options[:current_user]).data
end
def permissions(user:)
Gitlab::CycleAnalytics::Permissions.get(user: user, project: project)
end
def stats
@stats ||= default_stage_names.map do |stage_name|
self[stage_name].as_json
end
end
def [](stage_name)
::CycleAnalytics::ProjectLevelStageAdapter.new(build_stage(stage_name), options)
end
private
def build_stage(stage_name)
stage_params = stage_params_by_name(stage_name).merge(project: project)
Analytics::CycleAnalytics::ProjectStage.new(stage_params)
end
def stage_params_by_name(name)
Gitlab::Analytics::CycleAnalytics::DefaultStages.find_by_name!(name)
end
def default_stage_names
Gitlab::Analytics::CycleAnalytics::DefaultStages.symbolized_stage_names
end
end
end
end
# frozen_string_literal: true
module CycleAnalytics
class ProjectLevel
attr_reader :project, :options
def initialize(project, options:)
@project = project
@options = options.merge(project: project)
end
def summary
@summary ||= ::Gitlab::CycleAnalytics::StageSummary.new(project,
from: options[:from],
to: options[:to],
current_user: options[:current_user]).data
end
def permissions(user:)
Gitlab::CycleAnalytics::Permissions.get(user: user, project: project)
end
def stats
@stats ||= default_stage_names.map do |stage_name|
self[stage_name].as_json
end
end
def [](stage_name)
CycleAnalytics::ProjectLevelStageAdapter.new(build_stage(stage_name), options)
end
private
def build_stage(stage_name)
stage_params = stage_params_by_name(stage_name).merge(project: project)
Analytics::CycleAnalytics::ProjectStage.new(stage_params)
end
def stage_params_by_name(name)
Gitlab::Analytics::CycleAnalytics::DefaultStages.find_by_name!(name)
end
def default_stage_names
Gitlab::Analytics::CycleAnalytics::DefaultStages.symbolized_stage_names
end
end
end
...@@ -273,6 +273,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do ...@@ -273,6 +273,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
resources :value_streams, only: [:index] do resources :value_streams, only: [:index] do
resources :stages, only: [:index] resources :stages, only: [:index]
end end
resource :summary, controller: :summary, only: :show
end end
end end
......
# frozen_string_literal: true
module EE::Projects::Analytics::CycleAnalytics::SummaryController
extend ::Gitlab::Utils::Override
private
override :allowed_params
def allowed_params
return super unless @project.licensed_feature_available?(:cycle_analytics_for_projects) # rubocop: disable Gitlab/ModuleWithInstanceVariables
request_params.to_data_collector_params
end
end
...@@ -71,6 +71,7 @@ class License < ApplicationRecord ...@@ -71,6 +71,7 @@ class License < ApplicationRecord
custom_file_templates_for_namespace custom_file_templates_for_namespace
custom_project_templates custom_project_templates
cycle_analytics_for_groups cycle_analytics_for_groups
cycle_analytics_for_projects
db_load_balancing db_load_balancing
default_branch_protection_restriction_in_groups default_branch_protection_restriction_in_groups
default_project_deletion_protection default_project_deletion_protection
......
...@@ -20,7 +20,7 @@ RSpec.describe Groups::Analytics::CycleAnalytics::SummaryController do ...@@ -20,7 +20,7 @@ RSpec.describe Groups::Analytics::CycleAnalytics::SummaryController do
subject subject
expect(response).to be_successful expect(response).to be_successful
expect(response).to match_response_schema('analytics/cycle_analytics/summary', dir: 'ee') expect(response).to match_response_schema('analytics/cycle_analytics/summary')
end end
include_examples 'Value Stream Analytics data endpoint examples' include_examples 'Value Stream Analytics data endpoint examples'
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::Analytics::CycleAnalytics::SummaryController do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:author) { create(:user) }
let_it_be(:issue_with_author) { create(:issue, project: project, author: author, created_at: Date.new(2015, 1, 1)) }
let_it_be(:issue_with_other_author) { create(:issue, project: project, author: user, created_at: Date.new(2015, 1, 1)) }
let(:params) { { namespace_id: project.namespace.to_param, project_id: project.to_param, created_after: '2010-01-01', created_before: '2020-01-02' } }
before do
sign_in(user)
end
describe 'GET "show"' do
subject { get :show, params: params }
before do
project.add_reporter(user)
params[:author_username] = issue_with_author.author.username
end
context 'when cycle_analytics_for_projects feature is available' do
before do
stub_licensed_features(cycle_analytics_for_projects: true)
end
it 'filters by author username' do
subject
expect(response).to be_successful
issue_count = json_response.first
expect(issue_count['value']).to eq('1')
end
end
context 'when cycle_analytics_for_projects feature is not available' do
it 'does not apply the filter' do
subject
expect(response).to be_successful
issue_count = json_response.first
expect(issue_count['value']).to eq('2')
end
end
end
end
...@@ -3,10 +3,9 @@ ...@@ -3,10 +3,9 @@
module Gitlab module Gitlab
module CycleAnalytics module CycleAnalytics
class StageSummary class StageSummary
def initialize(project, from:, to: nil, current_user:) def initialize(project, options:, current_user:)
@project = project @project = project
@from = from @options = options
@to = to
@current_user = current_user @current_user = current_user
end end
...@@ -20,15 +19,15 @@ module Gitlab ...@@ -20,15 +19,15 @@ module Gitlab
private private
def issue_stats def issue_stats
serialize(Summary::Issue.new(project: @project, from: @from, to: @to, current_user: @current_user)) serialize(Summary::Issue.new(project: @project, options: @options, current_user: @current_user))
end end
def commit_stats def commit_stats
serialize(Summary::Commit.new(project: @project, from: @from, to: @to)) serialize(Summary::Commit.new(project: @project, options: @options))
end end
def deployments_summary def deployments_summary
@deployments_summary ||= Summary::Deploy.new(project: @project, from: @from, to: @to) @deployments_summary ||= Summary::Deploy.new(project: @project, options: @options)
end end
def deploy_stats def deploy_stats
...@@ -39,8 +38,7 @@ module Gitlab ...@@ -39,8 +38,7 @@ module Gitlab
serialize( serialize(
Summary::DeploymentFrequency.new( Summary::DeploymentFrequency.new(
deployments: deployments_summary.value.raw_value, deployments: deployments_summary.value.raw_value,
from: @from, options: @options),
to: @to),
with_unit: true with_unit: true
) )
end end
...@@ -50,8 +48,7 @@ module Gitlab ...@@ -50,8 +48,7 @@ module Gitlab
end end
def serialize(summary_object, with_unit: false) def serialize(summary_object, with_unit: false)
AnalyticsSummarySerializer.new.represent( AnalyticsSummarySerializer.new.represent(summary_object, with_unit: with_unit)
summary_object, with_unit: with_unit)
end end
end end
end end
......
...@@ -4,10 +4,9 @@ module Gitlab ...@@ -4,10 +4,9 @@ module Gitlab
module CycleAnalytics module CycleAnalytics
module Summary module Summary
class Base class Base
def initialize(project:, from:, to: nil) def initialize(project:, options:)
@project = project @project = project
@from = from @options = options
@to = to
end end
def title def title
......
...@@ -21,7 +21,7 @@ module Gitlab ...@@ -21,7 +21,7 @@ module Gitlab
def commits_count def commits_count
return unless ref return unless ref
@commits_count ||= gitaly_commit_client.commit_count(ref, after: @from, before: @to) @commits_count ||= gitaly_commit_client.commit_count(ref, after: @options[:from], before: @options[:to])
end end
def gitaly_commit_client def gitaly_commit_client
......
...@@ -16,7 +16,7 @@ module Gitlab ...@@ -16,7 +16,7 @@ module Gitlab
def deployments_count def deployments_count
DeploymentsFinder DeploymentsFinder
.new(project: @project, finished_after: @from, finished_before: @to, status: :success, order_by: :finished_at) .new(project: @project, finished_after: @options[:from], finished_before: @options[:to], status: :success, order_by: :finished_at)
.execute .execute
.count .count
end end
......
...@@ -6,10 +6,10 @@ module Gitlab ...@@ -6,10 +6,10 @@ module Gitlab
class DeploymentFrequency < Base class DeploymentFrequency < Base
include SummaryHelper include SummaryHelper
def initialize(deployments:, from:, to: nil, project: nil) def initialize(deployments:, options:, project: nil)
@deployments = deployments @deployments = deployments
super(project: project, from: from, to: to) super(project: project, options: options)
end end
def title def title
...@@ -17,7 +17,7 @@ module Gitlab ...@@ -17,7 +17,7 @@ module Gitlab
end end
def value def value
@value ||= frequency(@deployments, @from, @to || Time.now) @value ||= frequency(@deployments, @options[:from], @options[:to] || Time.current)
end end
def unit def unit
......
...@@ -4,10 +4,9 @@ module Gitlab ...@@ -4,10 +4,9 @@ module Gitlab
module CycleAnalytics module CycleAnalytics
module Summary module Summary
class Issue < Base class Issue < Base
def initialize(project:, from:, to: nil, current_user:) def initialize(project:, options:, current_user:)
@project = project @project = project
@from = from @options = options
@to = to
@current_user = current_user @current_user = current_user
end end
...@@ -23,10 +22,18 @@ module Gitlab ...@@ -23,10 +22,18 @@ module Gitlab
def issues_count def issues_count
IssuesFinder IssuesFinder
.new(@current_user, project_id: @project.id, created_after: @from, created_before: @to) .new(@current_user, finder_params)
.execute .execute
.count .count
end end
def finder_params
@options.dup.tap do |hash|
hash[:created_after] = hash.delete(:from)
hash[:created_before] = hash.delete(:to)
hash[:project_id] = @project.id
end
end
end end
end end
end end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::Analytics::CycleAnalytics::SummaryController do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let(:params) { { namespace_id: project.namespace.to_param, project_id: project.to_param, created_after: '2010-01-01', created_before: '2010-01-02' } }
before do
sign_in(user)
end
describe 'GET "show"' do
subject { get :show, params: params }
it 'succeeds' do
project.add_reporter(user)
subject
expect(response).to be_successful
expect(response).to match_response_schema('analytics/cycle_analytics/summary')
end
context 'when analytics_disabled features are disabled' do
it 'renders 404' do
project.add_reporter(user)
project.project_feature.update!(analytics_access_level: ProjectFeature::DISABLED)
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when user is not part of the project' do
it 'renders 404' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
end
...@@ -4,14 +4,15 @@ require 'spec_helper' ...@@ -4,14 +4,15 @@ require 'spec_helper'
RSpec.describe Gitlab::CycleAnalytics::StageSummary do RSpec.describe Gitlab::CycleAnalytics::StageSummary do
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
let(:options) { { from: 1.day.ago, current_user: user } } let(:options) { { from: 1.day.ago } }
let(:args) { { options: options, current_user: user } }
let(:user) { create(:user, :admin) } let(:user) { create(:user, :admin) }
before do before do
project.add_maintainer(user) project.add_maintainer(user)
end end
let(:stage_summary) { described_class.new(project, **options).data } let(:stage_summary) { described_class.new(project, **args).data }
describe "#new_issues" do describe "#new_issues" do
subject { stage_summary.first } subject { stage_summary.first }
...@@ -117,11 +118,11 @@ RSpec.describe Gitlab::CycleAnalytics::StageSummary do ...@@ -117,11 +118,11 @@ RSpec.describe Gitlab::CycleAnalytics::StageSummary do
before do before do
project.add_guest(guest_user) project.add_guest(guest_user)
options.merge!({ current_user: guest_user }) args.merge!({ current_user: guest_user })
end end
it 'does not include commit stats' do it 'does not include commit stats' do
data = described_class.new(project, **options).data data = described_class.new(project, **args).data
expect(includes_commits?(data)).to be_falsy expect(includes_commits?(data)).to be_falsy
end end
......
...@@ -12,7 +12,7 @@ RSpec.describe AnalyticsSummarySerializer do ...@@ -12,7 +12,7 @@ RSpec.describe AnalyticsSummarySerializer do
let(:resource) do let(:resource) do
Gitlab::CycleAnalytics::Summary::Issue Gitlab::CycleAnalytics::Summary::Issue
.new(project: double, from: 1.day.ago, current_user: user) .new(project: double, options: { from: 1.day.ago }, current_user: user)
end end
before do before do
...@@ -36,7 +36,7 @@ RSpec.describe AnalyticsSummarySerializer do ...@@ -36,7 +36,7 @@ RSpec.describe AnalyticsSummarySerializer do
context 'when representing with unit' do context 'when representing with unit' do
let(:resource) do let(:resource) do
Gitlab::CycleAnalytics::Summary::DeploymentFrequency Gitlab::CycleAnalytics::Summary::DeploymentFrequency
.new(deployments: 10, from: 1.day.ago) .new(deployments: 10, options: { from: 1.day.ago })
end end
subject { described_class.new.represent(resource, with_unit: true) } 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