Commit 10c0e556 authored by David Kim's avatar David Kim

Merge branch '2256-add-contract-update-mutation' into 'master'

Add customer relations contact update mutation to GraphQL

See merge request gitlab-org/gitlab!71866
parents 6514b893 36e00a53
......@@ -40,7 +40,7 @@ module Mutations
argument :description, GraphQL::Types::String,
required: false,
description: 'Description or notes for the contact.'
description: 'Description of or notes for the contact.'
authorize :admin_contact
......
# frozen_string_literal: true
module Mutations
module CustomerRelations
module Contacts
class Update < Mutations::BaseMutation
include ResolvesIds
graphql_name 'CustomerRelationsContactUpdate'
authorize :admin_contact
field :contact,
Types::CustomerRelations::ContactType,
null: true,
description: 'Contact after the mutation.'
argument :id, ::Types::GlobalIDType[::CustomerRelations::Contact],
required: true,
description: 'Global ID of the contact.'
argument :organization_id, ::Types::GlobalIDType[::CustomerRelations::Organization],
required: false,
description: 'Organization of the contact.'
argument :first_name, GraphQL::Types::String,
required: false,
description: 'First name of the contact.'
argument :last_name, GraphQL::Types::String,
required: false,
description: 'Last name of the contact.'
argument :phone, GraphQL::Types::String,
required: false,
description: 'Phone number of the contact.'
argument :email, GraphQL::Types::String,
required: false,
description: 'Email address of the contact.'
argument :description, GraphQL::Types::String,
required: false,
description: 'Description of or notes for the contact.'
def resolve(args)
contact = ::Gitlab::Graphql::Lazy.force(GitlabSchema.object_from_id(args.delete(:id), expected_type: ::CustomerRelations::Contact))
raise_resource_not_available_error! unless contact
group = contact.group
raise Gitlab::Graphql::Errors::ResourceNotAvailable, 'Feature disabled' unless Feature.enabled?(:customer_relations, group, default_enabled: :yaml)
authorize!(group)
result = ::CustomerRelations::Contacts::UpdateService.new(group: group, current_user: current_user, params: args).execute(contact)
{ contact: result.payload, errors: result.errors }
end
end
end
end
end
......@@ -31,7 +31,7 @@ module Mutations
argument :description,
GraphQL::Types::String,
required: false,
description: 'Description or notes for the organization.'
description: 'Description of or notes for the organization.'
authorize :admin_organization
......
......@@ -32,7 +32,7 @@ module Mutations
argument :description,
GraphQL::Types::String,
required: false,
description: 'Description or notes for the organization.'
description: 'Description of or notes for the organization.'
def resolve(args)
organization = ::Gitlab::Graphql::Lazy.force(GitlabSchema.object_from_id(args.delete(:id), expected_type: ::CustomerRelations::Organization))
......
......@@ -39,7 +39,7 @@ module Types
field :description,
GraphQL::Types::String,
null: true,
description: 'Description or notes for the contact.'
description: 'Description of or notes for the contact.'
field :created_at,
Types::TimeType,
......
......@@ -25,7 +25,7 @@ module Types
field :description,
GraphQL::Types::String,
null: true,
description: 'Description or notes for the organization.'
description: 'Description of or notes for the organization.'
field :created_at,
Types::TimeType,
......
......@@ -39,6 +39,7 @@ module Types
mount_mutation Mutations::CustomEmoji::Create, feature_flag: :custom_emoji
mount_mutation Mutations::CustomEmoji::Destroy, feature_flag: :custom_emoji
mount_mutation Mutations::CustomerRelations::Contacts::Create
mount_mutation Mutations::CustomerRelations::Contacts::Update
mount_mutation Mutations::CustomerRelations::Organizations::Create
mount_mutation Mutations::CustomerRelations::Organizations::Update
mount_mutation Mutations::Discussions::ToggleResolve
......
# frozen_string_literal: true
module CustomerRelations
module Contacts
class UpdateService < BaseService
def execute(contact)
return error_no_permissions unless allowed?
return error_updating(contact) unless contact.update(params)
ServiceResponse.success(payload: contact)
end
private
def error_no_permissions
error('You have insufficient permissions to update a contact for this group')
end
def error_updating(contact)
error(contact&.errors&.full_messages || 'Failed to update contact')
end
end
end
end
......@@ -1425,7 +1425,7 @@ Input type: `CustomerRelationsContactCreateInput`
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationcustomerrelationscontactcreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationcustomerrelationscontactcreatedescription"></a>`description` | [`String`](#string) | Description or notes for the contact. |
| <a id="mutationcustomerrelationscontactcreatedescription"></a>`description` | [`String`](#string) | Description of or notes for the contact. |
| <a id="mutationcustomerrelationscontactcreateemail"></a>`email` | [`String`](#string) | Email address of the contact. |
| <a id="mutationcustomerrelationscontactcreatefirstname"></a>`firstName` | [`String!`](#string) | First name of the contact. |
| <a id="mutationcustomerrelationscontactcreategroupid"></a>`groupId` | [`GroupID!`](#groupid) | Group for the contact. |
......@@ -1441,6 +1441,31 @@ Input type: `CustomerRelationsContactCreateInput`
| <a id="mutationcustomerrelationscontactcreatecontact"></a>`contact` | [`CustomerRelationsContact`](#customerrelationscontact) | Contact after the mutation. |
| <a id="mutationcustomerrelationscontactcreateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
### `Mutation.customerRelationsContactUpdate`
Input type: `CustomerRelationsContactUpdateInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationcustomerrelationscontactupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationcustomerrelationscontactupdatedescription"></a>`description` | [`String`](#string) | Description of or notes for the contact. |
| <a id="mutationcustomerrelationscontactupdateemail"></a>`email` | [`String`](#string) | Email address of the contact. |
| <a id="mutationcustomerrelationscontactupdatefirstname"></a>`firstName` | [`String`](#string) | First name of the contact. |
| <a id="mutationcustomerrelationscontactupdateid"></a>`id` | [`CustomerRelationsContactID!`](#customerrelationscontactid) | Global ID of the contact. |
| <a id="mutationcustomerrelationscontactupdatelastname"></a>`lastName` | [`String`](#string) | Last name of the contact. |
| <a id="mutationcustomerrelationscontactupdateorganizationid"></a>`organizationId` | [`CustomerRelationsOrganizationID`](#customerrelationsorganizationid) | Organization of the contact. |
| <a id="mutationcustomerrelationscontactupdatephone"></a>`phone` | [`String`](#string) | Phone number of the contact. |
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationcustomerrelationscontactupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationcustomerrelationscontactupdatecontact"></a>`contact` | [`CustomerRelationsContact`](#customerrelationscontact) | Contact after the mutation. |
| <a id="mutationcustomerrelationscontactupdateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
### `Mutation.customerRelationsOrganizationCreate`
Input type: `CustomerRelationsOrganizationCreateInput`
......@@ -1451,7 +1476,7 @@ Input type: `CustomerRelationsOrganizationCreateInput`
| ---- | ---- | ----------- |
| <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="mutationcustomerrelationsorganizationcreatedescription"></a>`description` | [`String`](#string) | Description of 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. |
......@@ -1473,7 +1498,7 @@ Input type: `CustomerRelationsOrganizationUpdateInput`
| ---- | ---- | ----------- |
| <a id="mutationcustomerrelationsorganizationupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationcustomerrelationsorganizationupdatedefaultrate"></a>`defaultRate` | [`Float`](#float) | Standard billing rate for the organization. |
| <a id="mutationcustomerrelationsorganizationupdatedescription"></a>`description` | [`String`](#string) | Description or notes for the organization. |
| <a id="mutationcustomerrelationsorganizationupdatedescription"></a>`description` | [`String`](#string) | Description of or notes for the organization. |
| <a id="mutationcustomerrelationsorganizationupdateid"></a>`id` | [`CustomerRelationsOrganizationID!`](#customerrelationsorganizationid) | Global ID of the organization. |
| <a id="mutationcustomerrelationsorganizationupdatename"></a>`name` | [`String`](#string) | Name of the organization. |
......@@ -8881,7 +8906,7 @@ A custom emoji uploaded by user.
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="customerrelationscontactcreatedat"></a>`createdAt` | [`Time!`](#time) | Timestamp the contact was created. |
| <a id="customerrelationscontactdescription"></a>`description` | [`String`](#string) | Description or notes for the contact. |
| <a id="customerrelationscontactdescription"></a>`description` | [`String`](#string) | Description of or notes for the contact. |
| <a id="customerrelationscontactemail"></a>`email` | [`String`](#string) | Email address of the contact. |
| <a id="customerrelationscontactfirstname"></a>`firstName` | [`String!`](#string) | First name of the contact. |
| <a id="customerrelationscontactid"></a>`id` | [`ID!`](#id) | Internal ID of the contact. |
......@@ -8898,7 +8923,7 @@ A custom emoji uploaded by user.
| ---- | ---- | ----------- |
| <a id="customerrelationsorganizationcreatedat"></a>`createdAt` | [`Time!`](#time) | Timestamp the organization was created. |
| <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="customerrelationsorganizationdescription"></a>`description` | [`String`](#string) | Description of 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="customerrelationsorganizationupdatedat"></a>`updatedAt` | [`Time!`](#time) | Timestamp the organization was last updated. |
......@@ -16941,6 +16966,12 @@ A `CustomEmojiID` is a global ID. It is encoded as a string.
An example `CustomEmojiID` is: `"gid://gitlab/CustomEmoji/1"`.
### `CustomerRelationsContactID`
A `CustomerRelationsContactID` is a global ID. It is encoded as a string.
An example `CustomerRelationsContactID` is: `"gid://gitlab/CustomerRelations::Contact/1"`.
### `CustomerRelationsOrganizationID`
A `CustomerRelationsOrganizationID` is a global ID. It is encoded as a string.
......
......@@ -4,8 +4,9 @@ require 'spec_helper'
RSpec.describe Mutations::CustomerRelations::Contacts::Create do
let_it_be(:user) { create(:user) }
let_it_be(:not_found_or_does_not_belong) { 'The specified organization was not found or does not belong to this group' }
let_it_be(:group) { create(:group) }
let(:not_found_or_does_not_belong) { 'The specified organization was not found or does not belong to this group' }
let(:valid_params) do
attributes_for(:contact,
group: group,
......@@ -22,8 +23,6 @@ RSpec.describe Mutations::CustomerRelations::Contacts::Create do
end
context 'when the user does not have permission' do
let_it_be(:group) { create(:group) }
before do
group.add_reporter(user)
end
......@@ -35,8 +34,6 @@ RSpec.describe Mutations::CustomerRelations::Contacts::Create do
end
context 'when the user has permission' do
let_it_be(:group) { create(:group) }
before_all do
group.add_developer(user)
end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Mutations::CustomerRelations::Contacts::Update do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let(:first_name) { 'Lionel' }
let(:last_name) { 'Smith' }
let(:email) { 'ls@gitlab.com' }
let(:description) { 'VIP' }
let(:does_not_exist_or_no_permission) { "The resource that you are attempting to access does not exist or you don't have permission to perform this action" }
let(:contact) { create(:contact, group: group) }
let(:attributes) do
{
id: contact.to_global_id,
first_name: first_name,
last_name: last_name,
email: email,
description: description
}
end
describe '#resolve' do
subject(:resolve_mutation) do
described_class.new(object: nil, context: { current_user: user }, field: nil).resolve(
attributes
)
end
context 'when the user does not have permission to update a contact' do
before do
group.add_reporter(user)
end
it 'raises an error' do
expect { resolve_mutation }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
.with_message(does_not_exist_or_no_permission)
end
end
context 'when the contact does not exist' do
it 'raises an error' do
attributes[:id] = "gid://gitlab/CustomerRelations::Contact/#{non_existing_record_id}"
expect { resolve_mutation }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
.with_message(does_not_exist_or_no_permission)
end
end
context 'when the user has permission to update a contact' do
before_all do
group.add_developer(user)
end
it 'updates the organization with correct values' do
expect(resolve_mutation[:contact]).to have_attributes(attributes)
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)
.with_message('Feature disabled')
end
end
end
end
specify { expect(described_class).to require_graphql_authorizations(:admin_contact) }
end
......@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Mutations::CustomerRelations::Organizations::Create do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let(:valid_params) do
attributes_for(:organization,
......@@ -23,8 +24,6 @@ RSpec.describe Mutations::CustomerRelations::Organizations::Create do
end
context 'when the user does not have permission' do
let_it_be(:group) { create(:group) }
before do
group.add_reporter(user)
end
......@@ -36,8 +35,6 @@ RSpec.describe Mutations::CustomerRelations::Organizations::Create do
end
context 'when the user has permission' do
let_it_be(:group) { create(:group) }
before_all do
group.add_developer(user)
end
......
......@@ -4,11 +4,12 @@ require 'spec_helper'
RSpec.describe Mutations::CustomerRelations::Organizations::Update do
let_it_be(:user) { create(:user) }
let_it_be(:name) { 'GitLab' }
let_it_be(:default_rate) { 1000.to_f }
let_it_be(:description) { 'VIP' }
let_it_be(:does_not_exist_or_no_permission) { "The resource that you are attempting to access does not exist or you don't have permission to perform this action" }
let_it_be(:group) { create(:group) }
let(:name) { 'GitLab' }
let(:default_rate) { 1000.to_f }
let(:description) { 'VIP' }
let(:does_not_exist_or_no_permission) { "The resource that you are attempting to access does not exist or you don't have permission to perform this action" }
let(:organization) { create(:organization, group: group) }
let(:attributes) do
{
......@@ -27,8 +28,6 @@ RSpec.describe Mutations::CustomerRelations::Organizations::Update do
end
context 'when the user does not have permission to update an organization' do
let_it_be(:group) { create(:group) }
before do
group.add_reporter(user)
end
......@@ -40,8 +39,6 @@ RSpec.describe Mutations::CustomerRelations::Organizations::Update do
end
context 'when the organization does not exist' do
let_it_be(:group) { create(:group) }
it 'raises an error' do
attributes[:id] = "gid://gitlab/CustomerRelations::Organization/#{non_existing_record_id}"
......@@ -51,8 +48,6 @@ RSpec.describe Mutations::CustomerRelations::Organizations::Update do
end
context 'when the user has permission to update an organization' do
let_it_be(:group) { create(:group) }
before_all do
group.add_developer(user)
end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe CustomerRelations::Contacts::UpdateService do
let_it_be(:user) { create(:user) }
let(:contact) { create(:contact, first_name: 'Mark', group: group) }
subject(:update) { described_class.new(group: group, current_user: user, params: params).execute(contact) }
describe '#execute' do
context 'when the user has no permission' do
let_it_be(:group) { create(:group) }
let(:params) { { first_name: 'Gary' } }
it 'returns an error' do
response = update
expect(response).to be_error
expect(response.message).to match_array(['You have insufficient permissions to update a contact for this group'])
end
end
context 'when user has permission' do
let_it_be(:group) { create(:group) }
before_all do
group.add_developer(user)
end
context 'when first_name is changed' do
let(:params) { { first_name: 'Gary' } }
it 'updates the contact' do
response = update
expect(response).to be_success
expect(response.payload.first_name).to eq('Gary')
end
end
context 'when the contact is invalid' do
let(:params) { { first_name: nil } }
it 'returns an error' do
response = update
expect(response).to be_error
expect(response.message).to match_array(["First name can't be blank"])
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