Commit b8b97284 authored by Dylan Griffith's avatar Dylan Griffith

Merge branch '321087-adapt-devops-adoption-for-groups' into 'master'

Introduce group-level API for devops adoption

See merge request gitlab-org/gitlab!55479
parents 2f6d081d 893e6633
...@@ -86,8 +86,10 @@ Returns [`DevopsAdoptionSegmentConnection`](#devopsadoptionsegmentconnection). ...@@ -86,8 +86,10 @@ Returns [`DevopsAdoptionSegmentConnection`](#devopsadoptionsegmentconnection).
| ---- | ---- | ----------- | | ---- | ---- | ----------- |
| `after` | [`String`](#string) | Returns the elements in the list that come after the specified cursor. | | `after` | [`String`](#string) | Returns the elements in the list that come after the specified cursor. |
| `before` | [`String`](#string) | Returns the elements in the list that come before the specified cursor. | | `before` | [`String`](#string) | Returns the elements in the list that come before the specified cursor. |
| `directDescendantsOnly` | [`Boolean`](#boolean) | Limits segments to direct descendants of specified parent. |
| `first` | [`Int`](#int) | Returns the first _n_ elements from the list. | | `first` | [`Int`](#int) | Returns the first _n_ elements from the list. |
| `last` | [`Int`](#int) | Returns the last _n_ elements from the list. | | `last` | [`Int`](#int) | Returns the last _n_ elements from the list. |
| `parentNamespaceId` | [`NamespaceID`](#namespaceid) | Filter by ancestor namespace. |
### `echo` ### `echo`
......
# frozen_string_literal: true
module Analytics
module DevopsAdoption
class SegmentsFinder
attr_reader :params, :current_user
def initialize(current_user, params:)
@current_user = current_user
@params = params
end
def execute
scope = ::Analytics::DevopsAdoption::Segment.ordered_by_name
if direct_descendants_only?
scope = scope.for_namespaces(parent_with_direct_descendants)
else
scope = scope.for_parent(parent_namespace) if parent_namespace
end
scope
end
private
def parent_with_direct_descendants
parent_namespace ? [parent_namespace] + parent_namespace.children : ::Group.top_most
end
def parent_namespace
params[:parent_namespace]
end
def direct_descendants_only?
params[:direct_descendants_only]
end
end
end
end
...@@ -60,9 +60,9 @@ module EE ...@@ -60,9 +60,9 @@ module EE
mount_mutation ::Mutations::DastSiteTokens::Create mount_mutation ::Mutations::DastSiteTokens::Create
mount_mutation ::Mutations::Namespaces::IncreaseStorageTemporarily mount_mutation ::Mutations::Namespaces::IncreaseStorageTemporarily
mount_mutation ::Mutations::QualityManagement::TestCases::Create mount_mutation ::Mutations::QualityManagement::TestCases::Create
mount_mutation ::Mutations::Admin::Analytics::DevopsAdoption::Segments::Create mount_mutation ::Mutations::Analytics::DevopsAdoption::Segments::Create
mount_mutation ::Mutations::Admin::Analytics::DevopsAdoption::Segments::BulkFindOrCreate mount_mutation ::Mutations::Analytics::DevopsAdoption::Segments::BulkFindOrCreate
mount_mutation ::Mutations::Admin::Analytics::DevopsAdoption::Segments::Delete mount_mutation ::Mutations::Analytics::DevopsAdoption::Segments::Delete
mount_mutation ::Mutations::IncidentManagement::OncallSchedule::Create mount_mutation ::Mutations::IncidentManagement::OncallSchedule::Create
mount_mutation ::Mutations::IncidentManagement::OncallSchedule::Update mount_mutation ::Mutations::IncidentManagement::OncallSchedule::Update
mount_mutation ::Mutations::IncidentManagement::OncallSchedule::Destroy mount_mutation ::Mutations::IncidentManagement::OncallSchedule::Destroy
......
# frozen_string_literal: true
module Mutations
module Admin
module Analytics
module DevopsAdoption
module Segments
class BulkFindOrCreate < BaseMutation
include Mixins::CommonMethods
graphql_name 'BulkFindOrCreateDevopsAdoptionSegments'
argument :namespace_ids, [::Types::GlobalIDType[::Namespace]],
required: true,
description: 'List of Namespace IDs for the segments.'
field :segments,
[::Types::Admin::Analytics::DevopsAdoption::SegmentType],
null: true,
description: 'Created segments after mutation.'
def resolve(namespace_ids:, **)
namespaces = GlobalID::Locator.locate_many(namespace_ids)
with_authorization_handler do
service = ::Analytics::DevopsAdoption::Segments::BulkFindOrCreateService
.new(current_user: current_user, params: { namespaces: namespaces })
segments = service.execute.payload.fetch(:segments)
{
segments: segments.select(&:persisted?),
errors: segments.sum { |segment| errors_on_object(segment) }
}
end
end
end
end
end
end
end
end
# frozen_string_literal: true
module Mutations
module Admin
module Analytics
module DevopsAdoption
module Segments
class Create < BaseMutation
include Mixins::CommonMethods
graphql_name 'CreateDevopsAdoptionSegment'
argument :namespace_id, ::Types::GlobalIDType[::Namespace],
required: true,
description: 'Namespace ID to set for the segment.'
field :segment,
Types::Admin::Analytics::DevopsAdoption::SegmentType,
null: true,
description: 'The segment after mutation.'
def resolve(namespace_id:, **)
namespace = namespace_id.find
with_authorization_handler do
service = ::Analytics::DevopsAdoption::Segments::CreateService
.new(current_user: current_user, params: { namespace: namespace })
response = service.execute
resolve_segment(response)
end
end
end
end
end
end
end
end
# frozen_string_literal: true
module Mutations
module Admin
module Analytics
module DevopsAdoption
module Segments
class Delete < BaseMutation
include Mixins::CommonMethods
graphql_name 'DeleteDevopsAdoptionSegment'
argument :id, [::Types::GlobalIDType[::Analytics::DevopsAdoption::Segment]],
required: true,
description: "One or many IDs of the segments to delete."
def resolve(id:, **)
segments = GlobalID::Locator.locate_many(id)
with_authorization_handler do
service = ::Analytics::DevopsAdoption::Segments::BulkDeleteService
.new(segments: segments, current_user: current_user)
response = service.execute
errors = response.payload[:segments].sum { |segment| errors_on_object(segment) }
{ errors: errors }
end
end
end
end
end
end
end
end
# frozen_string_literal: true
module Mutations
module Admin
module Analytics
module DevopsAdoption
module Segments
module Mixins
# This module ensures that the mutations are admin only
module CommonMethods
ADMIN_MESSAGE = 'You must be an admin to use this mutation'
FEATURE_UNAVAILABLE_MESSAGE = 'Feature is not available'
def ready?(**args)
unless License.feature_available?(:instance_level_devops_adoption)
raise_resource_not_available_error!(FEATURE_UNAVAILABLE_MESSAGE)
end
super
end
private
def resolve_segment(response)
segment = response.payload.fetch(:segment)
{
segment: response.success? ? response.payload.fetch(:segment) : nil,
errors: errors_on_object(segment)
}
end
def with_authorization_handler
yield
rescue ::Analytics::DevopsAdoption::Segments::AuthorizationError => e
handle_unauthorized!(e)
end
def handle_unauthorized!(_exception)
raise_resource_not_available_error!(ADMIN_MESSAGE)
end
end
end
end
end
end
end
end
# frozen_string_literal: true
module Mutations
module Analytics
module DevopsAdoption
module Segments
class BulkFindOrCreate < BaseMutation
include Mixins::CommonMethods
graphql_name 'BulkFindOrCreateDevopsAdoptionSegments'
argument :namespace_ids, [::Types::GlobalIDType[::Namespace]],
required: true,
description: 'List of Namespace IDs for the segments.'
field :segments,
[::Types::Admin::Analytics::DevopsAdoption::SegmentType],
null: true,
description: 'Created segments after mutation.'
def resolve(namespace_ids:, **)
namespaces = GlobalID::Locator.locate_many(namespace_ids)
with_authorization_handler do
service = ::Analytics::DevopsAdoption::Segments::BulkFindOrCreateService
.new(current_user: current_user, params: { namespaces: namespaces })
segments = service.execute.payload.fetch(:segments)
{
segments: segments.select(&:persisted?),
errors: segments.sum { |segment| errors_on_object(segment) }
}
end
end
end
end
end
end
end
# frozen_string_literal: true
module Mutations
module Analytics
module DevopsAdoption
module Segments
class Create < BaseMutation
include Mixins::CommonMethods
graphql_name 'CreateDevopsAdoptionSegment'
argument :namespace_id, ::Types::GlobalIDType[::Namespace],
required: true,
description: 'Namespace ID to set for the segment.'
field :segment,
Types::Admin::Analytics::DevopsAdoption::SegmentType,
null: true,
description: 'The segment after mutation.'
def resolve(namespace_id:, **)
namespace = namespace_id.find
with_authorization_handler do
service = ::Analytics::DevopsAdoption::Segments::CreateService
.new(current_user: current_user, params: { namespace: namespace })
response = service.execute
resolve_segment(response)
end
end
end
end
end
end
end
# frozen_string_literal: true
module Mutations
module Analytics
module DevopsAdoption
module Segments
class Delete < BaseMutation
include Mixins::CommonMethods
graphql_name 'DeleteDevopsAdoptionSegment'
argument :id, [::Types::GlobalIDType[::Analytics::DevopsAdoption::Segment]],
required: true,
description: 'One or many IDs of the segments to delete.'
def resolve(id:, **)
segments = GlobalID::Locator.locate_many(id)
with_authorization_handler do
service = ::Analytics::DevopsAdoption::Segments::BulkDeleteService
.new(segments: segments, current_user: current_user)
response = service.execute
errors = response.payload[:segments].sum { |segment| errors_on_object(segment) }
{ errors: errors }
end
end
end
end
end
end
end
# frozen_string_literal: true
module Mutations
module Analytics
module DevopsAdoption
module Segments
module Mixins
module CommonMethods
private
def resolve_segment(response)
segment = response.payload.fetch(:segment)
{
segment: response.success? ? response.payload.fetch(:segment) : nil,
errors: errors_on_object(segment)
}
end
def with_authorization_handler
yield
rescue ::Analytics::DevopsAdoption::Segments::AuthorizationError => e
handle_unauthorized!(e)
end
def handle_unauthorized!(_exception)
raise_resource_not_available_error!
end
end
end
end
end
end
end
...@@ -6,31 +6,44 @@ module Resolvers ...@@ -6,31 +6,44 @@ module Resolvers
module DevopsAdoption module DevopsAdoption
class SegmentsResolver < BaseResolver class SegmentsResolver < BaseResolver
include Gitlab::Graphql::Authorize::AuthorizeResource include Gitlab::Graphql::Authorize::AuthorizeResource
include Gitlab::Allowable
type Types::Admin::Analytics::DevopsAdoption::SegmentType, null: true type Types::Admin::Analytics::DevopsAdoption::SegmentType, null: true
def resolve argument :parent_namespace_id, ::Types::GlobalIDType[::Namespace],
authorize! required: false,
description: 'Filter by ancestor namespace.'
if segments_feature_available? argument :direct_descendants_only, ::GraphQL::BOOLEAN_TYPE,
::Analytics::DevopsAdoption::Segment.ordered_by_name required: false,
else description: 'Limits segments to direct descendants of specified parent.'
::Analytics::DevopsAdoption::Segment.none
end def resolve(parent_namespace_id: nil, direct_descendants_only: false, **)
parent = GlobalID::Locator.locate(parent_namespace_id) if parent_namespace_id
authorize!(parent)
::Analytics::DevopsAdoption::SegmentsFinder.new(current_user, params: {
parent_namespace: parent, direct_descendants_only: direct_descendants_only
}).execute
end end
private private
def segments_feature_available? def authorize!(parent)
License.feature_available?(:instance_level_devops_adoption) parent ? authorize_with_namespace!(parent) : authorize_global!
end end
def authorize! def authorize_global!
admin? || raise_resource_not_available_error! unless can?(current_user, :view_instance_devops_adoption)
raise_resource_not_available_error!
end
end end
def admin? def authorize_with_namespace!(parent)
context[:current_user].present? && context[:current_user].admin? unless can?(current_user, :view_group_devops_adoption, parent)
raise_resource_not_available_error!
end
end end
end end
end end
......
...@@ -12,4 +12,13 @@ class Analytics::DevopsAdoption::Segment < ApplicationRecord ...@@ -12,4 +12,13 @@ class Analytics::DevopsAdoption::Segment < ApplicationRecord
validates :namespace, uniqueness: true, presence: true validates :namespace, uniqueness: true, presence: true
scope :ordered_by_name, -> { includes(:namespace).order('"namespaces"."name" ASC') } scope :ordered_by_name, -> { includes(:namespace).order('"namespaces"."name" ASC') }
scope :for_namespaces, -> (namespaces) { where(namespace_id: namespaces) }
scope :for_parent, -> (namespace) {
if Feature.enabled?(:recursive_namespace_lookup_as_inner_join, namespace)
join_sql = namespace.self_and_descendants.to_sql
joins("INNER JOIN (#{join_sql}) namespaces ON namespaces.id=#{self.arel_table.name}.namespace_id")
else
for_namespaces(namespace.self_and_descendants)
end
}
end end
...@@ -27,6 +27,7 @@ class License < ApplicationRecord ...@@ -27,6 +27,7 @@ class License < ApplicationRecord
group_webhooks group_webhooks
group_level_devops_adoption group_level_devops_adoption
instance_level_devops_adoption instance_level_devops_adoption
group_level_devops_adoption
issuable_default_templates issuable_default_templates
issue_weights issue_weights
iterations iterations
......
...@@ -29,13 +29,21 @@ module EE ...@@ -29,13 +29,21 @@ module EE
end end
end end
condition(:instance_devops_adoption_available) do
::License.feature_available?(:instance_level_devops_adoption)
end
rule { ~anonymous & operations_dashboard_available }.enable :read_operations_dashboard rule { ~anonymous & operations_dashboard_available }.enable :read_operations_dashboard
rule { admin & instance_devops_adoption_available }.policy do
enable :manage_devops_adoption_segments
enable :view_instance_devops_adoption
end
rule { admin }.policy do rule { admin }.policy do
enable :read_licenses enable :read_licenses
enable :destroy_licenses enable :destroy_licenses
enable :read_all_geo enable :read_all_geo
enable :manage_devops_adoption_segments
enable :manage_subscription enable :manage_subscription
end end
......
...@@ -197,6 +197,7 @@ module EE ...@@ -197,6 +197,7 @@ module EE
end end
rule { reporter & group_devops_adoption_available }.policy do rule { reporter & group_devops_adoption_available }.policy do
enable :manage_devops_adoption_segments
enable :view_group_devops_adoption enable :view_group_devops_adoption
end end
......
...@@ -4,21 +4,19 @@ module Analytics ...@@ -4,21 +4,19 @@ module Analytics
module DevopsAdoption module DevopsAdoption
module Segments module Segments
class BulkDeleteService class BulkDeleteService
include CommonMethods
def initialize(segments:, current_user:) def initialize(segments:, current_user:)
@segments = segments @segments = segments
@current_user = current_user @current_user = current_user
end end
def execute def execute
authorize! deletion_services.map(&:authorize!)
result = nil result = nil
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
segments.each do |segment| deletion_services.each do |service|
response = delete_segment(segment) response = service.execute
if response.error? if response.error?
result = ServiceResponse.error(message: response.message, payload: response_payload) result = ServiceResponse.error(message: response.message, payload: response_payload)
...@@ -40,8 +38,10 @@ module Analytics ...@@ -40,8 +38,10 @@ module Analytics
{ segments: segments } { segments: segments }
end end
def delete_segment(segment) def deletion_services
DeleteService.new(current_user: current_user, segment: segment).execute @deletion_services ||= segments.map do |segment|
DeleteService.new(current_user: current_user, segment: segment)
end
end end
end end
end end
......
...@@ -4,8 +4,6 @@ module Analytics ...@@ -4,8 +4,6 @@ module Analytics
module DevopsAdoption module DevopsAdoption
module Segments module Segments
class BulkFindOrCreateService class BulkFindOrCreateService
include CommonMethods
def initialize(params: {}, current_user:) def initialize(params: {}, current_user:)
@params = params @params = params
@current_user = current_user @current_user = current_user
...@@ -14,20 +12,26 @@ module Analytics ...@@ -14,20 +12,26 @@ module Analytics
def execute def execute
authorize! authorize!
segments = params[:namespaces].map do |namespace| segments = services.map do |service|
response = FindOrCreateService service.execute.payload[:segment]
.new(current_user: current_user, params: { namespace: namespace })
.execute
response.payload[:segment]
end end
ServiceResponse.success(payload: { segments: segments }) ServiceResponse.success(payload: { segments: segments })
end end
def authorize!
services.each(&:authorize!)
end
private private
attr_reader :params, :current_user attr_reader :params, :current_user
def services
@services ||= params[:namespaces].map do |namespace|
FindOrCreateService.new(current_user: current_user, params: { namespace: namespace })
end
end
end end
end end
end end
......
...@@ -7,7 +7,7 @@ module Analytics ...@@ -7,7 +7,7 @@ module Analytics
include Gitlab::Allowable include Gitlab::Allowable
def authorize! def authorize!
unless can?(current_user, :manage_devops_adoption_segments, :global) unless can?(current_user, :manage_devops_adoption_segments, namespace)
raise AuthorizationError.new(self, 'Forbidden') raise AuthorizationError.new(self, 'Forbidden')
end end
end end
......
...@@ -15,7 +15,7 @@ module Analytics ...@@ -15,7 +15,7 @@ module Analytics
def execute def execute
authorize! authorize!
segment.assign_attributes(attributes) segment.assign_attributes(namespace: namespace)
if segment.save if segment.save
Analytics::DevopsAdoption::CreateSnapshotWorker.perform_async(segment.id) Analytics::DevopsAdoption::CreateSnapshotWorker.perform_async(segment.id)
...@@ -34,8 +34,8 @@ module Analytics ...@@ -34,8 +34,8 @@ module Analytics
{ segment: segment } { segment: segment }
end end
def attributes def namespace
params.slice(:namespace, :namespace_id) params[:namespace]
end end
end end
end end
......
...@@ -19,7 +19,7 @@ module Analytics ...@@ -19,7 +19,7 @@ module Analytics
ServiceResponse.success(payload: response_payload) ServiceResponse.success(payload: response_payload)
rescue ActiveRecord::RecordNotDestroyed rescue ActiveRecord::RecordNotDestroyed
ServiceResponse.error(message: 'Devops Adoption Segment deletion error', payload: response_payload) ServiceResponse.error(message: 'DevOps Adoption Segment deletion error', payload: response_payload)
end end
end end
...@@ -28,7 +28,11 @@ module Analytics ...@@ -28,7 +28,11 @@ module Analytics
attr_reader :segment, :current_user attr_reader :segment, :current_user
def response_payload def response_payload
{ segment: @segment } { segment: segment }
end
def namespace
segment.namespace
end end
end end
end end
......
...@@ -14,21 +14,29 @@ module Analytics ...@@ -14,21 +14,29 @@ module Analytics
def execute def execute
authorize! authorize!
segment = Analytics::DevopsAdoption::Segment.find_by_namespace_id(namespace_id) segment = Analytics::DevopsAdoption::Segment.find_by_namespace_id(namespace.id)
if segment if segment
ServiceResponse.success(payload: { segment: segment }) ServiceResponse.success(payload: { segment: segment })
else else
CreateService.new(current_user: current_user, params: params).execute create_service.execute
end end
end end
def authorize!
create_service.authorize!
end
private private
attr_reader :params, :current_user attr_reader :params, :current_user
def namespace_id def namespace
params.fetch(:namespace_id, params[:namespace]&.id) params[:namespace]
end
def create_service
@create_service ||= CreateService.new(current_user: current_user, params: params)
end end
end end
end end
......
---
title: Introduce group-level API for DevOps adoption
merge_request: 55479
author:
type: added
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Analytics::DevopsAdoption::SegmentsFinder do
let_it_be(:admin_user) { create(:user, :admin) }
subject(:finder_segments) { described_class.new(admin_user, params: params).execute }
let(:params) { {} }
describe '#execute' do
let_it_be(:root_group_1) { create(:group, name: 'bbb') }
let_it_be(:root_group_2) { create(:group, name: 'aaa') }
let_it_be(:segment_1) { create(:devops_adoption_segment, namespace: root_group_1) }
let_it_be(:segment_2) { create(:devops_adoption_segment, namespace: root_group_2) }
let_it_be(:direct_subgroup) { create(:group, name: 'ccc', parent: root_group_1) }
let_it_be(:direct_subgroup_segment) do
create(:devops_adoption_segment, namespace: direct_subgroup)
end
let_it_be(:indirect_subgroup) { create(:group, name: 'ddd', parent: direct_subgroup) }
let_it_be(:indirect_subgroup_segment) do
create(:devops_adoption_segment, namespace: indirect_subgroup)
end
before do
stub_licensed_features(instance_level_devops_adoption: true)
stub_licensed_features(group_level_devops_adoption: true)
end
context 'for instance level' do
it 'returns segments ordered by name' do
expect(finder_segments).to eq([segment_2, segment_1, direct_subgroup_segment, indirect_subgroup_segment])
end
context 'with direct_descendants_only' do
let(:params) { super().merge(direct_descendants_only: true) }
it 'returns direct descendants only' do
expect(finder_segments).to eq([segment_2, segment_1])
end
end
end
context 'for group level' do
let(:params) { super().merge(parent_namespace: segment_1.namespace) }
it 'returns segments scoped to given namespace ordered by name' do
expect(finder_segments).to eq([segment_1, direct_subgroup_segment, indirect_subgroup_segment])
end
context 'with direct_descendants_only' do
let(:params) { super().merge(direct_descendants_only: true) }
it 'returns direct descendants only' do
expect(finder_segments).to eq([segment_1, direct_subgroup_segment])
end
end
end
end
end
...@@ -6,7 +6,6 @@ RSpec.describe Resolvers::Admin::Analytics::DevopsAdoption::SegmentsResolver do ...@@ -6,7 +6,6 @@ RSpec.describe Resolvers::Admin::Analytics::DevopsAdoption::SegmentsResolver do
include GraphqlHelpers include GraphqlHelpers
let_it_be(:admin_user) { create(:user, :admin) } let_it_be(:admin_user) { create(:user, :admin) }
let(:current_user) { admin_user }
def resolve_segments(args = {}, context = {}) def resolve_segments(args = {}, context = {})
resolve(described_class, args: args, ctx: context) resolve(described_class, args: args, ctx: context)
...@@ -14,49 +13,105 @@ RSpec.describe Resolvers::Admin::Analytics::DevopsAdoption::SegmentsResolver do ...@@ -14,49 +13,105 @@ RSpec.describe Resolvers::Admin::Analytics::DevopsAdoption::SegmentsResolver do
describe '#resolve' do describe '#resolve' do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:segment_1) { create(:devops_adoption_segment, namespace: create(:group, name: 'bbb')) } let_it_be(:root_group_1) { create(:group, name: 'bbb') }
let_it_be(:segment_2) { create(:devops_adoption_segment, namespace: create(:group, name: 'aaa')) } let_it_be(:root_group_2) { create(:group, name: 'aaa') }
let_it_be(:segment_1) { create(:devops_adoption_segment, namespace: root_group_1) }
let_it_be(:segment_2) { create(:devops_adoption_segment, namespace: root_group_2) }
let_it_be(:direct_subgroup) { create(:group, name: 'ccc', parent: root_group_1) }
let_it_be(:direct_subgroup_segment) do
create(:devops_adoption_segment, namespace: direct_subgroup)
end
subject { resolve_segments({}, { current_user: current_user }) } let_it_be(:indirect_subgroup) { create(:group, name: 'ddd', parent: direct_subgroup) }
let_it_be(:indirect_subgroup_segment) do
create(:devops_adoption_segment, namespace: indirect_subgroup)
end
before do before do
stub_licensed_features(instance_level_devops_adoption: true) stub_licensed_features(instance_level_devops_adoption: true)
stub_licensed_features(group_level_devops_adoption: true)
end end
context 'when requesting project count measurements' do subject(:resolved_segments) { resolve_segments(params, { current_user: current_user }) }
context 'as an admin user' do
let(:params) { {} }
context 'for instance level', :enable_admin_mode do
let(:current_user) { admin_user } let(:current_user) { admin_user }
it 'returns the records, ordered by name' do context 'as an admin user' do
expect(subject).to eq([segment_2, segment_1]) it 'returns segments for all groups, ordered by name' do
expect(resolved_segments).to eq([segment_2, segment_1, direct_subgroup_segment, indirect_subgroup_segment])
end
context 'with direct_descendants_only' do
let(:params) { super().merge(direct_descendants_only: true) }
it 'returns segments for root groups, ordered by name' do
expect(resolved_segments).to eq([segment_2, segment_1])
end
end end
end end
context 'when the feature is not available' do context 'as a non-admin user' do
let(:current_user) { admin_user } let(:current_user) { user }
it 'raises ResourceNotAvailable error' do
expect { resolved_segments }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when the feature is not available' do
before do before do
stub_licensed_features(instance_level_devops_adoption: false) stub_licensed_features(instance_level_devops_adoption: false)
end end
it 'returns the records, ordered by name' do it 'raises ResourceNotAvailable error' do
expect(subject).to be_empty expect { resolved_segments }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end end
end end
context 'as a non-admin user' do context 'for group level' do
let(:params) { { parent_namespace_id: root_group_1.to_gid.to_s } }
let(:current_user) { user } let(:current_user) { user }
context 'for reporter+' do
before do
root_group_1.add_reporter(user)
end
it 'returns segments for given parent group and its descendants' do
expect(resolved_segments).to eq([segment_1, direct_subgroup_segment, indirect_subgroup_segment])
end
context 'with direct_descendants_only' do
let(:params) { super().merge(direct_descendants_only: true) }
it 'returns segments for given parent group and its direct descendants' do
expect(resolved_segments).to eq([segment_1, direct_subgroup_segment])
end
end
end
context 'for guests' do
before do
root_group_1.add_guest(user)
end
it 'raises ResourceNotAvailable error' do it 'raises ResourceNotAvailable error' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) expect { resolved_segments }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end end
end end
context 'as an unauthenticated user' do context 'when the feature is not available' do
let(:current_user) { nil } before do
stub_licensed_features(instance_level_devops_adoption: false)
end
it 'raises ResourceNotAvailable error' do it 'raises ResourceNotAvailable error' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) expect { resolved_segments }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end end
end end
end end
......
...@@ -28,6 +28,39 @@ RSpec.describe Analytics::DevopsAdoption::Segment, type: :model do ...@@ -28,6 +28,39 @@ RSpec.describe Analytics::DevopsAdoption::Segment, type: :model do
end end
end end
describe '.for_namespaces' do
subject(:segments) { described_class.for_namespaces(namespaces) }
let_it_be(:segment1) { create(:devops_adoption_segment) }
let_it_be(:segment2) { create(:devops_adoption_segment) }
let_it_be(:segment3) { create(:devops_adoption_segment) }
let_it_be(:namespaces) { [segment1.namespace, segment2.namespace]}
it 'selects segments for given namespaces only' do
expect(segments).to match_array([segment1, segment2])
end
end
describe '.for_parent' do
let_it_be(:group) { create :group }
let_it_be(:subgroup) { create :group, parent: group }
let_it_be(:group2) { create :group }
let_it_be(:segment1) { create(:devops_adoption_segment, namespace: group) }
let_it_be(:segment2) { create(:devops_adoption_segment, namespace: subgroup) }
let_it_be(:segment3) { create(:devops_adoption_segment, namespace: group2) }
subject(:segments) { described_class.for_parent(group) }
before do
stub_feature_flags(recursive_namespace_lookup_as_inner_join: true)
end
it 'selects segments for given namespace only' do
expect(segments).to match_array([segment1, segment2])
end
end
describe '.latest_snapshot' do describe '.latest_snapshot' do
it 'loads the latest snapshot' do it 'loads the latest snapshot' do
segment = create(:devops_adoption_segment) segment = create(:devops_adoption_segment)
......
...@@ -310,4 +310,26 @@ RSpec.describe GlobalPolicy do ...@@ -310,4 +310,26 @@ RSpec.describe GlobalPolicy do
end end
end end
end end
describe ':view_instance_devops_adoption & :manage_devops_adoption_segments', :enable_admin_mode do
let(:current_user) { admin }
context 'when license does not include the feature' do
before do
stub_licensed_features(instance_level_devops_adoption: false)
end
it { is_expected.to be_disallowed(:view_instance_devops_adoption, :manage_devops_adoption_segments) }
end
context 'when feature is enabled and license include the feature' do
it { is_expected.to be_allowed(:view_instance_devops_adoption, :manage_devops_adoption_segments) }
context 'for non-admins' do
let(:current_user) { user }
it { is_expected.to be_disallowed(:view_instance_devops_adoption, :manage_devops_adoption_segments) }
end
end
end
end end
...@@ -2,15 +2,24 @@ ...@@ -2,15 +2,24 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Mutations::Admin::Analytics::DevopsAdoption::Segments::BulkFindOrCreate do RSpec.describe Mutations::Analytics::DevopsAdoption::Segments::BulkFindOrCreate do
include GraphqlHelpers include GraphqlHelpers
let_it_be(:admin) { create(:admin) }
let_it_be(:group) { create(:group, name: 'aaaa') } let_it_be(:group) { create(:group, name: 'aaaa') }
let_it_be(:group2) { create(:group, name: 'bbbb') } let_it_be(:group2) { create(:group, name: 'bbbb') }
let_it_be(:group3) { create(:group, name: 'cccc') } let_it_be(:group3) { create(:group, name: 'cccc') }
let_it_be(:reporter) do
create(:user).tap do |u|
group.add_reporter(u)
group2.add_reporter(u)
group3.add_reporter(u)
end
end
let_it_be(:existing_segment) { create :devops_adoption_segment, namespace: group3 } let_it_be(:existing_segment) { create :devops_adoption_segment, namespace: group3 }
let(:current_user) { reporter }
let(:variables) { { namespace_ids: [group.to_gid.to_s, group2.to_gid.to_s, group3.to_gid.to_s] } } let(:variables) { { namespace_ids: [group.to_gid.to_s, group2.to_gid.to_s, group3.to_gid.to_s] } }
let(:mutation) do let(:mutation) do
...@@ -34,13 +43,25 @@ RSpec.describe Mutations::Admin::Analytics::DevopsAdoption::Segments::BulkFindOr ...@@ -34,13 +43,25 @@ RSpec.describe Mutations::Admin::Analytics::DevopsAdoption::Segments::BulkFindOr
end end
before do before do
stub_licensed_features(instance_level_devops_adoption: true) stub_licensed_features(group_level_devops_adoption: true)
end end
it_behaves_like 'DevOps Adoption top level errors' context 'when the user cannot manage segments at least for one namespace' do
let(:current_user) { create(:user).tap { |u| group.add_reporter(u) } }
it_behaves_like 'a mutation that returns a top-level access error'
end
context 'when the feature is not available' do
before do
stub_licensed_features(group_level_devops_adoption: false)
end
it_behaves_like 'a mutation that returns a top-level access error'
end
it 'creates the segment for each passed namespace or returns existing segment' do it 'creates the segment for each passed namespace or returns existing segment' do
post_graphql_mutation(mutation, current_user: admin) post_graphql_mutation(mutation, current_user: current_user)
expect(mutation_response['errors']).to be_empty expect(mutation_response['errors']).to be_empty
......
...@@ -2,11 +2,13 @@ ...@@ -2,11 +2,13 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Mutations::Admin::Analytics::DevopsAdoption::Segments::Create do RSpec.describe Mutations::Analytics::DevopsAdoption::Segments::Create do
include GraphqlHelpers include GraphqlHelpers
let_it_be(:admin) { create(:admin) }
let_it_be(:group) { create(:group, name: 'bbbb') } let_it_be(:group) { create(:group, name: 'bbbb') }
let_it_be(:reporter) { create(:user).tap { |u| group.add_reporter(u) } }
let(:current_user) { reporter }
let(:variables) { { namespace_id: group.to_gid.to_s } } let(:variables) { { namespace_id: group.to_gid.to_s } }
let(:mutation) do let(:mutation) do
...@@ -30,13 +32,25 @@ RSpec.describe Mutations::Admin::Analytics::DevopsAdoption::Segments::Create do ...@@ -30,13 +32,25 @@ RSpec.describe Mutations::Admin::Analytics::DevopsAdoption::Segments::Create do
end end
before do before do
stub_licensed_features(instance_level_devops_adoption: true) stub_licensed_features(group_level_devops_adoption: true)
end
context 'when the user cannot manage segments' do
let(:current_user) { create(:user) }
it_behaves_like 'a mutation that returns a top-level access error'
end
context 'when the feature is not available' do
before do
stub_licensed_features(group_level_devops_adoption: false)
end end
it_behaves_like 'DevOps Adoption top level errors' it_behaves_like 'a mutation that returns a top-level access error'
end
it 'creates the segment with the group' do it 'creates the segment with the group' do
post_graphql_mutation(mutation, current_user: admin) post_graphql_mutation(mutation, current_user: current_user)
expect(mutation_response['errors']).to be_empty expect(mutation_response['errors']).to be_empty
......
...@@ -2,12 +2,14 @@ ...@@ -2,12 +2,14 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Mutations::Admin::Analytics::DevopsAdoption::Segments::Delete do RSpec.describe Mutations::Analytics::DevopsAdoption::Segments::Delete do
include GraphqlHelpers include GraphqlHelpers
let_it_be(:admin) { create(:admin) } let_it_be(:group) { create :group }
let_it_be(:reporter) { create(:user).tap { |u| group.add_reporter(u) } }
let(:current_user) { reporter }
let!(:segment) { create(:devops_adoption_segment, namespace: group) }
let!(:segment) { create(:devops_adoption_segment) }
let(:variables) { { id: segment.to_gid.to_s } } let(:variables) { { id: segment.to_gid.to_s } }
let(:mutation) do let(:mutation) do
...@@ -20,17 +22,29 @@ RSpec.describe Mutations::Admin::Analytics::DevopsAdoption::Segments::Delete do ...@@ -20,17 +22,29 @@ RSpec.describe Mutations::Admin::Analytics::DevopsAdoption::Segments::Delete do
end end
before do before do
stub_licensed_features(instance_level_devops_adoption: true) stub_licensed_features(group_level_devops_adoption: true)
end end
def mutation_response def mutation_response
graphql_mutation_response(:delete_devops_adoption_segment) graphql_mutation_response(:delete_devops_adoption_segment)
end end
it_behaves_like 'DevOps Adoption top level errors' context 'when the user cannot manage segments' do
let(:current_user) { create(:user) }
it_behaves_like 'a mutation that returns a top-level access error'
end
context 'when the feature is not available' do
before do
stub_licensed_features(group_level_devops_adoption: false)
end
it_behaves_like 'a mutation that returns a top-level access error'
end
it 'deletes the segment' do it 'deletes the segment' do
post_graphql_mutation(mutation, current_user: admin) post_graphql_mutation(mutation, current_user: current_user)
expect(mutation_response['errors']).to be_empty expect(mutation_response['errors']).to be_empty
expect(::Analytics::DevopsAdoption::Segment.find_by_id(segment.id)).to eq(nil) expect(::Analytics::DevopsAdoption::Segment.find_by_id(segment.id)).to eq(nil)
...@@ -42,8 +56,13 @@ RSpec.describe Mutations::Admin::Analytics::DevopsAdoption::Segments::Delete do ...@@ -42,8 +56,13 @@ RSpec.describe Mutations::Admin::Analytics::DevopsAdoption::Segments::Delete do
let(:variables) { { id: [segment.to_gid.to_s, segment2.to_gid.to_s] } } let(:variables) { { id: [segment.to_gid.to_s, segment2.to_gid.to_s] } }
before do
segment2.namespace.add_reporter(current_user)
segment3.namespace.add_reporter(current_user)
end
it 'deletes the segments specified for deletion' do it 'deletes the segments specified for deletion' do
post_graphql_mutation(mutation, current_user: admin) post_graphql_mutation(mutation, current_user: current_user)
expect(mutation_response['errors']).to be_empty expect(mutation_response['errors']).to be_empty
expect(::Analytics::DevopsAdoption::Segment.where(id: [segment.id, segment2.id, segment3.id])) expect(::Analytics::DevopsAdoption::Segment.where(id: [segment.id, segment2.id, segment3.id]))
......
...@@ -5,19 +5,20 @@ require 'spec_helper' ...@@ -5,19 +5,20 @@ require 'spec_helper'
RSpec.describe Analytics::DevopsAdoption::Segments::BulkDeleteService do RSpec.describe Analytics::DevopsAdoption::Segments::BulkDeleteService do
include AdminModeHelper include AdminModeHelper
let_it_be(:user) { create(:user, :admin) }
let_it_be(:group) { create(:group) } let_it_be(:group) { create(:group) }
let_it_be(:admin) { create(:user, :admin) }
let(:segment) { create(:devops_adoption_segment, namespace: group) } let(:segment) { create(:devops_adoption_segment, namespace: group) }
let(:segment2) { create(:devops_adoption_segment) } let(:segment2) { create(:devops_adoption_segment) }
let(:current_user) { admin }
subject { described_class.new(segments: [segment, segment2], current_user: user).execute } subject(:response) { described_class.new(segments: [segment, segment2], current_user: current_user).execute }
before do before do
enable_admin_mode!(user) enable_admin_mode!(admin)
end end
it 'deletes the segments' do it 'deletes the segments' do
expect(subject).to be_success expect(response).to be_success
expect(segment).not_to be_persisted expect(segment).not_to be_persisted
expect(segment2).not_to be_persisted expect(segment2).not_to be_persisted
end end
...@@ -26,20 +27,23 @@ RSpec.describe Analytics::DevopsAdoption::Segments::BulkDeleteService do ...@@ -26,20 +27,23 @@ RSpec.describe Analytics::DevopsAdoption::Segments::BulkDeleteService do
it 'keeps records and returns error response' do it 'keeps records and returns error response' do
expect(segment).to receive(:destroy).and_raise(ActiveRecord::RecordNotDestroyed) expect(segment).to receive(:destroy).and_raise(ActiveRecord::RecordNotDestroyed)
expect(subject).to be_error expect(response).to be_error
expect(subject.message).to eq('Devops Adoption Segment deletion error') expect(response.message).to eq('DevOps Adoption Segment deletion error')
expect(segment).to be_persisted expect(segment).to be_persisted
expect(segment2).to be_persisted expect(segment2).to be_persisted
end end
end end
context 'for non-admins' do it 'authorizes for manage_devops_adoption' do
let_it_be(:user) { build(:user) } expect(::Ability).to receive(:allowed?)
.with(current_user, :manage_devops_adoption_segments, group)
it 'returns forbidden error' do .at_least(1)
expect do .and_return(true)
subject expect(::Ability).to receive(:allowed?)
end.to raise_error(Analytics::DevopsAdoption::Segments::AuthorizationError) .with(current_user, :manage_devops_adoption_segments, segment2.namespace)
end .at_least(1)
.and_return(true)
response
end end
end end
...@@ -3,37 +3,45 @@ ...@@ -3,37 +3,45 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Analytics::DevopsAdoption::Segments::BulkFindOrCreateService do RSpec.describe Analytics::DevopsAdoption::Segments::BulkFindOrCreateService do
include AdminModeHelper
let_it_be(:user) { create(:user, :admin) }
let_it_be(:group) { create(:group) } let_it_be(:group) { create(:group) }
let_it_be(:group2) { create(:group) } let_it_be(:group2) { create(:group) }
let_it_be(:reporter) do
create(:user).tap do |u|
group.add_reporter(u)
group2.add_reporter(u)
end
end
let_it_be(:segment) { create :devops_adoption_segment, namespace: group }
let(:current_user) { reporter }
let(:params) { { namespaces: [group, group2] } } let(:params) { { namespaces: [group, group2] } }
let!(:segment) { create :devops_adoption_segment, namespace: group }
subject { described_class.new(params: params, current_user: user).execute } subject(:response) { described_class.new(params: params, current_user: current_user).execute }
before do it 'authorizes for manage_devops_adoption' do
enable_admin_mode!(user) expect(::Ability).to receive(:allowed?)
end .with(current_user, :manage_devops_adoption_segments, group)
.at_least(1)
.and_return(true)
expect(::Ability).to receive(:allowed?)
.with(current_user, :manage_devops_adoption_segments, group2)
.at_least(1)
.and_return(true)
context 'for admins' do response
it 'returns existing segments for namespaces and creates new one if none exists' do
expect do
subject
end.to change { ::Analytics::DevopsAdoption::Segment.count }.by(1)
expect(subject.payload.fetch(:segments)).to include(segment)
end
end end
context 'for non-admins' do context 'when the user cannot manage segments at least for one namespace' do
let_it_be(:user) { build(:user) } let(:current_user) { create(:user).tap { |u| group.add_reporter(u) } }
it 'returns forbidden error' do it 'returns forbidden error' do
expect do expect { response }.to raise_error(Analytics::DevopsAdoption::Segments::AuthorizationError)
subject end
end.to raise_error(Analytics::DevopsAdoption::Segments::AuthorizationError)
end end
it 'returns existing segments for namespaces and creates new one if none exists' do
expect { response }.to change { ::Analytics::DevopsAdoption::Segment.count }.by(1)
expect(response.payload.fetch(:segments)).to include(segment)
end end
end end
...@@ -3,52 +3,39 @@ ...@@ -3,52 +3,39 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Analytics::DevopsAdoption::Segments::CreateService do RSpec.describe Analytics::DevopsAdoption::Segments::CreateService do
include AdminModeHelper
let_it_be(:user) { create(:user, :admin) }
let_it_be(:group) { create(:group) } let_it_be(:group) { create(:group) }
let_it_be(:reporter) { create(:user).tap { |u| group.add_reporter(u) } }
let(:current_user) { reporter }
let(:params) { { namespace: group } } let(:params) { { namespace: group } }
let(:segment) { subject.payload[:segment] } let(:segment) { subject.payload[:segment] }
subject { described_class.new(params: params, current_user: user).execute } subject(:response) { described_class.new(params: params, current_user: current_user).execute }
before do
enable_admin_mode!(user)
end
it 'persists the segment' do it 'persists the segment' do
expect(subject).to be_success expect(response).to be_success
expect(segment.namespace).to eq(group) expect(segment.namespace).to eq(group)
end end
it 'schedules for snapshot creation' do it 'schedules for snapshot creation' do
allow(Analytics::DevopsAdoption::CreateSnapshotWorker).to receive(:perform_async).and_call_original allow(Analytics::DevopsAdoption::CreateSnapshotWorker).to receive(:perform_async).and_call_original
subject response
expect(Analytics::DevopsAdoption::CreateSnapshotWorker).to have_received(:perform_async).with(Analytics::DevopsAdoption::Segment.last.id) expect(Analytics::DevopsAdoption::CreateSnapshotWorker).to have_received(:perform_async).with(Analytics::DevopsAdoption::Segment.last.id)
end end
context 'when namespace is not given' do it 'authorizes for manage_devops_adoption' do
before do expect(::Ability).to receive(:allowed?).with(current_user, :manage_devops_adoption_segments, group).and_return true
params.delete(:namespace)
end
it "doesn't save the segment" do response
expect(subject).to be_error
expect(subject.message).to eq('Validation error')
expect(segment.namespace).to be_nil
end
end end
context 'for non-admins' do context 'for guests' do
let_it_be(:user) { build(:user) } let(:current_user) { create(:user) }
it 'returns forbidden error' do it 'returns forbidden error' do
expect do expect { response }.to raise_error(Analytics::DevopsAdoption::Segments::AuthorizationError)
subject
end.to raise_error(Analytics::DevopsAdoption::Segments::AuthorizationError)
end end
end end
end end
...@@ -3,20 +3,15 @@ ...@@ -3,20 +3,15 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Analytics::DevopsAdoption::Segments::DeleteService do RSpec.describe Analytics::DevopsAdoption::Segments::DeleteService do
include AdminModeHelper
let_it_be(:user) { create(:user, :admin) }
let_it_be(:group) { create(:group) } let_it_be(:group) { create(:group) }
let_it_be(:reporter) { create(:user).tap { |u| group.add_reporter(u) } }
let(:segment) { create(:devops_adoption_segment, namespace: group) } let(:segment) { create(:devops_adoption_segment, namespace: group) }
let(:current_user) { reporter }
subject { described_class.new(segment: segment, current_user: user).execute } subject(:response) { described_class.new(segment: segment, current_user: current_user).execute }
before do
enable_admin_mode!(user)
end
it 'deletes the segment' do it 'deletes the segment' do
expect(subject).to be_success expect(response).to be_success
expect(segment).not_to be_persisted expect(segment).not_to be_persisted
end end
...@@ -24,18 +19,22 @@ RSpec.describe Analytics::DevopsAdoption::Segments::DeleteService do ...@@ -24,18 +19,22 @@ RSpec.describe Analytics::DevopsAdoption::Segments::DeleteService do
it 'returns error response' do it 'returns error response' do
expect(segment).to receive(:destroy).and_raise(ActiveRecord::RecordNotDestroyed) expect(segment).to receive(:destroy).and_raise(ActiveRecord::RecordNotDestroyed)
expect(subject).to be_error expect(response).to be_error
expect(subject.message).to eq('Devops Adoption Segment deletion error') expect(response.message).to eq('DevOps Adoption Segment deletion error')
end end
end end
context 'for non-admins' do it 'authorizes for manage_devops_adoption' do
let_it_be(:user) { build(:user) } expect(::Ability).to receive(:allowed?).with(current_user, :manage_devops_adoption_segments, group).and_return true
response
end
context 'when user cannot manage segments for the namespace' do
let(:current_user) { create(:user) }
it 'returns forbidden error' do it 'returns forbidden error' do
expect do expect { response }.to raise_error(Analytics::DevopsAdoption::Segments::AuthorizationError)
subject
end.to raise_error(Analytics::DevopsAdoption::Segments::AuthorizationError)
end end
end end
end end
...@@ -3,28 +3,23 @@ ...@@ -3,28 +3,23 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Analytics::DevopsAdoption::Segments::FindOrCreateService do RSpec.describe Analytics::DevopsAdoption::Segments::FindOrCreateService do
include AdminModeHelper
let_it_be(:user) { create(:user, :admin) }
let_it_be(:group) { create(:group) } let_it_be(:group) { create(:group) }
let_it_be(:reporter) { create(:user).tap { |u| group.add_reporter(u) } }
let(:current_user) { reporter }
let(:params) { { namespace: group } } let(:params) { { namespace: group } }
let(:segment) { subject.payload[:segment] }
subject { described_class.new(params: params, current_user: user).execute } subject(:response) { described_class.new(params: params, current_user: current_user).execute }
before do before do
enable_admin_mode!(user) stub_licensed_features(group_level_devops_adoption: true)
end end
context 'for admins' do
context 'when segment for given namespace already exists' do context 'when segment for given namespace already exists' do
let!(:segment) { create :devops_adoption_segment, namespace: group } let!(:segment) { create :devops_adoption_segment, namespace: group }
it 'returns existing segment' do it 'returns existing segment' do
expect do expect { response }.not_to change { Analytics::DevopsAdoption::Segment.count }
subject
end.not_to change { Analytics::DevopsAdoption::Segment.count }
expect(subject.payload.fetch(:segment)).to eq(segment) expect(subject.payload.fetch(:segment)).to eq(segment)
end end
...@@ -32,22 +27,28 @@ RSpec.describe Analytics::DevopsAdoption::Segments::FindOrCreateService do ...@@ -32,22 +27,28 @@ RSpec.describe Analytics::DevopsAdoption::Segments::FindOrCreateService do
context 'when segment for given namespace does not exist' do context 'when segment for given namespace does not exist' do
it 'calls for segment creation' do it 'calls for segment creation' do
expect_next_instance_of(Analytics::DevopsAdoption::Segments::CreateService, current_user: user, params: { namespace: group }) do |instance| expect_next_instance_of(Analytics::DevopsAdoption::Segments::CreateService, current_user: current_user, params: { namespace: group }) do |instance|
expect(instance).to receive(:execute).and_return('create_response') expect(instance).to receive(:execute).and_return('create_response')
end end
expect(subject).to eq 'create_response' expect(response).to eq 'create_response'
end end
end end
it 'authorizes for manage_devops_adoption' do
expect(::Ability).to receive(:allowed?)
.with(current_user, :manage_devops_adoption_segments, group)
.at_least(1)
.and_return(true)
response
end end
context 'for non-admins' do context 'when user cannot manage devops adoption for given namespace' do
let_it_be(:user) { build(:user) } let(:current_user) { create(:user) }
it 'returns forbidden error' do it 'returns forbidden error' do
expect do expect { response }.to raise_error(Analytics::DevopsAdoption::Segments::AuthorizationError)
subject
end.to raise_error(Analytics::DevopsAdoption::Segments::AuthorizationError)
end end
end end
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