Commit 0172b7c9 authored by Adam Hegyi's avatar Adam Hegyi Committed by Jan Provaznik

Expose cycle analytics summary via API

This change exposes the summary counts for group level cycle analytics
via a separate endpoint while making sure the old CA implementation is
still working.
parent 51d5357b
# frozen_string_literal: true
module Analytics
module CycleAnalytics
class SummaryController < Analytics::ApplicationController
include CycleAnalyticsParams
check_feature_flag Gitlab::Analytics::CYCLE_ANALYTICS_FEATURE_FLAG
before_action :load_group
before_action :validate_params
def show
return render_403 unless can?(current_user, :read_group_cycle_analytics, @group)
group_level = ::CycleAnalytics::GroupLevel.new(group: @group, options: options(allowed_group_params))
render json: group_level.summary
end
private
def allowed_group_params
params.permit(:created_after, :created_before, project_ids: [])
end
def validate_params
if request_params.invalid?
render(
json: { message: 'Invalid parameters', errors: request_params.errors },
status: :unprocessable_entity
)
end
end
def request_params
@request_params ||= Gitlab::Analytics::CycleAnalytics::RequestParams.new(params.permit(:created_before, :created_after))
end
end
end
end
...@@ -14,6 +14,7 @@ namespace :analytics do ...@@ -14,6 +14,7 @@ namespace :analytics do
get :records get :records
end end
end end
resource :summary, controller: :summary, only: [:show]
end end
end end
......
...@@ -187,18 +187,7 @@ describe Analytics::CycleAnalytics::StagesController do ...@@ -187,18 +187,7 @@ describe Analytics::CycleAnalytics::StagesController do
expect(response).to match_response_schema('analytics/cycle_analytics/median', dir: 'ee') expect(response).to match_response_schema('analytics/cycle_analytics/median', dir: 'ee')
end end
context 'when params are invalid' do include_examples 'date parameter examples'
before do
params[:created_before] = '2018-01-01'
end
it 'renders `unprocessable_entity`' do
subject
expect(response).to have_gitlab_http_status(:unprocessable_entity)
expect(response).to match_response_schema('analytics/cycle_analytics/validation_error', dir: 'ee')
end
end
include_examples 'group permission check on the controller level' include_examples 'group permission check on the controller level'
end end
...@@ -217,6 +206,8 @@ describe Analytics::CycleAnalytics::StagesController do ...@@ -217,6 +206,8 @@ describe Analytics::CycleAnalytics::StagesController do
expect(response).to be_successful expect(response).to be_successful
end end
include_examples 'date parameter examples'
include_examples 'group permission check on the controller level' include_examples 'group permission check on the controller level'
end end
end end
......
# frozen_string_literal: true
require 'spec_helper'
describe Analytics::CycleAnalytics::SummaryController do
let_it_be(:user) { create(:user) }
let_it_be(:group, refind: true) { create(:group) }
let(:params) { { group_id: group.full_path, created_after: '2010-01-01', created_before: '2010-01-02' } }
before do
stub_feature_flags(Gitlab::Analytics::CYCLE_ANALYTICS_FEATURE_FLAG => true)
stub_licensed_features(cycle_analytics_for_groups: true)
group.add_reporter(user)
sign_in(user)
end
describe 'GET `show`' do
subject { get :show, params: params }
it 'succeeds' do
subject
expect(response).to be_successful
expect(response).to match_response_schema('analytics/cycle_analytics/summary', dir: 'ee')
end
include_examples 'date parameter examples'
include_examples 'group permission check on the controller level'
end
end
{
"type": "array",
"items": {
"minItems": 2,
"maxItems": 2,
"type": "object",
"required": ["value", "title"],
"properties": {
"value": {
"type": "integer"
},
"title": {
"type": "string"
}
},
"additionalProperties": false
}
}
...@@ -77,3 +77,68 @@ shared_context 'when invalid stage parameters are given' do ...@@ -77,3 +77,68 @@ shared_context 'when invalid stage parameters are given' do
expect(response).to match_response_schema('analytics/cycle_analytics/validation_error', dir: 'ee') expect(response).to match_response_schema('analytics/cycle_analytics/validation_error', dir: 'ee')
end end
end end
shared_examples 'date parameter examples' do
before do
params[:created_after] = '2019-01-01'
params[:created_before] = '2020-01-01'
end
context 'when valid parameters are given' do
it 'succeeds' do
subject
expect(response).to be_successful
end
end
shared_examples 'example for invalid parameter' do
it 'renders `unprocessable_entity`' do
subject
expect(response).to have_gitlab_http_status(:unprocessable_entity)
expect(response).to match_response_schema('analytics/cycle_analytics/validation_error', dir: 'ee')
end
end
context 'when `created_after` is missing' do
before do
params.delete(:created_after)
end
include_examples 'example for invalid parameter'
end
context 'when `created_after` is invalid' do
before do
params[:created_after] = 'not-a-date'
end
include_examples 'example for invalid parameter'
end
context 'when `created_before` is missing' do
before do
params.delete(:created_before)
end
include_examples 'example for invalid parameter'
end
context 'when `created_after` is invalid' do
before do
params[:created_before] = 'not-a-date'
end
include_examples 'example for invalid parameter'
end
context 'when `created_after` is later than `created_before`' do
before do
params[:created_after] = '2012-01-01'
params[:created_before] = '2010-01-01'
end
include_examples 'example for invalid parameter'
end
end
...@@ -3,18 +3,17 @@ ...@@ -3,18 +3,17 @@
module Gitlab module Gitlab
module CycleAnalytics module CycleAnalytics
class GroupStageSummary class GroupStageSummary
attr_reader :group, :from, :current_user, :options attr_reader :group, :current_user, :options
def initialize(group, options:) def initialize(group, options:)
@group = group @group = group
@from = options[:from]
@current_user = options[:current_user] @current_user = options[:current_user]
@options = options @options = options
end end
def data def data
[serialize(Summary::Group::Issue.new(group: group, from: from, current_user: current_user, options: options)), [serialize(Summary::Group::Issue.new(group: group, current_user: current_user, options: options)),
serialize(Summary::Group::Deploy.new(group: group, from: from, options: options))] serialize(Summary::Group::Deploy.new(group: group, options: options))]
end end
private private
......
...@@ -5,11 +5,10 @@ module Gitlab ...@@ -5,11 +5,10 @@ module Gitlab
module Summary module Summary
module Group module Group
class Base class Base
attr_reader :group, :from, :options attr_reader :group, :options
def initialize(group:, from:, options:) def initialize(group:, options:)
@group = group @group = group
@from = from
@options = options @options = options
end end
......
...@@ -20,7 +20,8 @@ module Gitlab ...@@ -20,7 +20,8 @@ module Gitlab
def find_deployments def find_deployments
deployments = Deployment.joins(:project).merge(Project.inside_path(group.full_path)) deployments = Deployment.joins(:project).merge(Project.inside_path(group.full_path))
deployments = deployments.where(projects: { id: options[:projects] }) if options[:projects] deployments = deployments.where(projects: { id: options[:projects] }) if options[:projects]
deployments = deployments.where("deployments.created_at > ?", from) deployments = deployments.where("deployments.created_at > ?", options[:from])
deployments = deployments.where("deployments.created_at < ?", options[:to]) if options[:to]
deployments.success.count deployments.success.count
end end
end end
......
...@@ -5,11 +5,10 @@ module Gitlab ...@@ -5,11 +5,10 @@ module Gitlab
module Summary module Summary
module Group module Group
class Issue < Group::Base class Issue < Group::Base
attr_reader :group, :from, :current_user, :options attr_reader :group, :current_user, :options
def initialize(group:, from:, current_user:, options:) def initialize(group:, current_user:, options:)
@group = group @group = group
@from = from
@current_user = current_user @current_user = current_user
@options = options @options = options
end end
...@@ -25,10 +24,19 @@ module Gitlab ...@@ -25,10 +24,19 @@ module Gitlab
private private
def find_issues def find_issues
issues = IssuesFinder.new(current_user, group_id: group.id, include_subgroups: true, created_after: from).execute issues = IssuesFinder.new(current_user, finder_params).execute
issues = issues.where(projects: { id: options[:projects] }) if options[:projects] issues = issues.where(projects: { id: options[:projects] }) if options[:projects]
issues.count issues.count
end end
def finder_params
{
group_id: group.id,
include_subgroups: true,
created_after: options[:from],
created_before: options[:to]
}.compact
end
end end
end end
end end
......
...@@ -44,6 +44,14 @@ describe Gitlab::CycleAnalytics::GroupStageSummary do ...@@ -44,6 +44,14 @@ describe Gitlab::CycleAnalytics::GroupStageSummary do
expect(subject.first[:value]).to eq(2) expect(subject.first[:value]).to eq(2)
end end
end end
context 'when `from` and `to` parameters are provided' 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)
end
end
end end
context 'with other projects' do context 'with other projects' do
...@@ -97,6 +105,14 @@ describe Gitlab::CycleAnalytics::GroupStageSummary do ...@@ -97,6 +105,14 @@ describe Gitlab::CycleAnalytics::GroupStageSummary do
expect(subject.second[:value]).to eq(2) expect(subject.second[:value]).to eq(2)
end end
end end
context 'when `from` and `to` parameters are provided' 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)
end
end
end end
context 'with other projects' do context 'with other projects' do
......
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