Commit 227c3221 authored by Lee Tickett's avatar Lee Tickett Committed by Vitali Tatarintev

Support CRM contacts only in root groups

To reduce complexity, it has been decided to only support
CRM contacts and organizations in root groups for now.

This MR ensures the CRM feature can only be enabled for
root groups, and updates validation to only check root
groups rather than the entire hierarchy.

Changelog: fixed
parent 4e1397b1
......@@ -17,10 +17,6 @@ module IssuableActions
def show
respond_to do |format|
format.html do
@show_crm_contacts = issuable.is_a?(Issue) && # rubocop:disable Gitlab/ModuleWithInstanceVariables
can?(current_user, :read_crm_contact, issuable.project.group) &&
CustomerRelations::Contact.exists_for_group?(issuable.project.group)
@issuable_sidebar = serializer.represent(issuable, serializer: 'sidebar') # rubocop:disable Gitlab/ModuleWithInstanceVariables
render 'show'
end
......
......@@ -82,6 +82,10 @@ class Groups::ApplicationController < ApplicationController
def has_project_list?
false
end
def validate_root_group!
render_404 unless group.root?
end
end
Groups::ApplicationController.prepend_mod_with('Groups::ApplicationController')
......@@ -3,6 +3,7 @@
class Groups::Crm::ContactsController < Groups::ApplicationController
feature_category :team_planning
before_action :validate_root_group!
before_action :authorize_read_crm_contact!
def new
......
......@@ -3,6 +3,7 @@
class Groups::Crm::OrganizationsController < Groups::ApplicationController
feature_category :team_planning
before_action :validate_root_group!
before_action :authorize_read_crm_organization!
def new
......
......@@ -2,7 +2,7 @@
module Groups
module CrmSettingsHelper
def crm_feature_flag_enabled?(group)
def crm_feature_available?(group)
Feature.enabled?(:customer_relations, group)
end
end
......
......@@ -23,8 +23,9 @@ class CustomerRelations::Contact < ApplicationRecord
validates :last_name, presence: true, length: { maximum: 255 }
validates :email, length: { maximum: 255 }
validates :description, length: { maximum: 1024 }
validates :email, uniqueness: { scope: :group_id }
validate :validate_email_format
validate :unique_email_for_group_hierarchy
validate :validate_root_group
def self.reference_prefix
'[contact:'
......@@ -41,14 +42,13 @@ class CustomerRelations::Contact < ApplicationRecord
def self.find_ids_by_emails(group, emails)
raise ArgumentError, "Cannot lookup more than #{MAX_PLUCK} emails" if emails.length > MAX_PLUCK
where(group_id: group.self_and_ancestor_ids, email: emails)
.pluck(:id)
where(group: group, email: emails).pluck(:id)
end
def self.exists_for_group?(group)
return false unless group
exists?(group_id: group.self_and_ancestor_ids)
exists?(group: group)
end
private
......@@ -59,13 +59,9 @@ class CustomerRelations::Contact < ApplicationRecord
self.errors.add(:email, I18n.t(:invalid, scope: 'valid_email.validations.email')) unless ValidateEmail.valid?(self.email)
end
def unique_email_for_group_hierarchy
return unless group
return unless email
def validate_root_group
return if group&.root?
duplicate_email_exists = CustomerRelations::Contact
.where(group_id: group.self_and_hierarchy.pluck(:id), email: email)
.where.not(id: id).exists?
self.errors.add(:email, _('contact with same email already exists in group hierarchy')) if duplicate_email_exists
self.errors.add(:base, _('contacts can only be added to root groups'))
end
end
......@@ -6,7 +6,7 @@ class CustomerRelations::IssueContact < ApplicationRecord
belongs_to :issue, optional: false, inverse_of: :customer_relations_contacts
belongs_to :contact, optional: false, inverse_of: :issue_contacts
validate :contact_belongs_to_issue_group_or_ancestor
validate :contact_belongs_to_root_group
def self.find_contact_ids_by_emails(issue_id, emails)
raise ArgumentError, "Cannot lookup more than #{MAX_PLUCK} emails" if emails.length > MAX_PLUCK
......@@ -24,11 +24,11 @@ class CustomerRelations::IssueContact < ApplicationRecord
private
def contact_belongs_to_issue_group_or_ancestor
def contact_belongs_to_root_group
return unless contact&.group_id
return unless issue&.project&.namespace_id
return if issue.project.group&.self_and_ancestor_ids&.include?(contact.group_id)
return if issue.project.root_ancestor&.id == contact.group_id
errors.add(:base, _('The contact does not belong to the issue group or an ancestor'))
errors.add(:base, _("The contact does not belong to the issue group's root ancestor"))
end
end
......@@ -19,9 +19,18 @@ class CustomerRelations::Organization < ApplicationRecord
validates :name, uniqueness: { case_sensitive: false, scope: [:group_id] }
validates :name, length: { maximum: 255 }
validates :description, length: { maximum: 1024 }
validate :validate_root_group
def self.find_by_name(group_id, name)
where(group: group_id)
.where('LOWER(name) = LOWER(?)', name)
end
private
def validate_root_group
return if group&.root?
self.errors.add(:base, _('organizations can only be added to root groups'))
end
end
......@@ -13,7 +13,7 @@ class IssuePolicy < IssuablePolicy
end
desc "User can read contacts belonging to the issue group"
condition(:can_read_crm_contacts, scope: :subject) { @user.can?(:read_crm_contact, @subject.project.group) }
condition(:can_read_crm_contacts, scope: :subject) { @user.can?(:read_crm_contact, @subject.project.root_ancestor) }
desc "Issue is confidential"
condition(:confidential, scope: :subject) { @subject.confidential? }
......
......@@ -10,6 +10,11 @@ class IssueSidebarBasicEntity < IssuableSidebarBasicEntity
can?(current_user, :update_escalation_status, issue.project)
end
end
expose :show_crm_contacts do |issuable|
current_user&.can?(:read_crm_contact, issuable.project.root_ancestor) &&
CustomerRelations::Contact.exists_for_group?(issuable.project.root_ancestor)
end
end
IssueSidebarBasicEntity.prepend_mod_with('IssueSidebarBasicEntity')
......@@ -52,7 +52,7 @@ module Issues
end
def add_by_email
contact_ids = ::CustomerRelations::Contact.find_ids_by_emails(project_group, emails(:add_emails))
contact_ids = ::CustomerRelations::Contact.find_ids_by_emails(project_group.root_ancestor, emails(:add_emails))
add_by_id(contact_ids)
end
......
......@@ -43,7 +43,7 @@
= render_if_exists 'groups/personal_access_token_expiration_policy', f: f, group: @group
= render 'groups/settings/membership', f: f, group: @group
- if crm_feature_flag_enabled?(@group)
- if crm_feature_available?(@group)
%h5= _('Customer relations')
.form-group.gl-mb-3
= f.gitlab_ui_checkbox_component :crm_enabled,
......
......@@ -41,7 +41,7 @@
.block{ class: 'gl-pt-0! gl-collapse-empty', data: { qa_selector: 'iteration_container', testid: 'iteration_container' } }<
= render_if_exists 'shared/issuable/iteration_select', can_edit: can_edit_issuable.to_s, group_path: @project.group.full_path, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid], issuable_type: issuable_type
- if @show_crm_contacts
- if issuable_sidebar[:show_crm_contacts]
.block.contact
#js-issue-crm-contacts{ data: { issue_id: issuable_sidebar[:id] } }
......
......@@ -6,15 +6,18 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Customer relations management (CRM) **(FREE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/2256) in GitLab 14.6 [with a flag](../../administration/feature_flags.md) named `customer_relations`. Disabled by default.
FLAG:
On self-managed GitLab, by default this feature is not available. To make it available, ask an administrator to [enable the feature flag](../../administration/feature_flags.md) named `customer_relations`.
On GitLab.com, this feature is not available.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/2256) in GitLab 14.6 [with a flag](../../administration/feature_flags.md) named `customer_relations`. Disabled by default.
> - In GitLab 14.8 and later, you can [create contacts and organizations only in root groups](https://gitlab.com/gitlab-org/gitlab/-/issues/350634).
With customer relations management (CRM) you can create a record of contacts
(individuals) and organizations (companies) and relate them to issues.
Contacts and organizations can only be created for root groups.
You can use contacts and organizations to tie work to customers for billing and reporting purposes.
To read more about what is planned for the future, see [issue 2256](https://gitlab.com/gitlab-org/gitlab/-/issues/2256).
......
......@@ -203,6 +203,18 @@ RSpec.describe 'Group navbar' do
it_behaves_like 'verified navigation bar'
end
context 'when customer relations feature and flag is enabled but subgroup' do
let(:group) { create(:group, :crm_enabled, parent: create(:group)) }
before do
stub_feature_flags(customer_relations: true)
visit group_path(group)
end
it_behaves_like 'verified navigation bar'
end
end
context 'when iterations are available' do
......
......@@ -291,7 +291,7 @@ module Gitlab
types Issue
condition do
current_user.can?(:set_issue_crm_contacts, quick_action_target) &&
CustomerRelations::Contact.exists_for_group?(quick_action_target.project.group)
CustomerRelations::Contact.exists_for_group?(quick_action_target.project.root_ancestor)
end
execution_message do
_('One or more contacts were successfully added.')
......@@ -306,7 +306,7 @@ module Gitlab
types Issue
condition do
current_user.can?(:set_issue_crm_contacts, quick_action_target) &&
CustomerRelations::Contact.exists_for_group?(quick_action_target.project.group)
CustomerRelations::Contact.exists_for_group?(quick_action_target.project.root_ancestor)
end
execution_message do
_('One or more contacts were successfully removed.')
......
......@@ -24,6 +24,8 @@ module Sidebars
override :render?
def render?
return false unless context.group.root?
can_read_contact? || can_read_organization?
end
......
......@@ -36526,7 +36526,7 @@ msgstr ""
msgid "The connection will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
msgid "The contact does not belong to the issue group or an ancestor"
msgid "The contact does not belong to the issue group's root ancestor"
msgstr ""
msgid "The content editor may change the markdown formatting style of the document, which may not match your original markdown style."
......@@ -43315,7 +43315,7 @@ msgstr ""
msgid "compliance violation has already been recorded"
msgstr ""
msgid "contact with same email already exists in group hierarchy"
msgid "contacts can only be added to root groups"
msgstr ""
msgid "container registry images"
......@@ -44225,6 +44225,9 @@ msgstr ""
msgid "or"
msgstr ""
msgid "organizations can only be added to root groups"
msgstr ""
msgid "other card matches"
msgstr ""
......
......@@ -21,7 +21,7 @@ FactoryBot.define do
trait :for_issue do
issue { raise ArgumentError, '`issue` is manadatory' }
contact { association(:contact, group: issue.project.group) }
contact { association(:contact, group: issue.project.root_ancestor) }
end
end
end
......@@ -59,6 +59,18 @@ RSpec.describe 'Group navbar' do
it_behaves_like 'verified navigation bar'
end
context 'when customer_relations feature and flag is enabled but subgroup' do
let(:group) { create(:group, :crm_enabled, parent: create(:group)) }
before do
stub_feature_flags(customer_relations: true)
visit group_path(group)
end
it_behaves_like 'verified navigation bar'
end
context 'when dependency proxy is available' do
before do
stub_config(dependency_proxy: { enabled: true })
......
......@@ -3,13 +3,16 @@
require 'spec_helper'
RSpec.describe Groups::CrmSettingsHelper do
let_it_be(:group) { create(:group) }
let_it_be(:root_group) { create(:group) }
describe '#crm_feature_flag_enabled?' do
describe '#crm_feature_available?' do
subject do
helper.crm_feature_flag_enabled?(group)
helper.crm_feature_available?(group)
end
context 'in root group' do
let(:group) { root_group }
context 'when feature flag is enabled' do
it { is_expected.to be_truthy }
end
......@@ -22,4 +25,23 @@ RSpec.describe Groups::CrmSettingsHelper do
it { is_expected.to be_falsy }
end
end
context 'in subgroup' do
let_it_be(:subgroup) { create(:group, parent: root_group) }
let(:group) { subgroup }
context 'when feature flag is enabled' do
it { is_expected.to be_truthy }
end
context 'when feature flag is disabled' do
before do
stub_feature_flags(customer_relations: false)
end
it { is_expected.to be_falsy }
end
end
end
end
......@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe CustomerRelations::Contact, type: :model do
let_it_be(:group) { create(:group) }
describe 'associations' do
it { is_expected.to belong_to(:group) }
it { is_expected.to belong_to(:organization).optional }
......@@ -23,6 +25,8 @@ RSpec.describe CustomerRelations::Contact, type: :model do
it { is_expected.to validate_length_of(:email).is_at_most(255) }
it { is_expected.to validate_length_of(:description).is_at_most(1024) }
it { is_expected.to validate_uniqueness_of(:email).scoped_to(:group_id) }
it_behaves_like 'an object with RFC3696 compliant email-formatted attributes', :email
end
......@@ -38,33 +42,15 @@ RSpec.describe CustomerRelations::Contact, type: :model do
it { expect(described_class.reference_postfix).to eq(']') }
end
describe '#unique_email_for_group_hierarchy' do
let_it_be(:parent) { create(:group) }
let_it_be(:group) { create(:group, parent: parent) }
let_it_be(:subgroup) { create(:group, parent: group) }
let_it_be(:existing_contact) { create(:contact, group: group) }
context 'with unique email for group hierarchy' do
describe '#root_group' do
context 'when root group' do
subject { build(:contact, group: group) }
it { is_expected.to be_valid }
end
context 'with duplicate email in group' do
subject { build(:contact, email: existing_contact.email, group: group) }
it { is_expected.to be_invalid }
end
context 'with duplicate email in parent group' do
subject { build(:contact, email: existing_contact.email, group: subgroup) }
it { is_expected.to be_invalid }
end
context 'with duplicate email in subgroup' do
subject { build(:contact, email: existing_contact.email, group: parent) }
context 'when subgroup' do
subject { build(:contact, group: create(:group, parent: group)) }
it { is_expected.to be_invalid }
end
......@@ -82,7 +68,6 @@ RSpec.describe CustomerRelations::Contact, type: :model do
end
describe '#self.find_ids_by_emails' do
let_it_be(:group) { create(:group) }
let_it_be(:group_contacts) { create_list(:contact, 2, group: group) }
let_it_be(:other_contacts) { create_list(:contact, 2) }
......@@ -92,13 +77,6 @@ RSpec.describe CustomerRelations::Contact, type: :model do
expect(contact_ids).to match_array(group_contacts.pluck(:id))
end
it 'returns ids of contacts from parent group' do
subgroup = create(:group, parent: group)
contact_ids = described_class.find_ids_by_emails(subgroup, group_contacts.pluck(:email))
expect(contact_ids).to match_array(group_contacts.pluck(:id))
end
it 'does not return ids of contacts from other groups' do
contact_ids = described_class.find_ids_by_emails(group, other_contacts.pluck(:email))
......@@ -112,28 +90,17 @@ RSpec.describe CustomerRelations::Contact, type: :model do
end
describe '#self.exists_for_group?' do
let(:group) { create(:group) }
let(:subgroup) { create(:group, parent: group) }
context 'with no contacts in group or parent' do
context 'with no contacts in group' do
it 'returns false' do
expect(described_class.exists_for_group?(subgroup)).to be_falsey
expect(described_class.exists_for_group?(group)).to be_falsey
end
end
context 'with contacts in group' do
it 'returns true' do
create(:contact, group: subgroup)
expect(described_class.exists_for_group?(subgroup)).to be_truthy
end
end
context 'with contacts in parent' do
it 'returns true' do
create(:contact, group: group)
expect(described_class.exists_for_group?(subgroup)).to be_truthy
expect(described_class.exists_for_group?(group)).to be_truthy
end
end
end
......
......@@ -6,7 +6,8 @@ RSpec.describe CustomerRelations::IssueContact do
let_it_be(:issue_contact, reload: true) { create(:issue_customer_relations_contact) }
let_it_be(:group) { create(:group) }
let_it_be(:subgroup) { create(:group, parent: group) }
let_it_be(:project) { create(:project, group: subgroup) }
let_it_be(:project) { create(:project, group: group) }
let_it_be(:subgroup_project) { create(:project, group: subgroup) }
let_it_be(:issue) { create(:issue, project: project) }
subject { issue_contact }
......@@ -27,33 +28,36 @@ RSpec.describe CustomerRelations::IssueContact do
let(:for_issue) { build(:issue_customer_relations_contact, :for_issue, issue: issue) }
let(:for_contact) { build(:issue_customer_relations_contact, :for_contact, contact: contact) }
context 'for root groups' do
it 'uses objects from the same group', :aggregate_failures do
expect(stubbed.contact.group).to eq(stubbed.issue.project.group)
expect(built.contact.group).to eq(built.issue.project.group)
expect(created.contact.group).to eq(created.issue.project.group)
end
end
it 'builds using the same group', :aggregate_failures do
expect(for_issue.contact.group).to eq(subgroup)
expect(for_contact.issue.project.group).to eq(group)
context 'for subgroups' do
it 'builds using the root ancestor' do
expect(for_issue.contact.group).to eq(group)
end
end
end
describe 'validation' do
it 'fails when the contact group does not belong to the issue group or ancestors' do
it 'fails when the contact group is unrelated to the issue group' do
built = build(:issue_customer_relations_contact, issue: create(:issue), contact: create(:contact))
expect(built).not_to be_valid
end
it 'succeeds when the contact group is the same as the issue group' do
built = build(:issue_customer_relations_contact, issue: create(:issue, project: project), contact: create(:contact, group: subgroup))
it 'succeeds when the contact belongs to a root group and is the same as the issue group' do
built = build(:issue_customer_relations_contact, issue: create(:issue, project: project), contact: create(:contact, group: group))
expect(built).to be_valid
end
it 'succeeds when the contact group is an ancestor of the issue group' do
built = build(:issue_customer_relations_contact, issue: create(:issue, project: project), contact: create(:contact, group: group))
it 'succeeds when the contact belongs to a root group and it is an ancestor of the issue group' do
built = build(:issue_customer_relations_contact, issue: create(:issue, project: subgroup_project), contact: create(:contact, group: group))
expect(built).to be_valid
end
......
......@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe CustomerRelations::Organization, type: :model do
let_it_be(:group) { create(:group) }
describe 'associations' do
it { is_expected.to belong_to(:group).with_foreign_key('group_id') }
end
......@@ -17,6 +19,20 @@ RSpec.describe CustomerRelations::Organization, type: :model do
it { is_expected.to validate_length_of(:description).is_at_most(1024) }
end
describe '#root_group' do
context 'when root group' do
subject { build(:organization, group: group) }
it { is_expected.to be_valid }
end
context 'when subgroup' do
subject { build(:organization, group: create(:group, parent: group)) }
it { is_expected.to be_invalid }
end
end
describe '#name' do
it 'strips name' do
organization = described_class.new(name: ' GitLab ')
......@@ -27,7 +43,6 @@ RSpec.describe CustomerRelations::Organization, type: :model do
end
describe '#find_by_name' do
let!(:group) { create(:group) }
let!(:organiztion1) { create(:organization, group: group, name: 'Test') }
let!(:organiztion2) { create(:organization, group: create(:group), name: 'Test') }
......
......@@ -2883,7 +2883,14 @@ RSpec.describe Group do
expect(group.crm_enabled?).to be_truthy
end
it 'returns true where crm_settings.state is enabled for subgroup' do
subgroup = create(:group, :crm_enabled, parent: group)
expect(subgroup.crm_enabled?).to be_truthy
end
end
describe '.get_ids_by_ids_or_paths' do
let(:group_path) { 'group_path' }
let!(:group) { create(:group, path: group_path) }
......
......@@ -1167,7 +1167,6 @@ RSpec.describe Issue do
end
describe '#check_for_spam?' do
using RSpec::Parameterized::TableSyntax
let_it_be(:support_bot) { ::User.support_bot }
where(:support_bot?, :visibility_level, :confidential, :new_attributes, :check_for_spam?) do
......
......@@ -396,4 +396,36 @@ RSpec.describe IssuePolicy do
expect(policies).to be_allowed(:read_issue_iid)
end
end
describe 'set_issue_crm_contacts' do
let(:user) { create(:user) }
let(:subgroup) { create(:group, :crm_enabled, parent: create(:group, :crm_enabled)) }
let(:project) { create(:project, group: subgroup) }
let(:issue) { create(:issue, project: project) }
let(:policies) { described_class.new(user, issue) }
context 'when project reporter' do
it 'is disallowed' do
project.add_reporter(user)
expect(policies).to be_disallowed(:set_issue_crm_contacts)
end
end
context 'when subgroup reporter' do
it 'is allowed' do
subgroup.add_reporter(user)
expect(policies).to be_disallowed(:set_issue_crm_contacts)
end
end
context 'when root group reporter' do
it 'is allowed' do
subgroup.parent.add_reporter(user)
expect(policies).to be_allowed(:set_issue_crm_contacts)
end
end
end
end
......@@ -9,12 +9,10 @@ RSpec.describe 'Setting issues crm contacts' do
let_it_be(:group) { create(:group, :crm_enabled) }
let_it_be(:subgroup) { create(:group, :crm_enabled, parent: group) }
let_it_be(:project) { create(:project, group: subgroup) }
let_it_be(:group_contacts) { create_list(:contact, 4, group: group) }
let_it_be(:subgroup_contacts) { create_list(:contact, 4, group: subgroup) }
let_it_be(:contacts) { create_list(:contact, 4, group: group) }
let(:issue) { create(:issue, project: project) }
let(:operation_mode) { Types::MutationOperationModeEnum.default_mode }
let(:contacts) { subgroup_contacts }
let(:initial_contacts) { contacts[0..1] }
let(:mutation_contacts) { contacts[1..2] }
let(:contact_ids) { contact_global_ids(mutation_contacts) }
......@@ -116,15 +114,7 @@ RSpec.describe 'Setting issues crm contacts' do
end
end
context 'with issue group contacts' do
let(:contacts) { subgroup_contacts }
it_behaves_like 'successful mutation'
end
context 'with issue ancestor group contacts' do
it_behaves_like 'successful mutation'
end
context 'when the contact does not exist' do
let(:contact_ids) { ["gid://gitlab/CustomerRelations::Contact/#{non_existing_record_id}"] }
......
......@@ -49,6 +49,12 @@ RSpec.describe Groups::Crm::ContactsController do
it_behaves_like 'response with 404 status'
end
context 'when subgroup' do
let(:group) { create(:group, :private, :crm_enabled, parent: create(:group)) }
it_behaves_like 'response with 404 status'
end
end
context 'with unauthorized user' do
......
......@@ -49,6 +49,12 @@ RSpec.describe Groups::Crm::OrganizationsController do
it_behaves_like 'response with 404 status'
end
context 'when subgroup' do
let(:group) { create(:group, :private, :crm_enabled, parent: create(:group)) }
it_behaves_like 'response with 404 status'
end
end
context 'with unauthorized user' do
......
......@@ -3,9 +3,10 @@
require 'spec_helper'
RSpec.describe IssueSidebarBasicEntity do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:group) { create(:group, :crm_enabled) }
let_it_be(:project) { create(:project, :repository, group: group) }
let_it_be(:user) { create(:user, developer_projects: [project]) }
let_it_be(:issue) { create(:issue, project: project, assignees: [user]) }
let_it_be_with_reload(:issue) { create(:issue, project: project, assignees: [user]) }
let(:serializer) { IssueSerializer.new(current_user: user, project: project) }
......@@ -71,4 +72,27 @@ RSpec.describe IssueSidebarBasicEntity do
end
end
end
describe 'show_crm_contacts' do
using RSpec::Parameterized::TableSyntax
where(:is_reporter, :contacts_exist_for_group, :expected) do
false | false | false
false | true | false
true | false | false
true | true | true
end
with_them do
it 'sets proper boolean value for show_crm_contacts' do
allow(CustomerRelations::Contact).to receive(:exists_for_group?).with(group).and_return(contacts_exist_for_group)
if is_reporter
project.root_ancestor.add_reporter(user)
end
expect(entity[:show_crm_contacts]).to be(expected)
end
end
end
end
......@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Issues::SetCrmContactsService do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group, :crm_enabled) }
let_it_be(:project) { create(:project, group: group) }
let_it_be(:project) { create(:project, group: create(:group, parent: group)) }
let_it_be(:contacts) { create_list(:contact, 4, group: group) }
let_it_be(:issue, reload: true) { create(:issue, project: project) }
let_it_be(:issue_contact_1) do
......
......@@ -203,7 +203,7 @@ RSpec.shared_context 'group navbar structure' do
nav_sub_items: []
},
{
nav_item: _('Group information'),
nav_item: group.root? ? _('Group information') : _('Subgroup information'),
nav_sub_items: [
_('Activity'),
_('Labels'),
......
......@@ -11,7 +11,7 @@ RSpec.describe 'shared/issuable/_sidebar.html.haml' do
end
context 'project in a group' do
let_it_be(:group) { create(:group) }
let_it_be(:group) { create(:group, :crm_enabled) }
let_it_be(:project) { create(:project, group: group) }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:incident) { create(:incident, project: project) }
......@@ -35,5 +35,34 @@ RSpec.describe 'shared/issuable/_sidebar.html.haml' do
expect(rendered).not_to have_css('[data-testid="escalation_status_container"]')
end
end
context 'crm contacts widget' do
let(:issuable) { issue }
context 'without permission' do
it 'is expected not to be shown' do
create(:contact, group: group)
expect(rendered).not_to have_css('#js-issue-crm-contacts')
end
end
context 'without contacts' do
it 'is expected not to be shown' do
group.add_developer(user)
expect(rendered).not_to have_css('#js-issue-crm-contacts')
end
end
context 'with permission and contacts' do
it 'is expected to be shown' do
create(:contact, group: group)
group.add_developer(user)
expect(rendered).to have_css('#js-issue-crm-contacts')
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