Commit 6247da83 authored by Bob Van Landuyt's avatar Bob Van Landuyt

Merge branch '2256-add-organization-mutation-to-graphql' into 'master'

Add Customer Relations Create Mutation to GraphQL

See merge request gitlab-org/gitlab!69472
parents 88c836c7 1273cfdb
# 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 @@ module Types
field :name,
GraphQL::Types::String,
null: true,
null: false,
description: 'Name of the organization.'
field :default_rate,
......
......@@ -33,6 +33,7 @@ module Types
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
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