Commit 614e6aaf authored by Markus Koller's avatar Markus Koller

Merge branch 'remove-instance-level-vsa' into 'master'

Remove instance level Value Stream Analytics code

See merge request gitlab-org/gitlab!42005
parents d7c8a30c 437842a7
......@@ -135,7 +135,6 @@ Rails.application.routes.draw do
draw :country
draw :country_state
draw :subscription
draw :analytics
scope '/push_from_secondary/:geo_node_id' do
draw :git_http
......
# frozen_string_literal: true
module Analytics
class ApplicationController < ::ApplicationController
include RoutableActions
include GracefulTimeoutHandling
layout 'analytics'
private
def self.check_feature_flag(flag, *args)
before_action(*args) { render_404 unless Feature.enabled?(flag, default_enabled: Gitlab::Analytics.feature_enabled_by_default?(flag)) }
end
def self.increment_usage_counter(counter_klass, counter, *args)
before_action(*args) { counter_klass.count(counter) }
end
def authorize_view_by_action!(action)
return render_403 unless can?(current_user, action, @group || :global)
end
def check_feature_availability!(feature)
return render_403 unless ::License.feature_available?(feature)
return render_403 unless @group && @group.root_ancestor.feature_available?(feature)
end
def load_group
return unless params['group_id']
@group = find_routable!(Group, params['group_id'])
end
def load_project
return unless @group && params['project_id']
@project = find_routable!(@group.projects, params['project_id'])
end
private_class_method :check_feature_flag, :increment_usage_counter
end
end
# frozen_string_literal: true
module Analytics
module CycleAnalytics
class StagesController < Analytics::ApplicationController
include CycleAnalyticsParams
extend ::Gitlab::Utils::Override
check_feature_flag Gitlab::Analytics::CYCLE_ANALYTICS_FEATURE_FLAG
before_action :load_group
before_action :load_value_stream
before_action :validate_params, only: %i[median records duration_chart]
def index
return render_403 unless can?(current_user, :read_group_cycle_analytics, @group)
result = list_service.execute
if result.success?
render json: cycle_analytics_configuration(result.payload[:stages])
else
render json: { message: result.message }, status: result.http_status
end
end
def create
return render_403 unless can?(current_user, :create_group_stage, @group)
render_stage_service_result(create_service.execute)
end
def update
return render_403 unless can?(current_user, :update_group_stage, @group)
render_stage_service_result(update_service.execute)
end
def destroy
return render_403 unless can?(current_user, :delete_group_stage, @group)
render_stage_service_result(delete_service.execute)
end
def median
return render_403 unless can?(current_user, :read_group_stage, @group)
render json: { value: data_collector.median.seconds }
end
def records
return render_403 unless can?(current_user, :read_group_stage, @group)
render json: data_collector.serialized_records
end
def duration_chart
return render_403 unless can?(current_user, :read_group_stage, @group)
render json: ::Analytics::CycleAnalytics::DurationChartItemEntity.represent(data_collector.duration_chart_data)
end
private
def data_collector
@data_collector ||= Gitlab::Analytics::CycleAnalytics::DataCollector.new(
stage: stage,
params: request_params.to_data_collector_params
)
end
def stage
@stage ||= ::Analytics::CycleAnalytics::StageFinder.new(parent: @group, stage_id: params[:id]).execute
end
def cycle_analytics_configuration(stages)
stage_presenters = stages.map { |s| StagePresenter.new(s) }
::Analytics::CycleAnalytics::ConfigurationEntity.new(stages: stage_presenters)
end
def list_service
::Analytics::CycleAnalytics::Stages::ListService.new(parent: @group, current_user: current_user, params: list_params)
end
def create_service
::Analytics::CycleAnalytics::Stages::CreateService.new(parent: @group, current_user: current_user, params: create_params)
end
def update_service
::Analytics::CycleAnalytics::Stages::UpdateService.new(parent: @group, current_user: current_user, params: update_params)
end
def delete_service
::Analytics::CycleAnalytics::Stages::DeleteService.new(parent: @group, current_user: current_user, params: delete_params)
end
def render_stage_service_result(result)
if result.success?
stage = StagePresenter.new(result.payload[:stage])
render json: ::Analytics::CycleAnalytics::StageEntity.new(stage), status: result.http_status
else
render json: { message: result.message, errors: result.payload[:errors] }, status: result.http_status
end
end
override :all_cycle_analytics_params
def all_cycle_analytics_params
super.merge({ group: @group })
end
def list_params
{ value_stream: @value_stream }
end
def update_params
params.permit(:name, :start_event_identifier, :end_event_identifier, :id, :move_after_id, :move_before_id, :hidden, :start_event_label_id, :end_event_label_id).merge(list_params)
end
def create_params
params.permit(:name, :start_event_identifier, :end_event_identifier, :start_event_label_id, :end_event_label_id).merge(list_params)
end
def delete_params
params.permit(:id)
end
def load_value_stream
if params[:value_stream_id] && params[:value_stream_id] != Analytics::CycleAnalytics::Stages::BaseService::DEFAULT_VALUE_STREAM_NAME
@value_stream = @group.value_streams.find(params[:value_stream_id])
end
end
end
end
end
# 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 :authorize_access
before_action :validate_params
def show
render json: group_level.summary
end
def time_summary
render json: group_level.time_summary
end
private
def group_level
@group_level ||= GroupLevel.new(group: @group, options: options(request_params.to_data_collector_params))
end
def authorize_access
return render_403 unless can?(current_user, :read_group_cycle_analytics, @group)
end
end
end
end
# frozen_string_literal: true
class Analytics::CycleAnalyticsController < Analytics::ApplicationController
include CycleAnalyticsParams
extend ::Gitlab::Utils::Override
check_feature_flag Gitlab::Analytics::CYCLE_ANALYTICS_FEATURE_FLAG
increment_usage_counter Gitlab::UsageDataCounters::CycleAnalyticsCounter, :views, only: :show
before_action :load_group, only: :show
before_action :load_project, only: :show
before_action :request_params, only: :show
before_action do
push_frontend_feature_flag(:cycle_analytics_scatterplot_enabled, default_enabled: true)
push_frontend_feature_flag(:value_stream_analytics_path_navigation, @group)
push_frontend_feature_flag(:value_stream_analytics_create_multiple_value_streams, default_enabled: true)
push_frontend_feature_flag(:analytics_similarity_search, @group, default_enabled: true)
end
private
override :all_cycle_analytics_params
def all_cycle_analytics_params
super.merge({ group: @group })
end
end
# frozen_string_literal: true
module Analytics
class ProductivityAnalyticsController < ::Analytics::ApplicationController
check_feature_flag Gitlab::Analytics::PRODUCTIVITY_ANALYTICS_FEATURE_FLAG
increment_usage_counter Gitlab::UsageDataCounters::ProductivityAnalyticsCounter,
:views, only: :show, if: -> { request.format.html? }
before_action :load_group
before_action :load_project
before_action :build_request_params
before_action -> {
check_feature_availability!(:productivity_analytics)
}, if: -> { request.format.json? }
before_action -> {
authorize_view_by_action!(:view_productivity_analytics)
}
before_action -> {
push_frontend_feature_flag(:productivity_analytics_scatterplot_enabled, default_enabled: true)
push_frontend_feature_flag(:analytics_similarity_search, @group, default_enabled: true)
}
before_action :validate_params, only: :show, if: -> { request.format.json? }
include IssuableCollections
def show
respond_to do |format|
format.html
format.json do
metric = params.fetch('metric_type', ProductivityAnalytics::DEFAULT_TYPE)
data = case params['chart_type']
when 'scatterplot'
productivity_analytics.scatterplot_data(type: metric)
when 'histogram'
productivity_analytics.histogram_data(type: metric)
else
include_relations(paginate(productivity_analytics.merge_requests_extended)).map do |merge_request|
serializer.represent(merge_request, {}, ProductivityAnalyticsMergeRequestEntity)
end
end
render json: data, status: :ok
end
end
end
private
def paginate(merge_requests)
merge_requests.page(params[:page]).per(params[:per_page]).tap do |paginated_data|
response.set_header('X-Per-Page', paginated_data.limit_value.to_s)
response.set_header('X-Page', paginated_data.current_page.to_s)
response.set_header('X-Next-Page', paginated_data.next_page.to_s)
response.set_header('X-Prev-Page', paginated_data.prev_page.to_s)
response.set_header('X-Total', paginated_data.total_count.to_s)
response.set_header('X-Total-Pages', paginated_data.total_pages.to_s)
end
end
def serializer
@serializer ||= BaseSerializer.new(current_user: current_user)
end
def finder_type
ProductivityAnalyticsFinder
end
def default_state
'merged'
end
def validate_params
if @request_params.invalid?
render(
json: { message: 'Invalid parameters', errors: @request_params.errors },
status: :unprocessable_entity
)
end
end
def build_request_params
@request_params ||= ::Analytics::ProductivityAnalyticsRequestParams.new(allowed_request_params.merge(group: @group, project: @project))
end
def allowed_request_params
params.permit(
:merged_after,
:merged_before,
:author_username,
:milestone_title,
label_name: []
)
end
def productivity_analytics
@productivity_analytics ||= ProductivityAnalytics.new(merge_requests: finder.execute, sort: params[:sort])
end
# rubocop: disable CodeReuse/ActiveRecord
def include_relations(paginated_mrs)
# Due to Rails bug: https://github.com/rails/rails/issues/34889 we can't use .includes statement
# to avoid N+1 call when we load custom columns.
# So we load relations manually here.
preloader = ActiveRecord::Associations::Preloader.new
preloader.preload(paginated_mrs, { author: [], target_project: { namespace: :route } })
paginated_mrs
end
# rubocop: enable CodeReuse/ActiveRecord
end
end
# frozen_string_literal: true
class Analytics::TasksByTypeController < Analytics::ApplicationController
before_action :load_group
before_action -> { check_feature_availability!(:type_of_work_analytics) }
before_action -> { authorize_view_by_action!(:view_type_of_work_charts) }
before_action :validate_label_ids, only: :show
before_action :prepare_date_range
def show
render json: Analytics::TasksByTypeLabelEntity.represent(counts_by_labels)
end
def top_labels
render json: LabelEntity.represent(tasks_by_type.top_labels)
end
private
def counts_by_labels
tasks_by_type.counts_by_labels
end
def tasks_by_type
Gitlab::Analytics::TypeOfWork::TasksByType.new(group: @group, current_user: current_user, params: {
subject: params[:subject],
label_ids: Array(params[:label_ids]),
project_ids: Array(params[:project_ids]),
created_after: @created_after.to_time.utc.beginning_of_day,
created_before: @created_before.to_time.utc.end_of_day
})
end
def validate_label_ids
return respond_422 if Array(params[:label_ids]).empty?
end
def prepare_date_range
@created_after = parse_date(params[:created_after])
return respond_422 unless @created_after
@created_before = parse_date(params[:created_before]) || Date.today
return respond_422 if @created_after > @created_before
end
def parse_date(value)
return unless value
Date.parse(value)
rescue ArgumentError
end
end
......@@ -2,6 +2,7 @@
class Groups::Analytics::ApplicationController < ApplicationController
include RoutableActions
include GracefulTimeoutHandling
private
......
......@@ -3,7 +3,134 @@
module Groups
module Analytics
module CycleAnalytics
class StagesController < ::Analytics::CycleAnalytics::StagesController
class StagesController < Groups::Analytics::ApplicationController
include CycleAnalyticsParams
extend ::Gitlab::Utils::Override
check_feature_flag Gitlab::Analytics::CYCLE_ANALYTICS_FEATURE_FLAG
before_action :load_group
before_action :load_value_stream
before_action :validate_params, only: %i[median records duration_chart]
def index
return render_403 unless can?(current_user, :read_group_cycle_analytics, @group)
result = list_service.execute
if result.success?
render json: cycle_analytics_configuration(result.payload[:stages])
else
render json: { message: result.message }, status: result.http_status
end
end
def create
return render_403 unless can?(current_user, :create_group_stage, @group)
render_stage_service_result(create_service.execute)
end
def update
return render_403 unless can?(current_user, :update_group_stage, @group)
render_stage_service_result(update_service.execute)
end
def destroy
return render_403 unless can?(current_user, :delete_group_stage, @group)
render_stage_service_result(delete_service.execute)
end
def median
return render_403 unless can?(current_user, :read_group_stage, @group)
render json: { value: data_collector.median.seconds }
end
def records
return render_403 unless can?(current_user, :read_group_stage, @group)
render json: data_collector.serialized_records
end
def duration_chart
return render_403 unless can?(current_user, :read_group_stage, @group)
render json: ::Analytics::CycleAnalytics::DurationChartItemEntity.represent(data_collector.duration_chart_data)
end
private
def data_collector
@data_collector ||= Gitlab::Analytics::CycleAnalytics::DataCollector.new(
stage: stage,
params: request_params.to_data_collector_params
)
end
def stage
@stage ||= ::Analytics::CycleAnalytics::StageFinder.new(parent: @group, stage_id: params[:id]).execute
end
def cycle_analytics_configuration(stages)
stage_presenters = stages.map { |s| ::Analytics::CycleAnalytics::StagePresenter.new(s) }
::Analytics::CycleAnalytics::ConfigurationEntity.new(stages: stage_presenters)
end
def list_service
::Analytics::CycleAnalytics::Stages::ListService.new(parent: @group, current_user: current_user, params: list_params)
end
def create_service
::Analytics::CycleAnalytics::Stages::CreateService.new(parent: @group, current_user: current_user, params: create_params)
end
def update_service
::Analytics::CycleAnalytics::Stages::UpdateService.new(parent: @group, current_user: current_user, params: update_params)
end
def delete_service
::Analytics::CycleAnalytics::Stages::DeleteService.new(parent: @group, current_user: current_user, params: delete_params)
end
def render_stage_service_result(result)
if result.success?
stage = ::Analytics::CycleAnalytics::StagePresenter.new(result.payload[:stage])
render json: ::Analytics::CycleAnalytics::StageEntity.new(stage), status: result.http_status
else
render json: { message: result.message, errors: result.payload[:errors] }, status: result.http_status
end
end
override :all_cycle_analytics_params
def all_cycle_analytics_params
super.merge({ group: @group })
end
def list_params
{ value_stream: @value_stream }
end
def update_params
params.permit(:name, :start_event_identifier, :end_event_identifier, :id, :move_after_id, :move_before_id, :hidden, :start_event_label_id, :end_event_label_id).merge(list_params)
end
def create_params
params.permit(:name, :start_event_identifier, :end_event_identifier, :start_event_label_id, :end_event_label_id).merge(list_params)
end
def delete_params
params.permit(:id)
end
def load_value_stream
if params[:value_stream_id] && params[:value_stream_id] != ::Analytics::CycleAnalytics::Stages::BaseService::DEFAULT_VALUE_STREAM_NAME
@value_stream = @group.value_streams.find(params[:value_stream_id])
end
end
end
end
end
......
......@@ -3,7 +3,32 @@
module Groups
module Analytics
module CycleAnalytics
class SummaryController < ::Analytics::CycleAnalytics::SummaryController
class SummaryController < Groups::Analytics::ApplicationController
include CycleAnalyticsParams
check_feature_flag Gitlab::Analytics::CYCLE_ANALYTICS_FEATURE_FLAG
before_action :load_group
before_action :authorize_access
before_action :validate_params
def show
render json: group_level.summary
end
def time_summary
render json: group_level.time_summary
end
private
def group_level
@group_level ||= ::Analytics::CycleAnalytics::GroupLevel.new(group: @group, options: options(request_params.to_data_collector_params))
end
def authorize_access
return render_403 unless can?(current_user, :read_group_cycle_analytics, @group)
end
end
end
end
......
# frozen_string_literal: true
class Groups::Analytics::CycleAnalytics::ValueStreamsController < Analytics::ApplicationController
class Groups::Analytics::CycleAnalytics::ValueStreamsController < Groups::Analytics::ApplicationController
respond_to :json
check_feature_flag Gitlab::Analytics::CYCLE_ANALYTICS_FEATURE_FLAG
......
# frozen_string_literal: true
class Groups::Analytics::CycleAnalyticsController < Analytics::CycleAnalyticsController
class Groups::Analytics::CycleAnalyticsController < Groups::Analytics::ApplicationController
include Analytics::UniqueVisitsHelper
include CycleAnalyticsParams
extend ::Gitlab::Utils::Override
layout 'group'
check_feature_flag Gitlab::Analytics::CYCLE_ANALYTICS_FEATURE_FLAG
increment_usage_counter Gitlab::UsageDataCounters::CycleAnalyticsCounter, :views, only: :show
before_action :load_group, only: :show
before_action :load_project, only: :show
before_action :request_params, only: :show
before_action do
push_frontend_feature_flag(:cycle_analytics_scatterplot_enabled, default_enabled: true)
push_frontend_feature_flag(:value_stream_analytics_path_navigation, @group)
push_frontend_feature_flag(:value_stream_analytics_create_multiple_value_streams, default_enabled: true)
push_frontend_feature_flag(:analytics_similarity_search, @group, default_enabled: true)
render_403 unless can?(current_user, :read_group_cycle_analytics, @group)
end
layout 'group'
track_unique_visits :show, target_id: 'g_analytics_valuestream'
private
override :all_cycle_analytics_params
def all_cycle_analytics_params
super.merge({ group: @group })
end
end
# frozen_string_literal: true
class Groups::Analytics::TasksByTypeController < ::Analytics::TasksByTypeController
class Groups::Analytics::TasksByTypeController < Groups::Analytics::ApplicationController
before_action :load_group
before_action -> { check_feature_availability!(:type_of_work_analytics) }
before_action -> { authorize_view_by_action!(:view_type_of_work_charts) }
before_action :validate_label_ids, only: :show
before_action :prepare_date_range
def show
render json: Analytics::TasksByTypeLabelEntity.represent(counts_by_labels)
end
def top_labels
render json: LabelEntity.represent(tasks_by_type.top_labels)
end
private
def counts_by_labels
tasks_by_type.counts_by_labels
end
def tasks_by_type
Gitlab::Analytics::TypeOfWork::TasksByType.new(group: @group, current_user: current_user, params: {
subject: params[:subject],
label_ids: Array(params[:label_ids]),
project_ids: Array(params[:project_ids]),
created_after: @created_after.to_time.utc.beginning_of_day,
created_before: @created_before.to_time.utc.end_of_day
})
end
def validate_label_ids
return respond_422 if Array(params[:label_ids]).empty?
end
def prepare_date_range
@created_after = parse_date(params[:created_after])
return respond_422 unless @created_after
@created_before = parse_date(params[:created_before]) || Date.today
return respond_422 if @created_after > @created_before
end
def parse_date(value)
return unless value
Date.parse(value)
rescue ArgumentError
end
end
- page_title _("Value Stream Analytics")
- initial_data = @request_params.valid? ? @request_params.to_data_attributes : {}
#js-cycle-analytics-app{ data: initial_data.merge({ empty_state_svg_path: image_path("illustrations/analytics/cycle-analytics-empty-chart.svg"),
no_data_svg_path: image_path("illustrations/analytics/cycle-analytics-empty-chart.svg"),
no_access_svg_path: image_path("illustrations/analytics/no-access.svg") }) }
- page_title _('Analytics')
- header_title _('Analytics'), analytics_root_path
- nav 'analytics'
- @left_sidebar = true
= render template: 'layouts/application'
# frozen_string_literal: true
namespace :analytics do
root to: redirect('admin/dev_ops_report')
constraints(-> (req) { Gitlab::Analytics.cycle_analytics_enabled? }) do
scope module: :cycle_analytics, as: 'cycle_analytics', path: 'value_stream_analytics' do
resources :stages, only: [:index, :create, :update, :destroy] do
member do
get :duration_chart
get :median
get :records
end
end
resource :summary, controller: :summary, only: :show
get '/time_summary' => 'summary#time_summary'
end
get '/cycle_analytics', to: redirect('-/analytics/value_stream_analytics')
end
scope :type_of_work do
resource :tasks_by_type, controller: :tasks_by_type, only: :show do
get :top_labels
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Analytics::CycleAnalytics::StagesController do
let_it_be(:user) { create(:user) }
let_it_be(:group, refind: true) { create(:group) }
let(:params) { { group_id: group.full_path } }
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 `index`' do
subject { get :index, params: params }
it 'succeeds' do
subject
expect(response).to be_successful
expect(response).to match_response_schema('analytics/cycle_analytics/stages', dir: 'ee')
end
it 'returns correct start events' do
subject
response_start_events = json_response['stages'].map { |s| s['start_event_identifier'] }
start_events = Gitlab::Analytics::CycleAnalytics::DefaultStages.all.map { |s| s['start_event_identifier'] }
expect(response_start_events).to eq(start_events)
end
it 'does not include internal events' do
subject
response_event_names = json_response['events'].map { |s| s['name'] }
event_names = Gitlab::Analytics::CycleAnalytics::StageEvents.events
internal_events = Gitlab::Analytics::CycleAnalytics::StageEvents.internal_events
expected_event_names = (event_names - internal_events).map(&:name)
expect(response_event_names).to eq(expected_event_names.sort)
end
it 'succeeds for subgroups' do
subgroup = create(:group, parent: group)
params[:group_id] = subgroup.full_path
subject
expect(response).to be_successful
end
context 'when `default` value_stream_id is given' do
before do
params[:value_stream_id] = Analytics::CycleAnalytics::Stages::BaseService::DEFAULT_VALUE_STREAM_NAME
end
it 'returns only the default value stream stages' do
subject
expect(response).to be_successful
expect(json_response['stages'].size).to eq(Gitlab::Analytics::CycleAnalytics::DefaultStages.all.size)
end
end
it 'renders `forbidden` based on the response of the service object' do
expect_any_instance_of(Analytics::CycleAnalytics::Stages::ListService).to receive(:can?).and_return(false)
subject
expect(response).to have_gitlab_http_status(:forbidden)
end
include_examples 'group permission check on the controller level'
end
describe 'POST `create`' do
subject { post :create, params: params }
include_examples 'group permission check on the controller level'
context 'when valid parameters are given' do
before do
params.merge!({
name: 'my new stage',
start_event_identifier: :merge_request_created,
end_event_identifier: :merge_request_merged
})
end
it 'creates the stage' do
subject
expect(response).to be_successful
expect(response).to match_response_schema('analytics/cycle_analytics/stage', dir: 'ee')
end
end
include_context 'when invalid stage parameters are given'
end
describe 'PUT `update`' do
let(:stage) { create(:cycle_analytics_group_stage, parent: group, relative_position: 15) }
subject { put :update, params: params.merge(id: stage.id) }
include_examples 'group permission check on the controller level'
context 'when valid parameters are given' do
before do
params.merge!({
name: 'my updated stage',
start_event_identifier: :merge_request_created,
end_event_identifier: :merge_request_merged
})
end
it 'succeeds' do
subject
expect(response).to be_successful
expect(response).to match_response_schema('analytics/cycle_analytics/stage', dir: 'ee')
end
it 'updates the name attribute' do
subject
stage.reload
expect(stage.name).to eq(params[:name])
end
context 'hidden attribute' do
before do
params[:hidden] = true
end
it 'updates the hidden attribute' do
subject
stage.reload
expect(stage.hidden).to eq(true)
end
end
context 'when positioning parameter is given' do
before do
params[:move_before_id] = create(:cycle_analytics_group_stage, parent: group, relative_position: 10).id
end
it 'moves the stage before the last place' do
subject
before_last = group.cycle_analytics_stages.ordered[-2]
expect(before_last.id).to eq(stage.id)
end
end
end
include_context 'when invalid stage parameters are given'
end
describe 'DELETE `destroy`' do
let(:stage) { create(:cycle_analytics_group_stage, parent: group) }
subject { delete :destroy, params: params }
before do
params[:id] = stage.id
end
include_examples 'group permission check on the controller level'
context 'when persisted stage id is passed' do
it 'succeeds' do
subject
expect(response).to be_successful
end
it 'deletes the record' do
subject
expect(group.reload.cycle_analytics_stages.find_by(id: stage.id)).to be_nil
end
end
context 'when default stage id is passed' do
before do
params[:id] = Gitlab::Analytics::CycleAnalytics::DefaultStages.names.first
end
it 'fails with `forbidden` response' do
subject
expect(response).to have_gitlab_http_status(:forbidden)
end
end
end
describe 'data endpoints' do
let(:stage) { create(:cycle_analytics_group_stage, parent: group) }
before do
params[:id] = stage.id
end
describe 'GET `median`' do
subject { get :median, params: params }
it 'matches the response schema' do
subject
expect(response).to match_response_schema('analytics/cycle_analytics/median', dir: 'ee')
end
include_examples 'cycle analytics data endpoint examples'
end
describe 'GET `records`' do
subject { get :records, params: params }
include_examples 'cycle analytics data endpoint examples'
include_examples 'group permission check on the controller level'
end
describe 'GET `duration_chart`' do
subject { get :duration_chart, params: params }
it 'matches the response schema' do
fake_result = [double(MergeRequest, duration_in_seconds: 10, finished_at: Time.current)]
expect_any_instance_of(Gitlab::Analytics::CycleAnalytics::DataForDurationChart).to receive(:load).and_return(fake_result)
subject
expect(response).to match_response_schema('analytics/cycle_analytics/duration_chart', dir: 'ee')
end
include_examples 'cycle analytics data endpoint examples'
include_examples 'group permission check on the controller level'
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Analytics::TasksByTypeController do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:label) { create(:group_label, group: group) }
let(:params) { { group_id: group.full_path, label_ids: [label.id], created_after: 10.days.ago, subject: 'Issue' } }
let!(:issue) { create(:labeled_issue, created_at: 5.days.ago, project: create(:project, group: group), labels: [label]) }
subject { get :show, params: params }
before do
stub_licensed_features(type_of_work_analytics: true)
group.add_reporter(user)
sign_in(user)
end
shared_examples 'expects unprocessable_entity response' do
it 'returns unprocessable_entity as response' do
subject
expect(response).to have_gitlab_http_status(:unprocessable_entity)
end
end
shared_examples 'parameter validation' do
context 'when user access level is lower than reporter' do
before do
group.add_guest(user)
end
it 'returns forbidden as response' do
subject
expect(response).to have_gitlab_http_status(:forbidden)
end
end
context 'when license is missing' do
before do
stub_licensed_features(type_of_work_analytics: false)
end
it 'returns forbidden as response' do
subject
expect(response).to have_gitlab_http_status(:forbidden)
end
end
context 'when `created_after` parameter is invalid' do
before do
params[:created_after] = 'invalid_date'
end
it_behaves_like 'expects unprocessable_entity response'
end
context 'when `created_after` parameter is missing' do
before do
params.delete(:created_after)
end
it_behaves_like 'expects unprocessable_entity response'
end
context 'when `created_after` date is later than `created_before` date' do
before do
params[:created_after] = 1.year.ago.to_date
params[:created_before] = 2.years.ago.to_date
end
it_behaves_like 'expects unprocessable_entity response'
end
end
describe 'GET show' do
context 'when valid parameters are given' do
it 'succeeds' do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('analytics/tasks_by_type', dir: 'ee')
end
it 'returns valid count' do
subject
date, count = json_response.first["series"].first
expect(Date.parse(date)).to eq(issue.created_at.to_date)
expect(count).to eq(1)
end
end
context 'when `label_id` is missing' do
before do
params.delete(:label_ids)
end
it_behaves_like 'expects unprocessable_entity response'
end
it_behaves_like 'parameter validation'
end
describe 'GET top_labels' do
let(:params) { { group_id: group.full_path, created_after: 10.days.ago, subject: 'Issue' } }
subject { get :top_labels, params: params }
context 'when valid parameters are given' do
it 'succeeds' do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('analytics/tasks_by_type_top_labels', dir: 'ee')
end
it 'returns valid count' do
subject
label_item = json_response.first
expect(label_item['title']).to eq(label.title)
expect(json_response.count).to eq(1)
end
end
it_behaves_like 'parameter validation'
end
end
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Analytics::CycleAnalytics::SummaryController do
RSpec.describe Groups::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' } }
......
......@@ -21,6 +21,30 @@ RSpec.describe Groups::Analytics::CycleAnalyticsController do
expect(response).to be_successful
end
it 'increments usage counter' do
expect(Gitlab::UsageDataCounters::CycleAnalyticsCounter).to receive(:count).with(:views)
get(:show, params: { group_id: group })
expect(response).to be_successful
end
it 'renders `show` template when feature flag is enabled' do
stub_feature_flags(Gitlab::Analytics::CYCLE_ANALYTICS_FEATURE_FLAG => true)
get(:show, params: { group_id: group })
expect(response).to render_template :show
end
it 'renders `404` when feature flag is disabled' do
stub_feature_flags(Gitlab::Analytics::CYCLE_ANALYTICS_FEATURE_FLAG => false)
get(:show, params: { group_id: group })
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when the license is missing' do
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Analytics::TasksByTypeController do
RSpec.describe Groups::Analytics::TasksByTypeController do
let_it_be(:user) { create(:user) }
let(:group) { create(:group) }
let(:label) { create(:group_label, group: group) }
......
......@@ -124,7 +124,7 @@ RSpec.describe 'Analytics (JavaScript fixtures)', :sidekiq_inline do
clean_frontend_fixtures('cycle_analytics/')
end
describe Analytics::CycleAnalytics::StagesController, type: :controller do
describe Groups::Analytics::CycleAnalytics::StagesController, type: :controller do
render_views
let(:params) { { created_after: 3.months.ago, created_before: Time.now, group_id: group.full_path } }
......@@ -183,7 +183,7 @@ RSpec.describe 'Analytics (JavaScript fixtures)', :sidekiq_inline do
end
end
describe Analytics::CycleAnalytics::SummaryController, type: :controller do
describe Groups::Analytics::CycleAnalytics::SummaryController, type: :controller do
render_views
let(:params) { { created_after: 3.months.ago, created_before: Time.now, group_id: group.full_path } }
......@@ -221,7 +221,7 @@ RSpec.describe 'Analytics (JavaScript fixtures)', :sidekiq_inline do
end
end
describe Analytics::TasksByTypeController, type: :controller do
describe Groups::Analytics::TasksByTypeController, type: :controller do
render_views
let(:label) { create(:group_label, group: group) }
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'admin dev ops analytics' do
let(:user) { create(:admin) }
before do
login_as(user)
end
it 'redirects from -/analytics to admin/dev_ops_report' do
get '/-/analytics'
expect(response).to redirect_to(admin_dev_ops_report_path)
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