Commit 3e386174 authored by Robert May's avatar Robert May

Merge branch '350133-contact-autocomplete' into 'master'

Add endpoint for contacts autocomplete

See merge request gitlab-org/gitlab!78469
parents db56cb3b 763492b1
...@@ -2,8 +2,9 @@ ...@@ -2,8 +2,9 @@
class Projects::AutocompleteSourcesController < Projects::ApplicationController class Projects::AutocompleteSourcesController < Projects::ApplicationController
before_action :authorize_read_milestone!, only: :milestones before_action :authorize_read_milestone!, only: :milestones
before_action :authorize_read_crm_contact!, only: :contacts
feature_category :team_planning, [:issues, :labels, :milestones, :commands] feature_category :team_planning, [:issues, :labels, :milestones, :commands, :contacts]
feature_category :code_review, [:merge_requests] feature_category :code_review, [:merge_requests]
feature_category :users, [:members] feature_category :users, [:members]
feature_category :snippets, [:snippets] feature_category :snippets, [:snippets]
...@@ -38,6 +39,10 @@ class Projects::AutocompleteSourcesController < Projects::ApplicationController ...@@ -38,6 +39,10 @@ class Projects::AutocompleteSourcesController < Projects::ApplicationController
render json: autocomplete_service.snippets render json: autocomplete_service.snippets
end end
def contacts
render json: autocomplete_service.contacts
end
private private
def autocomplete_service def autocomplete_service
...@@ -49,6 +54,10 @@ class Projects::AutocompleteSourcesController < Projects::ApplicationController ...@@ -49,6 +54,10 @@ class Projects::AutocompleteSourcesController < Projects::ApplicationController
.new(project, current_user) .new(project, current_user)
.execute(params[:type], params[:type_id]) .execute(params[:type], params[:type_id])
end end
def authorize_read_crm_contact!
render_404 unless can?(current_user, :read_crm_contact, project.root_ancestor)
end
end end
Projects::AutocompleteSourcesController.prepend_mod_with('Projects::AutocompleteSourcesController') Projects::AutocompleteSourcesController.prepend_mod_with('Projects::AutocompleteSourcesController')
# frozen_string_literal: true
# Finder for retrieving contacts scoped to a group
#
# Arguments:
# current_user - user performing the action. Must have the correct permission level for the group.
# params:
# group: Group, required
module Crm
class ContactsFinder
include Gitlab::Allowable
include Gitlab::Utils::StrongMemoize
attr_reader :params, :current_user
def initialize(current_user, params = {})
@current_user = current_user
@params = params
end
def execute
return CustomerRelations::Contact.none unless root_group
root_group.contacts
end
private
def root_group
strong_memoize(:root_group) do
group = params[:group]&.root_ancestor
next unless can?(@current_user, :read_crm_contact, group)
group
end
end
end
end
...@@ -33,6 +33,11 @@ module Projects ...@@ -33,6 +33,11 @@ module Projects
SnippetsFinder.new(current_user, project: project).execute.select([:id, :title]) SnippetsFinder.new(current_user, project: project).execute.select([:id, :title])
end end
def contacts
Crm::ContactsFinder.new(current_user, group: project.group).execute
.select([:id, :email, :first_name, :last_name])
end
def labels_as_hash(target) def labels_as_hash(target)
super(target, project_id: project.id, include_ancestor_groups: true) super(target, project_id: project.id, include_ancestor_groups: true)
end end
......
...@@ -158,6 +158,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do ...@@ -158,6 +158,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get 'milestones' get 'milestones'
get 'commands' get 'commands'
get 'snippets' get 'snippets'
get 'contacts'
end end
end end
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Projects::AutocompleteSourcesController do RSpec.describe Projects::AutocompleteSourcesController do
let_it_be(:group) { create(:group) } let_it_be(:group, reload: true) { create(:group) }
let_it_be(:project) { create(:project, namespace: group) } let_it_be(:project) { create(:project, namespace: group) }
let_it_be(:issue) { create(:issue, project: project) } let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
...@@ -69,4 +69,62 @@ RSpec.describe Projects::AutocompleteSourcesController do ...@@ -69,4 +69,62 @@ RSpec.describe Projects::AutocompleteSourcesController do
end end
end end
end end
describe 'GET contacts' do
let_it_be(:contact_1) { create(:contact, group: group) }
let_it_be(:contact_2) { create(:contact, group: group) }
before do
sign_in(user)
end
context 'when feature flag is enabled' do
context 'when a group has contact relations enabled' do
before do
create(:crm_settings, group: group, enabled: true)
end
context 'when a user can read contacts' do
it 'lists contacts' do
group.add_developer(user)
get :contacts, format: :json, params: { namespace_id: group.path, project_id: project.path }
emails = json_response.map { |contact_data| contact_data["email"] }
expect(emails).to match_array([contact_1.email, contact_2.email])
end
end
context 'when a user can not read contacts' do
it 'renders 404' do
get :contacts, format: :json, params: { namespace_id: group.path, project_id: project.path }
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
context 'when a group has contact relations disabled' do
it 'renders 404' do
group.add_developer(user)
get :contacts, format: :json, params: { namespace_id: group.path, project_id: project.path }
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
context 'when feature flag is disabled' do
before do
stub_feature_flags(customer_relations: false)
end
it 'renders 404' do
get :contacts, format: :json, params: { namespace_id: group.path, project_id: project.path }
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
end end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Crm::ContactsFinder do
let_it_be(:user) { create(:user) }
describe '#execute' do
subject { described_class.new(user, group: group).execute }
context 'when customer relations feature is enabled for the group' do
let_it_be(:root_group) { create(:group, :crm_enabled) }
let_it_be(:group) { create(:group, parent: root_group) }
let_it_be(:contact_1) { create(:contact, group: root_group) }
let_it_be(:contact_2) { create(:contact, group: root_group) }
context 'when user does not have permissions to see contacts in the group' do
it 'returns an empty array' do
expect(subject).to be_empty
end
end
context 'when user is member of the root group' do
before do
root_group.add_developer(user)
end
context 'when feature flag is disabled' do
before do
stub_feature_flags(customer_relations: false)
end
it 'returns an empty array' do
expect(subject).to be_empty
end
end
context 'when feature flag is enabled' do
it 'returns all group contacts' do
expect(subject).to match_array([contact_1, contact_2])
end
end
end
context 'when user is member of the sub group' do
before do
group.add_developer(user)
end
it 'returns an empty array' do
expect(subject).to be_empty
end
end
end
context 'when customer relations feature is disabled for the group' do
let_it_be(:group) { create(:group) }
let_it_be(:contact) { create(:contact, group: group) }
before do
group.add_developer(user)
end
it 'returns an empty array' do
expect(subject).to be_empty
end
end
end
end
...@@ -148,6 +148,32 @@ RSpec.describe Projects::AutocompleteService do ...@@ -148,6 +148,32 @@ RSpec.describe Projects::AutocompleteService do
end end
end end
describe '#contacts' 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(:contact_1) { create(:contact, group: group) }
let_it_be(:contact_2) { create(:contact, group: group) }
subject { described_class.new(project, user).contacts.as_json }
before do
stub_feature_flags(customer_relations: true)
group.add_developer(user)
end
it 'returns contact data correctly' do
expected_contacts = [
{ 'id' => contact_1.id, 'email' => contact_1.email,
'first_name' => contact_1.first_name, 'last_name' => contact_1.last_name },
{ 'id' => contact_2.id, 'email' => contact_2.email,
'first_name' => contact_2.first_name, 'last_name' => contact_2.last_name }
]
expect(subject).to match_array(expected_contacts)
end
end
describe '#labels_as_hash' do describe '#labels_as_hash' do
def expect_labels_to_equal(labels, expected_labels) def expect_labels_to_equal(labels, expected_labels)
expect(labels.size).to eq(expected_labels.size) expect(labels.size).to eq(expected_labels.size)
......
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