Skip to content
Snippets Groups Projects
Commit 1273cfdb authored by Lee Tickett's avatar Lee Tickett Committed by Bob Van Landuyt
Browse files

Add Customer Relations Create Mutation to GraphQL

parent 97b7e052
No related branches found
No related tags found
1 merge request!69472Add Customer Relations Create Mutation to GraphQL
Showing
with 253 additions and 7 deletions
# frozen_string_literal: true
module Mutations
module CustomerRelations
module Organizations
class Create < BaseMutation
include ResolvesIds
include Gitlab::Graphql::Authorize::AuthorizeResource
graphql_name 'CustomerRelationsOrganizationCreate'
field :organization,
Types::CustomerRelations::OrganizationType,
null: true,
description: 'Organization after the mutation.'
argument :group_id, ::Types::GlobalIDType[::Group],
required: true,
description: 'Group for the organization.'
argument :name,
GraphQL::Types::String,
required: true,
description: 'Name of the organization.'
argument :default_rate,
GraphQL::Types::Float,
required: false,
description: 'Standard billing rate for the organization.'
argument :description,
GraphQL::Types::String,
required: false,
description: 'Description or notes for the organization.'
authorize :admin_organization
def resolve(args)
group = authorized_find!(id: args[:group_id])
raise Gitlab::Graphql::Errors::ResourceNotAvailable, 'Feature disabled' unless Feature.enabled?(:customer_relations, group)
result = ::CustomerRelations::Organizations::CreateService.new(group: group, current_user: current_user, params: args).execute
if result.success?
{ organization: result.payload }
else
{ errors: result.errors }
end
end
def find_object(id:)
GitlabSchema.object_from_id(id, expected_type: ::Group)
end
end
end
end
end
......@@ -14,7 +14,7 @@ class OrganizationType < BaseObject
field :name,
GraphQL::Types::String,
null: true,
null: false,
description: 'Name of the organization.'
field :default_rate,
......
......@@ -33,6 +33,7 @@ class MutationType < BaseObject
mount_mutation Mutations::Branches::Create, calls_gitaly: true
mount_mutation Mutations::Commits::Create, calls_gitaly: true
mount_mutation Mutations::CustomEmoji::Create, feature_flag: :custom_emoji
mount_mutation Mutations::CustomerRelations::Organizations::Create
mount_mutation Mutations::Discussions::ToggleResolve
mount_mutation Mutations::DependencyProxy::ImageTtlGroupPolicy::Update
mount_mutation Mutations::Environments::CanaryIngress::Update
......
......@@ -145,6 +145,7 @@ class GroupPolicy < BasePolicy
enable :read_prometheus
enable :read_package
enable :read_package_settings
enable :admin_organization
end
rule { maintainer }.policy do
......
......@@ -2,12 +2,12 @@
# Base class, scoped by container (project or group).
#
# New or existing services which only require project as a container
# should subclass BaseProjectService.
# New or existing services which only require a project or group container
# should subclass BaseProjectService or BaseGroupService.
#
# If you require a different but specific, non-polymorphic container (such
# as group), consider creating a new subclass such as BaseGroupService,
# and update the related comment at the top of the original BaseService.
# If you require a different but specific, non-polymorphic container
# consider creating a new subclass, and update the related comment at
# the top of the original BaseService.
class BaseContainerService
include BaseServiceUtility
......
# frozen_string_literal: true
# Base class, scoped by group
class BaseGroupService < ::BaseContainerService # rubocop:disable Gitlab/NamespacedClass
attr_accessor :group
def initialize(group:, current_user: nil, params: {})
super(container: group, current_user: current_user, params: params)
@group = group
end
end
......@@ -10,6 +10,7 @@
#
# - BaseContainerService for services scoped by container (project or group)
# - BaseProjectService for services scoped to projects
# - BaseGroupService for services scoped to groups
#
# or, create a new base class and update this comment.
class BaseService
......
# frozen_string_literal: true
module CustomerRelations
module Organizations
class CreateService < ::BaseGroupService
# returns the created organization
def execute
return error_no_permissions unless allowed?
params[:group_id] = group.id
organization = Organization.create(params)
return error_creating(organization) unless organization.persisted?
ServiceResponse.success(payload: organization)
end
private
def allowed?
current_user&.can?(:admin_organization, group)
end
def error(message)
ServiceResponse.error(message: message)
end
def error_no_permissions
error('You have insufficient permissions to create an organization for this group')
end
def error_creating(organization)
error(organization&.errors&.full_messages || 'Failed to create organization')
end
end
end
end
---
name: customer_relations
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/69472
rollout_issue_url:
milestone: '14.3'
type: development
group: group::product planning
default_enabled: false
......@@ -1407,6 +1407,28 @@ Input type: `CreateTestCaseInput`
| <a id="mutationcreatetestcaseerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationcreatetestcasetestcase"></a>`testCase` | [`Issue`](#issue) | Test case created. |
 
### `Mutation.customerRelationsOrganizationCreate`
Input type: `CustomerRelationsOrganizationCreateInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationcustomerrelationsorganizationcreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationcustomerrelationsorganizationcreatedefaultrate"></a>`defaultRate` | [`Float`](#float) | Standard billing rate for the organization. |
| <a id="mutationcustomerrelationsorganizationcreatedescription"></a>`description` | [`String`](#string) | Description or notes for the organization. |
| <a id="mutationcustomerrelationsorganizationcreategroupid"></a>`groupId` | [`GroupID!`](#groupid) | Group for the organization. |
| <a id="mutationcustomerrelationsorganizationcreatename"></a>`name` | [`String!`](#string) | Name of the organization. |
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationcustomerrelationsorganizationcreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationcustomerrelationsorganizationcreateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationcustomerrelationsorganizationcreateorganization"></a>`organization` | [`CustomerRelationsOrganization`](#customerrelationsorganization) | Organization after the mutation. |
### `Mutation.dastOnDemandScanCreate`
 
Input type: `DastOnDemandScanCreateInput`
......@@ -8652,7 +8674,7 @@ A custom emoji uploaded by user.
| <a id="customerrelationsorganizationdefaultrate"></a>`defaultRate` | [`Float`](#float) | Standard billing rate for the organization. |
| <a id="customerrelationsorganizationdescription"></a>`description` | [`String`](#string) | Description or notes for the organization. |
| <a id="customerrelationsorganizationid"></a>`id` | [`ID!`](#id) | Internal ID of the organization. |
| <a id="customerrelationsorganizationname"></a>`name` | [`String`](#string) | Name of the organization. |
| <a id="customerrelationsorganizationname"></a>`name` | [`String!`](#string) | Name of the organization. |
| <a id="customerrelationsorganizationupdatedat"></a>`updatedAt` | [`Time!`](#time) | Timestamp the organization was last updated. |
 
### `DastProfile`
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Mutations::CustomerRelations::Organizations::Create do
let_it_be(:user) { create(:user) }
let(:group) { create(:group) }
let(:valid_params) do
attributes_for(:organization,
group: group,
description: 'This company is super important!',
default_rate: 1_000
)
end
describe 'create organizations mutation' do
describe '#resolve' do
subject(:resolve_mutation) do
described_class.new(object: nil, context: { current_user: user }, field: nil).resolve(
**valid_params,
group_id: group.to_global_id
)
end
context 'when the user does not have permission' do
before do
group.add_guest(user)
end
it 'raises an error' do
expect { resolve_mutation }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when the user has permission' do
before do
group.add_reporter(user)
end
context 'when the feature is disabled' do
before do
stub_feature_flags(customer_relations: false)
end
it 'raises an error' do
expect { resolve_mutation }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when the params are invalid' do
before do
valid_params[:name] = nil
end
it 'returns the validation error' do
expect(resolve_mutation[:errors]).to eq(["Name can't be blank"])
end
end
context 'when the user has permission to create an organization' do
it 'creates organization with correct values' do
expect(resolve_mutation[:organization]).to have_attributes(valid_params)
end
end
end
end
end
specify { expect(described_class).to require_graphql_authorizations(:admin_organization) }
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe CustomerRelations::Organizations::CreateService do
describe '#execute' do
let_it_be(:user) { create(:user) }
let(:group) { create(:group) }
let(:params) { attributes_for(:organization, group: group) }
subject(:response) { described_class.new(group: group, current_user: user, params: params).execute }
it 'creates an organization' do
group.add_reporter(user)
expect(response).to be_success
end
it 'returns an error when user does not have permission' do
expect(response).to be_error
expect(response.message).to eq('You have insufficient permissions to create an organization for this group')
end
it 'returns an error when the organization is not persisted' do
group.add_reporter(user)
params[:name] = nil
expect(response).to be_error
expect(response.message).to eq(["Name can't be blank"])
end
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment