Commit 64896390 authored by Tiger Watson's avatar Tiger Watson

Merge branch '217973-accept-filter-params-in-vsa-v2' into 'master'

Add finder params in Value Stream Analytics

See merge request gitlab-org/gitlab!33833
parents bb894c71 98f76e9b
...@@ -3,6 +3,9 @@ ...@@ -3,6 +3,9 @@
module Analytics module Analytics
module CycleAnalytics module CycleAnalytics
class StagesController < Analytics::ApplicationController class StagesController < Analytics::ApplicationController
include CycleAnalyticsParams
extend ::Gitlab::Utils::Override
check_feature_flag Gitlab::Analytics::CYCLE_ANALYTICS_FEATURE_FLAG check_feature_flag Gitlab::Analytics::CYCLE_ANALYTICS_FEATURE_FLAG
before_action :load_group before_action :load_group
...@@ -58,26 +61,11 @@ module Analytics ...@@ -58,26 +61,11 @@ module Analytics
private private
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(data_collector_params, current_user: current_user)
end
def data_collector def data_collector
@data_collector ||= Gitlab::Analytics::CycleAnalytics::DataCollector.new(stage: stage, params: { @data_collector ||= Gitlab::Analytics::CycleAnalytics::DataCollector.new(
current_user: current_user, stage: stage,
from: request_params.created_after, params: request_params.to_data_collector_params
to: request_params.created_before, )
project_ids: request_params.project_ids
})
end end
def stage def stage
...@@ -115,8 +103,9 @@ module Analytics ...@@ -115,8 +103,9 @@ module Analytics
end end
end end
def data_collector_params override :all_cycle_analytics_params
params.permit(:created_before, :created_after, project_ids: []) def all_cycle_analytics_params
super.merge({ group: @group })
end end
def update_params def update_params
......
...@@ -22,30 +22,7 @@ module Analytics ...@@ -22,30 +22,7 @@ module Analytics
private private
def group_level def group_level
@group_level ||= GroupLevel.new(group: @group, options: options(group_params)) @group_level ||= GroupLevel.new(group: @group, options: options(request_params.to_data_collector_params))
end
def group_params
hash = { created_after: request_params.created_after, created_before: request_params.created_before }
hash[:project_ids] = request_params.project_ids if request_params.project_ids.any?
hash
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(allowed_params, current_user: current_user)
end
def allowed_params
params.permit(:created_after, :created_before, project_ids: [])
end end
def authorize_access def authorize_access
......
...@@ -2,13 +2,14 @@ ...@@ -2,13 +2,14 @@
class Analytics::CycleAnalyticsController < Analytics::ApplicationController class Analytics::CycleAnalyticsController < Analytics::ApplicationController
include CycleAnalyticsParams include CycleAnalyticsParams
extend ::Gitlab::Utils::Override
check_feature_flag Gitlab::Analytics::CYCLE_ANALYTICS_FEATURE_FLAG check_feature_flag Gitlab::Analytics::CYCLE_ANALYTICS_FEATURE_FLAG
increment_usage_counter Gitlab::UsageDataCounters::CycleAnalyticsCounter, :views, only: :show increment_usage_counter Gitlab::UsageDataCounters::CycleAnalyticsCounter, :views, only: :show
before_action :load_group, only: :show before_action :load_group, only: :show
before_action :load_project, only: :show before_action :load_project, only: :show
before_action :build_request_params, only: :show before_action :request_params, only: :show
before_action do before_action do
push_frontend_feature_flag(:cycle_analytics_scatterplot_enabled, default_enabled: true) push_frontend_feature_flag(:cycle_analytics_scatterplot_enabled, default_enabled: true)
...@@ -16,15 +17,10 @@ class Analytics::CycleAnalyticsController < Analytics::ApplicationController ...@@ -16,15 +17,10 @@ class Analytics::CycleAnalyticsController < Analytics::ApplicationController
push_frontend_feature_flag(:value_stream_analytics_path_navigation, @group) push_frontend_feature_flag(:value_stream_analytics_path_navigation, @group)
end end
def build_request_params private
@request_params ||= Gitlab::Analytics::CycleAnalytics::RequestParams.new(allowed_params.merge(group: @group), current_user: current_user)
end
def allowed_params override :all_cycle_analytics_params
params.permit( def all_cycle_analytics_params
:created_after, super.merge({ group: @group })
:created_before,
project_ids: []
)
end end
end end
...@@ -12,8 +12,32 @@ module EE ...@@ -12,8 +12,32 @@ module EE
options[:branch] = params[:branch_name] options[:branch] = params[:branch_name]
options[:projects] = params[:project_ids] if params[:project_ids] options[:projects] = params[:project_ids] if params[:project_ids]
options[:group] = params[:group_id] if params[:group_id] options[:group] = params[:group_id] if params[:group_id]
options.merge!(params.slice(*::Gitlab::Analytics::CycleAnalytics::RequestParams::FINDER_PARAM_NAMES))
end end
end end
end end
private
def permitted_cycle_analytics_params
params.permit(*::Gitlab::Analytics::CycleAnalytics::RequestParams::STRONG_PARAMS_DEFINITION)
end
def all_cycle_analytics_params
permitted_cycle_analytics_params.merge(current_user: current_user)
end
def request_params
@request_params ||= ::Gitlab::Analytics::CycleAnalytics::RequestParams.new(all_cycle_analytics_params)
end
def validate_params
if request_params.invalid?
render(
json: { message: 'Invalid parameters', errors: request_params.errors },
status: :unprocessable_entity
)
end
end
end end
end end
...@@ -13,6 +13,8 @@ module EE::Gitlab::Analytics::CycleAnalytics::BaseQueryBuilder ...@@ -13,6 +13,8 @@ module EE::Gitlab::Analytics::CycleAnalytics::BaseQueryBuilder
override :build_finder_params override :build_finder_params
def build_finder_params(params) def build_finder_params(params)
super.tap do |finder_params| super.tap do |finder_params|
finder_params.merge!(params.slice(*::Gitlab::Analytics::CycleAnalytics::RequestParams::FINDER_PARAM_NAMES))
finder_params[:project_ids] = Array(params[:project_ids]) finder_params[:project_ids] = Array(params[:project_ids])
end end
end end
......
...@@ -11,14 +11,33 @@ module Gitlab ...@@ -11,14 +11,33 @@ module Gitlab
MAX_RANGE_DAYS = 180.days.freeze MAX_RANGE_DAYS = 180.days.freeze
DEFAULT_DATE_RANGE = 29.days # 30 including Date.today DEFAULT_DATE_RANGE = 29.days # 30 including Date.today
STRONG_PARAMS_DEFINITION = [
:created_before,
:created_after,
:author_username,
:milestone_title,
label_name: [].freeze,
assignee_username: [].freeze,
project_ids: [].freeze
].freeze
FINDER_PARAM_NAMES = [
:assignee_username,
:author_username,
:milestone_title,
:label_name
].freeze
attr_writer :project_ids attr_writer :project_ids
attribute :created_after, :datetime attribute :created_after, :datetime
attribute :created_before, :datetime attribute :created_before, :datetime
attribute :group
attribute :current_user
attr_accessor :group FINDER_PARAM_NAMES.each do |param_name|
attribute param_name
attr_reader :current_user end
validates :created_after, presence: true validates :created_after, presence: true
validates :created_before, presence: true validates :created_before, presence: true
...@@ -26,25 +45,36 @@ module Gitlab ...@@ -26,25 +45,36 @@ module Gitlab
validate :validate_created_before validate :validate_created_before
validate :validate_date_range validate :validate_date_range
def initialize(params = {}, current_user:) def initialize(params = {})
super(params) super(params)
self.created_before = (self.created_before || Time.now).at_end_of_day self.created_before = (self.created_before || Time.now).at_end_of_day
self.created_after = (created_after || default_created_after).at_beginning_of_day self.created_after = (created_after || default_created_after).at_beginning_of_day
@current_user = current_user
end end
def project_ids def project_ids
Array(@project_ids) Array(@project_ids)
end end
def to_data_collector_params
{
current_user: current_user,
from: created_after,
to: created_before,
project_ids: project_ids
}.merge(attributes.symbolize_keys.slice(*FINDER_PARAM_NAMES))
end
def to_data_attributes def to_data_attributes
{}.tap do |attrs| {}.tap do |attrs|
attrs[:group] = group_data_attributes if group attrs[:group] = group_data_attributes if group
attrs[:created_after] = created_after.to_date.iso8601 attrs[:created_after] = created_after.to_date.iso8601
attrs[:created_before] = created_before.to_date.iso8601 attrs[:created_before] = created_before.to_date.iso8601
attrs[:projects] = group_projects(project_ids) if group && project_ids.any? attrs[:projects] = group_projects(project_ids) if group && project_ids.present?
attrs[:labels] = label_name.to_json if label_name.present?
attrs[:assignees] = assignee_username.to_json if assignee_username.present?
attrs[:author] = author_username if author_username.present?
attrs[:milestone] = milestone_title if milestone_title.present?
end end
end end
...@@ -67,10 +97,10 @@ module Gitlab ...@@ -67,10 +97,10 @@ module Gitlab
options: { include_subgroups: true }, options: { include_subgroups: true },
project_ids_relation: project_ids project_ids_relation: project_ids
) )
.execute .execute
.with_route .with_route
.map { |project| project_data_attributes(project) } .map { |project| project_data_attributes(project) }
.to_json .to_json
end end
def project_data_attributes(project) def project_data_attributes(project)
......
...@@ -22,7 +22,7 @@ module Gitlab ...@@ -22,7 +22,7 @@ module Gitlab
def deployments_count def deployments_count
@deployments_count ||= begin @deployments_count ||= begin
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].present?
deployments = deployments.where("deployments.created_at > ?", options[:from]) deployments = deployments.where("deployments.created_at > ?", options[:from])
deployments = deployments.where("deployments.created_at < ?", options[:to]) if options[:to] deployments = deployments.where("deployments.created_at < ?", options[:to]) if options[:to]
deployments.success.count deployments.success.count
......
...@@ -27,18 +27,19 @@ module Gitlab ...@@ -27,18 +27,19 @@ module Gitlab
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def issues_count def issues_count
issues = IssuesFinder.new(current_user, finder_params).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].present?
issues.count issues.count
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
def finder_params def finder_params
{ options.dup.tap do |hash|
group_id: group.id, hash.delete(:projects)
include_subgroups: true, hash[:created_after] = hash.delete(:from)
created_after: options[:from], hash[:created_before] = hash.delete(:to)
created_before: options[:to] hash[:group_id] = group.id
}.compact hash[:include_subgroups] = true
end
end end
end end
end end
......
...@@ -23,24 +23,6 @@ RSpec.describe Analytics::CycleAnalytics::SummaryController do ...@@ -23,24 +23,6 @@ RSpec.describe Analytics::CycleAnalytics::SummaryController do
expect(response).to match_response_schema('analytics/cycle_analytics/summary', dir: 'ee') expect(response).to match_response_schema('analytics/cycle_analytics/summary', dir: 'ee')
end end
it 'omits `projects` parameter if it is not given' do
expect(Analytics::CycleAnalytics::GroupLevel).to receive(:new).with(group: group, options: hash_excluding(:projects)).and_call_original
subject
expect(response).to be_successful
end
it 'contains `projects` parameter' do
params[:project_ids] = [-1]
expect(Analytics::CycleAnalytics::GroupLevel).to receive(:new).with(group: group, options: hash_including(projects: ['-1'])).and_call_original
subject
expect(response).to be_successful
end
include_examples 'cycle analytics data endpoint examples' include_examples 'cycle analytics data endpoint examples'
include_examples 'group permission check on the controller level' include_examples 'group permission check on the controller level'
end end
......
...@@ -393,10 +393,12 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::DataCollector do ...@@ -393,10 +393,12 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::DataCollector do
end end
end end
context 'when `project_ids` parameter is given' do context 'when filter parameters are given' do
let(:group) { create(:group) } let_it_be(:group) { create(:group) }
let(:project1) { create(:project, :repository, group: group) } let_it_be(:project1) { create(:project, :repository, group: group) }
let(:project2) { create(:project, :repository, group: group) } let_it_be(:project2) { create(:project, :repository, group: group) }
let(:merge_request) { project2.merge_requests.first }
let(:stage) do let(:stage) do
Analytics::CycleAnalytics::GroupStage.new( Analytics::CycleAnalytics::GroupStage.new(
...@@ -407,12 +409,17 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::DataCollector do ...@@ -407,12 +409,17 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::DataCollector do
) )
end end
let(:data_collector) do let(:data_collector_params) do
described_class.new(stage: stage, params: { {
from: Time.new(2019, 1, 1), created_after: Time.new(2019, 1, 1),
project_ids: [project2.id],
current_user: user current_user: user
}) }
end
subject do
params = Gitlab::Analytics::CycleAnalytics::RequestParams.new(data_collector_params).to_data_collector_params
described_class.new(stage: stage, params: params).records_fetcher.serialized_records
end end
before do before do
...@@ -427,13 +434,73 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::DataCollector do ...@@ -427,13 +434,73 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::DataCollector do
end end
end end
it 'filters for the given `project_ids`' do shared_examples 'filter examples' do
items = data_collector.records_fetcher.serialized_records it 'provides filtered results' do
expect(items.size).to eq(1) expect(subject.size).to eq(1)
expect(subject.first[:title]).to eq(merge_request.title)
expect(subject.first[:iid]).to eq(merge_request.iid.to_s)
end
end
context 'when `project_ids` parameter is given' do
before do
data_collector_params[:project_ids] = [project2.id]
end
it_behaves_like 'filter examples'
end
context 'when `assignee_username` is given' do
let(:assignee) { create(:user) }
before do
merge_request.assignees << assignee
data_collector_params[:assignee_username] = [assignee.username]
end
it_behaves_like 'filter examples'
end
context 'when `author_username` is given' do
let(:author) { create(:user) }
before do
merge_request.update!(author: author)
data_collector_params[:author_username] = author.username
end
it_behaves_like 'filter examples'
end
context 'when `label_name` is given' do
let(:label) { create(:group_label, group: group) }
before do
MergeRequests::UpdateService.new(
merge_request.project,
user,
label_ids: [label.id]
).execute(merge_request)
data_collector_params[:label_name] = [label.name]
end
it_behaves_like 'filter examples'
end
context 'when `milestone_title` is given' do
let(:milestone) { create(:milestone, group: group) }
before do
merge_request.update!(milestone: milestone)
data_collector_params[:milestone_title] = milestone.title
end
merge_request = project2.merge_requests.first it_behaves_like 'filter examples'
expect(items.first[:title]).to eq(merge_request.title)
expect(items.first[:iid]).to eq(merge_request.iid.to_s)
end end
end end
end end
......
...@@ -16,13 +16,16 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::RequestParams do ...@@ -16,13 +16,16 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::RequestParams do
let(:project_ids) { root_group_projects.collect(&:id) } let(:project_ids) { root_group_projects.collect(&:id) }
let(:params) do let(:params) do
{ created_after: '2019-01-01', {
created_after: '2019-01-01',
created_before: '2019-03-01', created_before: '2019-03-01',
project_ids: [2, 3], project_ids: [2, 3],
group: root_group } group: root_group,
current_user: user
}
end end
subject { described_class.new(params, current_user: user) } subject { described_class.new(params) }
before do before do
root_group.add_owner(user) root_group.add_owner(user)
...@@ -164,4 +167,22 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::RequestParams do ...@@ -164,4 +167,22 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::RequestParams do
it { expect(subject.group).to eq(sub_group.id) } it { expect(subject.group).to eq(sub_group.id) }
end end
end end
describe 'issuable filter params' do
before do
params.merge!(
milestone_title: 'title',
assignee_username: ['username1'],
label_name: %w[label1 label2],
author_username: 'author'
)
end
subject { described_class.new(params).to_data_attributes }
it { expect(subject[:milestone]).to eq('title') }
it { expect(subject[:assignees]).to eq('["username1"]') }
it { expect(subject[:labels]).to eq('["label1","label2"]') }
it { expect(subject[:author]).to eq('author') }
end
end end
...@@ -51,6 +51,35 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::Summary::Group::StageSummary d ...@@ -51,6 +51,35 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::Summary::Group::StageSummary d
end end
end end
context 'with `assignee_username` filter' do
let(:assignee) { create(:user) }
before do
issue = project.issues.last
issue.assignees << assignee
end
subject { described_class.new(group, options: { from: Time.now, current_user: user, assignee_username: [assignee.username] }).data }
it 'finds issues from those projects' do
expect(subject.first[:value]).to eq('1')
end
end
context 'with `author_username` filter' do
let(:author) { create(:user) }
before do
project.issues.last.update!(author: author)
end
subject { described_class.new(group, options: { from: Time.now, current_user: user, author_username: [author.username] }).data }
it 'finds issues from those projects' do
expect(subject.first[:value]).to eq('1')
end
end
context 'when `from` and `to` parameters are provided' do 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 } subject { described_class.new(group, options: { from: 10.days.ago, to: Time.now, current_user: user }).data }
......
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