Commit 0d54bbca authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge branch '32454-tasks-by-type-chart-backend' into 'master'

Data endpoint for tasks by type chart

Closes #32454

See merge request gitlab-org/gitlab!17526
parents 7fcd35ff de6cd259
......@@ -15,7 +15,7 @@ class Analytics::ApplicationController < ApplicationController
before_action(*args) { counter_klass.count(counter) }
end
def authorize_view_productivity_analytics!(action)
def authorize_view_by_action!(action)
return render_403 unless can?(current_user, action, @group || :global)
end
......
......@@ -11,7 +11,7 @@ class Analytics::ProductivityAnalyticsController < Analytics::ApplicationControl
check_feature_availability!(:productivity_analytics)
}
before_action -> {
authorize_view_productivity_analytics!(:view_productivity_analytics)
authorize_view_by_action!(:view_productivity_analytics)
}
before_action -> {
push_frontend_feature_flag(:productivity_analytics_scatterplot_enabled, default_enabled: true)
......
# frozen_string_literal: true
class Analytics::TasksByTypeController < Analytics::ApplicationController
check_feature_flag Gitlab::Analytics::TASKS_BY_TYPE_CHART_FEATURE_FLAG
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
before_action :prepare_date_range
# Mocked data, this will be replaced with real implementation
class TasksByType
LabelCountResult = Struct.new(:label, :series)
def counts_by_labels
[
LabelCountResult.new(GroupLabel.new(id: 1, title: 'label 1'), [
["2018-01-01", 23],
["2018-01-02", 5]
]),
LabelCountResult.new(GroupLabel.new(id: 2, title: 'label 3'), [
["2018-01-01", 3],
["2018-01-03", 10]
])
]
end
end
def show
render json: Analytics::TasksByTypeLabelEntity.represent(counts_by_labels)
end
private
def counts_by_labels
TasksByType.new.counts_by_labels
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
......@@ -92,6 +92,7 @@ class License < ApplicationRecord
scoped_labels
service_desk
smartcard_auth
type_of_work_analytics
unprotection_restrictions
]
EEP_FEATURES.freeze
......
......@@ -51,6 +51,7 @@ module EE
enable :read_prometheus
enable :view_code_analytics
enable :view_productivity_analytics
enable :view_type_of_work_charts
end
rule { maintainer }.policy do
......
# frozen_string_literal: true
module Analytics
class TasksByTypeLabelEntity < Grape::Entity
expose :label, with: LabelEntity
expose :series
end
end
......@@ -13,4 +13,10 @@ namespace :analytics do
resources :stages, only: [:index]
end
end
constraints(::Constraints::FeatureConstrainer.new(Gitlab::Analytics::TASKS_BY_TYPE_CHART_FEATURE_FLAG)) do
scope :type_of_work do
resource :tasks_by_type, controller: :tasks_by_type, only: :show
end
end
end
......@@ -5,10 +5,12 @@ module Gitlab
# Normally each analytics feature should be guarded with a feature flag.
CYCLE_ANALYTICS_FEATURE_FLAG = :cycle_analytics
PRODUCTIVITY_ANALYTICS_FEATURE_FLAG = :productivity_analytics
TASKS_BY_TYPE_CHART_FEATURE_FLAG = :tasks_by_type_chart
FEATURE_FLAGS = [
CYCLE_ANALYTICS_FEATURE_FLAG,
PRODUCTIVITY_ANALYTICS_FEATURE_FLAG
PRODUCTIVITY_ANALYTICS_FEATURE_FLAG,
TASKS_BY_TYPE_CHART_FEATURE_FLAG
].freeze
def self.any_features_enabled?
......
# frozen_string_literal: true
require 'spec_helper'
describe Analytics::TasksByTypeController do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:params) { { group_id: group.full_path, label_ids: [1, 2], created_after: '2018-01-01' } }
let(:subject) { get :show, params: params }
before do
stub_licensed_features(type_of_work_analytics: true)
stub_feature_flags(Gitlab::Analytics::TASKS_BY_TYPE_CHART_FEATURE_FLAG => true)
group.add_reporter(user)
sign_in(user)
end
it 'succeeds' do
subject
expect(response).to be_successful
expect(response).to match_response_schema('analytics/tasks_by_type', dir: 'ee')
end
context 'when user access level is lower than reporter' do
before do
group.add_guest(user)
end
it 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 feature flag is disabled' do
before do
stub_feature_flags(Gitlab::Analytics::TASKS_BY_TYPE_CHART_FEATURE_FLAG => false)
end
it 'returns not_found as response' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end
shared_examples 'expects unprocessable_entity response' do
it 'returns unprocessable_entity as resposne' do
subject
expect(response).to have_gitlab_http_status(:unprocessable_entity)
end
end
context 'when `label_id` is missing' do
before do
params.delete(:label_ids)
end
it_behaves_like 'expects unprocessable_entity response'
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
{
"type": "array",
"items": {
"type": "object" ,
"additionalProperties": false,
"properties": {
"label": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"title": {
"type": "string"
},
"color": {
"type": "string"
},
"text_color": {
"type": "string"
}
}
},
"series": {
"type": "array",
"items": [
{
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": [
{
"description": "Creation date represented as string with ISO 8601 date format (YYYY-MM-DD).",
"type": "string"
},
{
"description": "Number of issues or merge requests created at the given date.",
"type": "integer"
}
]
}
]
}
}
}
}
......@@ -437,4 +437,8 @@ describe GroupPolicy do
describe 'view_productivity_analytics' do
include_examples 'analytics policy', :view_productivity_analytics
end
describe 'view_type_of_work_charts' do
include_examples 'analytics policy', :view_type_of_work_charts
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