Commit ae256731 authored by Adam Hegyi's avatar Adam Hegyi Committed by Michael Kozono

Expose CA stage services via API

- Add create, update, destroy actions
- Implement delete service
parent ee421052
...@@ -6,10 +6,11 @@ module Analytics ...@@ -6,10 +6,11 @@ module Analytics
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
before_action :authorize_access!
def index def index
result = stage_list_service.execute return render_403 unless can?(current_user, :read_group_cycle_analytics, @group)
result = list_service.execute
if result.success? if result.success?
render json: cycle_analytics_configuration(result.payload[:stages]) render json: cycle_analytics_configuration(result.payload[:stages])
...@@ -18,23 +19,55 @@ module Analytics ...@@ -18,23 +19,55 @@ module Analytics
end end
end end
private 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)
def authorize_access! render_stage_service_result(update_service.execute)
render_403 unless can?(current_user, :read_group_cycle_analytics, @group)
end end
def destroy
return render_403 unless can?(current_user, :delete_group_stage, @group)
render_stage_service_result(delete_service.execute)
end
private
def cycle_analytics_configuration(stages) def cycle_analytics_configuration(stages)
stage_presenters = stages.map { |s| StagePresenter.new(s) } stage_presenters = stages.map { |s| StagePresenter.new(s) }
Analytics::CycleAnalytics::ConfigurationEntity.new(stages: stage_presenters) Analytics::CycleAnalytics::ConfigurationEntity.new(stages: stage_presenters)
end end
def stage_list_service def list_service
Analytics::CycleAnalytics::Stages::ListService.new( Stages::ListService.new(parent: @group, current_user: current_user)
parent: @group, end
current_user: current_user
) def create_service
Stages::CreateService.new(parent: @group, current_user: current_user, params: params.permit(:name, :start_event_identifier, :end_event_identifier))
end
def update_service
Stages::UpdateService.new(parent: @group, current_user: current_user, params: params.permit(:name, :start_event_identifier, :end_event_identifier, :id))
end
def delete_service
Stages::DeleteService.new(parent: @group, current_user: current_user, params: params.permit(:id))
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 end
end end
end end
......
...@@ -8,8 +8,8 @@ module Analytics ...@@ -8,8 +8,8 @@ module Analytics
expose :description expose :description
expose :id expose :id
expose :custom expose :custom
expose :start_event_identifier, if: :custom? expose :start_event_identifier, if: -> (s) { s.custom? }
expose :end_event_identifier, if: :custom? expose :end_event_identifier, if: -> (s) { s.custom? }
def id def id
object.id || object.name object.id || object.name
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
module Analytics module Analytics
module CycleAnalytics module CycleAnalytics
module Stages
class BaseService class BaseService
include Gitlab::Allowable include Gitlab::Allowable
...@@ -52,4 +53,5 @@ module Analytics ...@@ -52,4 +53,5 @@ module Analytics
end end
end end
end end
end
end end
# frozen_string_literal: true
module Analytics
module CycleAnalytics
module Stages
class DeleteService < BaseService
def initialize(parent:, current_user:, params:)
super
@stage = Analytics::CycleAnalytics::StageFinder.new(parent: parent, stage_id: params[:id]).execute
end
def execute
return forbidden if !can?(current_user, :delete_group_stage, parent) || @stage.default_stage?
@stage.destroy!
success(@stage, :ok)
end
end
end
end
end
...@@ -10,7 +10,7 @@ namespace :analytics do ...@@ -10,7 +10,7 @@ namespace :analytics do
constraints(::Constraints::FeatureConstrainer.new(Gitlab::Analytics::CYCLE_ANALYTICS_FEATURE_FLAG)) do constraints(::Constraints::FeatureConstrainer.new(Gitlab::Analytics::CYCLE_ANALYTICS_FEATURE_FLAG)) do
resource :cycle_analytics, only: :show resource :cycle_analytics, only: :show
namespace :cycle_analytics do namespace :cycle_analytics do
resources :stages, only: [:index] resources :stages, only: [:index, :create, :update, :destroy]
end end
end end
......
...@@ -3,12 +3,10 @@ ...@@ -3,12 +3,10 @@
require 'spec_helper' require 'spec_helper'
describe Analytics::CycleAnalytics::StagesController do describe Analytics::CycleAnalytics::StagesController do
let(:user) { create(:user) } let_it_be(:user) { create(:user) }
let(:group) { create(:group) } let_it_be(:group, refind: true) { create(:group) }
let(:params) { { group_id: group.full_path } } let(:params) { { group_id: group.full_path } }
subject { get :index, params: params }
before do before do
stub_feature_flags(Gitlab::Analytics::CYCLE_ANALYTICS_FEATURE_FLAG => true) stub_feature_flags(Gitlab::Analytics::CYCLE_ANALYTICS_FEATURE_FLAG => true)
stub_licensed_features(cycle_analytics_for_groups: true) stub_licensed_features(cycle_analytics_for_groups: true)
...@@ -17,6 +15,9 @@ describe Analytics::CycleAnalytics::StagesController do ...@@ -17,6 +15,9 @@ describe Analytics::CycleAnalytics::StagesController do
sign_in(user) sign_in(user)
end end
describe 'GET `index`' do
subject { get :index, params: params }
it 'succeeds' do it 'succeeds' do
subject subject
...@@ -51,52 +52,111 @@ describe Analytics::CycleAnalytics::StagesController do ...@@ -51,52 +52,111 @@ describe Analytics::CycleAnalytics::StagesController do
expect(response).to be_successful expect(response).to be_successful
end end
it 'renders 404 when group_id is not provided' do it 'renders `forbidden` based on the response of the service object' do
params[:group_id] = nil expect_any_instance_of(Analytics::CycleAnalytics::Stages::ListService).to receive(:can?).and_return(false)
subject subject
expect(response).to have_gitlab_http_status(:not_found) expect(response).to have_gitlab_http_status(:forbidden)
end
include_examples 'group permission check on the controller level'
end end
it 'renders 404 when group is missing' do describe 'POST `create`' do
params[:group_id] = 'missing_group' 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 subject
expect(response).to have_gitlab_http_status(:not_found) 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 end
it 'renders 404 when feature flag is disabled' do describe 'PUT `update`' do
stub_feature_flags(Gitlab::Analytics::CYCLE_ANALYTICS_FEATURE_FLAG => false) let(:stage) { create(:cycle_analytics_group_stage, parent: group) }
subject { put :update, params: params.merge(id: stage.id) }
subject include_examples 'group permission check on the controller level'
expect(response).to have_gitlab_http_status(:not_found) 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 end
it 'renders 403 when user has no reporter access' do it 'succeeds' do
GroupMember.where(user: user).delete_all subject
group.add_guest(user)
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 subject
expect(response).to have_gitlab_http_status(:forbidden) stage.reload
expect(stage.name).to eq(params[:name])
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 end
it 'renders 403 when feature is not available for the group' do include_examples 'group permission check on the controller level'
stub_licensed_features(cycle_analytics_for_groups: false)
context 'when persisted stage id is passed' do
it 'succeeds' do
subject subject
expect(response).to have_gitlab_http_status(:forbidden) expect(response).to be_successful
end end
it 'renders 403 based on the response of the service object' do it 'deletes the record' do
expect_any_instance_of(Analytics::CycleAnalytics::Stages::ListService).to receive(:can?).and_return(false) 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 subject
expect(response).to have_gitlab_http_status(:forbidden) expect(response).to have_gitlab_http_status(:forbidden)
end end
end
end
end end
...@@ -21,5 +21,5 @@ ...@@ -21,5 +21,5 @@
"type": "boolean" "type": "boolean"
} }
}, },
"additionalProperties": false "additionalProperties": true
} }
{
"type": "object",
"properties": {
"message": {
"type": "string"
},
"errors": {
"type": "object",
"additionalProperties" : {
"type" : "array",
"items": {
"type": "string"
}
}
}
},
"required": ["message", "errors"],
"additionalProperties": false
}
# frozen_string_literal: true
require 'spec_helper'
describe Analytics::CycleAnalytics::Stages::DeleteService do
let_it_be(:group, refind: true) { create(:group) }
let_it_be(:user, refind: true) { create(:user) }
let_it_be(:stage, refind: true) { create(:cycle_analytics_group_stage, group: group) }
let(:params) { { id: stage.id } }
subject { described_class.new(parent: group, params: params, current_user: user).execute }
before_all do
group.add_user(user, :reporter)
end
before do
stub_licensed_features(cycle_analytics_for_groups: true)
end
it_behaves_like 'permission check for cycle analytics stage services', :cycle_analytics_for_groups
context 'when persisted stage is given' do
it { expect(subject).to be_success }
it 'deletes the stage' do
subject
expect(group.cycle_analytics_stages.find_by(id: stage.id)).to be_nil
end
end
context 'disallows deletion when default stage is given' do
let_it_be(:stage, refind: true) { create(:cycle_analytics_group_stage, group: group, custom: false) }
it { expect(subject).not_to be_success }
it { expect(subject.http_status).to eq(:forbidden) }
end
end
# frozen_string_literal: true
require 'spec_helper'
shared_examples 'group permission check on the controller level' do
context 'when `group_id` is not provided' do
before do
params[:group_id] = nil
end
it 'renders `not_found` when group_id is not provided' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when `group_id` is not found' do
before do
params[:group_id] = 'missing_group'
end
it 'renders `not_found` when group is missing' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when feature flag is disabled' do
before do
stub_feature_flags(Gitlab::Analytics::CYCLE_ANALYTICS_FEATURE_FLAG => false)
end
it 'renders `not_found` response' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when user has no lower access level than `reporter`' do
before do
GroupMember.where(user: user).delete_all
group.add_guest(user)
end
it 'renders `forbidden` response' do
subject
expect(response).to have_gitlab_http_status(:forbidden)
end
end
context 'when feature is not available for the group' do
before do
stub_licensed_features(cycle_analytics_for_groups: false)
end
it 'renders `forbidden` response' do
subject
expect(response).to have_gitlab_http_status(:forbidden)
end
end
end
shared_context 'when invalid stage parameters are given' do
before do
params[:name] = ''
end
it 'renders the validation errors' 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
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