Commit 1b0351c2 authored by Adam Hegyi's avatar Adam Hegyi

Project level VSA summary endpoint

Project level VSA summary endpoint with EE (permium) params filter.
parent f90a8af4
# 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
end
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
......
......@@ -14,7 +14,7 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController
feature_category :planning_analytics
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|
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
resources :value_streams, only: [:index] do
resources :stages, only: [:index]
end
resource :summary, controller: :summary, only: :show
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
custom_file_templates_for_namespace
custom_project_templates
cycle_analytics_for_groups
cycle_analytics_for_projects
db_load_balancing
default_branch_protection_restriction_in_groups
default_project_deletion_protection
......
......@@ -20,7 +20,7 @@ RSpec.describe Groups::Analytics::CycleAnalytics::SummaryController do
subject
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
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 @@
module Gitlab
module CycleAnalytics
class StageSummary
def initialize(project, from:, to: nil, current_user:)
def initialize(project, options:, current_user:)
@project = project
@from = from
@to = to
@options = options
@current_user = current_user
end
......@@ -20,15 +19,15 @@ module Gitlab
private
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
def commit_stats
serialize(Summary::Commit.new(project: @project, from: @from, to: @to))
serialize(Summary::Commit.new(project: @project, options: @options))
end
def deployments_summary
@deployments_summary ||= Summary::Deploy.new(project: @project, from: @from, to: @to)
@deployments_summary ||= Summary::Deploy.new(project: @project, options: @options)
end
def deploy_stats
......@@ -39,8 +38,7 @@ module Gitlab
serialize(
Summary::DeploymentFrequency.new(
deployments: deployments_summary.value.raw_value,
from: @from,
to: @to),
options: @options),
with_unit: true
)
end
......@@ -50,8 +48,7 @@ module Gitlab
end
def serialize(summary_object, with_unit: false)
AnalyticsSummarySerializer.new.represent(
summary_object, with_unit: with_unit)
AnalyticsSummarySerializer.new.represent(summary_object, with_unit: with_unit)
end
end
end
......
......@@ -4,10 +4,9 @@ module Gitlab
module CycleAnalytics
module Summary
class Base
def initialize(project:, from:, to: nil)
def initialize(project:, options:)
@project = project
@from = from
@to = to
@options = options
end
def title
......
......@@ -21,7 +21,7 @@ module Gitlab
def commits_count
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
def gitaly_commit_client
......
......@@ -16,7 +16,7 @@ module Gitlab
def deployments_count
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
.count
end
......
......@@ -6,10 +6,10 @@ module Gitlab
class DeploymentFrequency < Base
include SummaryHelper
def initialize(deployments:, from:, to: nil, project: nil)
def initialize(deployments:, options:, project: nil)
@deployments = deployments
super(project: project, from: from, to: to)
super(project: project, options: options)
end
def title
......@@ -17,7 +17,7 @@ module Gitlab
end
def value
@value ||= frequency(@deployments, @from, @to || Time.now)
@value ||= frequency(@deployments, @options[:from], @options[:to] || Time.current)
end
def unit
......
......@@ -4,10 +4,9 @@ module Gitlab
module CycleAnalytics
module Summary
class Issue < Base
def initialize(project:, from:, to: nil, current_user:)
def initialize(project:, options:, current_user:)
@project = project
@from = from
@to = to
@options = options
@current_user = current_user
end
......@@ -23,10 +22,18 @@ module Gitlab
def issues_count
IssuesFinder
.new(@current_user, project_id: @project.id, created_after: @from, created_before: @to)
.new(@current_user, finder_params)
.execute
.count
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
......
# 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'
RSpec.describe Gitlab::CycleAnalytics::StageSummary do
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) }
before do
project.add_maintainer(user)
end
let(:stage_summary) { described_class.new(project, **options).data }
let(:stage_summary) { described_class.new(project, **args).data }
describe "#new_issues" do
subject { stage_summary.first }
......@@ -117,11 +118,11 @@ RSpec.describe Gitlab::CycleAnalytics::StageSummary do
before do
project.add_guest(guest_user)
options.merge!({ current_user: guest_user })
args.merge!({ current_user: guest_user })
end
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
end
......
......@@ -12,7 +12,7 @@ RSpec.describe AnalyticsSummarySerializer do
let(:resource) do
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
before do
......@@ -36,7 +36,7 @@ RSpec.describe AnalyticsSummarySerializer do
context 'when representing with unit' do
let(:resource) do
Gitlab::CycleAnalytics::Summary::DeploymentFrequency
.new(deployments: 10, from: 1.day.ago)
.new(deployments: 10, options: { 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