Skip to content
Snippets Groups Projects
Commit 52c378d7 authored by Sean McGivern's avatar Sean McGivern :red_circle:
Browse files

Merge branch 'ee-users-search-results' into 'master'

[EE] Add users search results to global search

See merge request gitlab-org/gitlab-ee!8985
parents 7380e48c 09da07e6
No related branches found
No related tags found
1 merge request!8985[EE] Add users search results to global search
Pipeline #52530230 passed with warnings
Showing
with 273 additions and 14 deletions
......@@ -29,6 +29,7 @@ def show
@search_objects = search_service.search_objects
render_commits if @scope == 'commits'
eager_load_user_status if @scope == 'users'
check_single_commit_result
end
......@@ -54,6 +55,12 @@ def render_commits
@search_objects = prepare_commits_for_rendering(@search_objects)
end
def eager_load_user_status
return if Feature.disabled?(:users_search, default_enabled: true)
@search_objects = @search_objects.eager_load(:status) # rubocop:disable CodeReuse/ActiveRecord
end
def check_single_commit_result
if @search_results.single_commit_result?
only_commit = @search_results.objects('commits').first
......
......@@ -366,7 +366,8 @@ def search_tab_ability_map
blobs: :download_code,
commits: :download_code,
merge_requests: :read_merge_request,
notes: [:read_merge_request, :download_code, :read_issue, :read_project_snippet]
notes: [:read_merge_request, :download_code, :read_issue, :read_project_snippet],
members: :read_project_member
)
end
......
......@@ -201,6 +201,16 @@ def search_md_sanitize(object, field)
def limited_count(count, limit = 1000)
count > limit ? "#{limit}+" : count
end
def search_tabs?(tab)
return false if Feature.disabled?(:users_search, default_enabled: true)
if @project
project_search_tabs?(:members)
else
can?(current_user, :read_users_list)
end
end
end
SearchHelper.prepend(EE::SearchHelper)
......@@ -23,7 +23,8 @@ def projects
def allowed_scopes
strong_memoize(:allowed_scopes) do
%w[issues merge_requests milestones]
allowed_scopes = %w[issues merge_requests milestones]
allowed_scopes << 'users' if Feature.enabled?(:users_search, default_enabled: true)
end
end
......
......@@ -11,6 +11,12 @@ def initialize(user, group, params)
@group = group
end
def execute
Gitlab::GroupSearchResults.new(
current_user, projects, group, params[:search], default_project_filter: default_project_filter
)
end
def projects
return Project.none unless group
return @projects if defined? @projects
......
......@@ -16,7 +16,12 @@ def execute
end
def scope
@scope ||= %w[notes issues merge_requests milestones wiki_blobs commits].delete(params[:scope]) { 'blobs' }
@scope ||= begin
allowed_scopes = %w[notes issues merge_requests milestones wiki_blobs commits]
allowed_scopes << 'users' if Feature.enabled?(:users_search, default_enabled: true)
allowed_scopes.delete(params[:scope]) { 'blobs' }
end
end
end
end
......
- users = capture_haml do
- if search_tabs?(:members)
%li{ class: active_when(@scope == 'users') }
= link_to search_filter_path(scope: 'users') do
Users
%span.badge.badge-pill
= limited_count(@search_results.limited_users_count)
.scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
.fade-left= icon('angle-left')
.fade-right= icon('angle-right')
......@@ -45,6 +53,7 @@
= _("Commits")
%span.badge.badge-pill
= @search_results.commits_count
= users
- elsif @show_snippets
%li{ class: active_when(@scope == 'snippet_blobs') }
......@@ -94,3 +103,4 @@
= _("Wiki")
%span.badge.badge-pill
= limited_count(@search_results.wiki_blobs_count)
= users
%ul.content-list
%li
.avatar-cell.d-none.d-sm-block
= user_avatar(user: user, user_name: user.name, css_class: 'd-none d-sm-inline avatar s40')
.user-info
= link_to user_path(user), class: 'd-none d-sm-inline' do
.item-title
= user.name
= user_status(user)
.cgray= user.to_reference
---
title: Add users search results to global search
merge_request: 21197
author: Alexis Reigel
type: added
......@@ -17,7 +17,7 @@ GET /search
| `scope` | string | yes | The scope to search in |
| `search` | string | yes | The search query |
Search the expression within the specified scope. Currently these scopes are supported: projects, issues, merge_requests, milestones, snippet_titles, snippet_blobs.
Search the expression within the specified scope. Currently these scopes are supported: projects, issues, merge_requests, milestones, snippet_titles, snippet_blobs, users.
If Elasticsearch is enabled additional scopes available are blobs, wiki_blobs and commits. Find more about [the feature](../integration/elasticsearch.md).
......@@ -255,7 +255,7 @@ Example response:
### Scope: snippet_blobs
```bash
curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/search?scope=snippet_blobs&search=test
curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/search?scope=snippet_blos&search=test
```
Example response:
......@@ -375,6 +375,27 @@ Example response:
]
```
### Scope: users
```bash
curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/search?scope=users&search=doe
```
Example response:
```json
[
{
"id": 1,
"name": "John Doe1",
"username": "user1",
"state": "active",
"avatar_url": "http://www.gravatar.com/avatar/c922747a93b40d1ea88262bf1aebee62?s=80&d=identicon",
"web_url": "http://localhost/user1"
}
]
```
## Group Search API
Search within the specified group.
......@@ -391,7 +412,7 @@ GET /groups/:id/search
| `scope` | string | yes | The scope to search in |
| `search` | string | yes | The search query |
Search the expression within the specified scope. Currently these scopes are supported: projects, issues, merge_requests, milestones.
Search the expression within the specified scope. Currently these scopes are supported: projects, issues, merge_requests, milestones, users.
If Elasticsearch is enabled additional scopes available are blobs, wiki_blobs and commits. Find more about [the feature](../integration/elasticsearch.md).
......@@ -687,6 +708,27 @@ Example response:
]
```
### Scope: users
```bash
curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/3/search?scope=users&search=doe
```
Example response:
```json
[
{
"id": 1,
"name": "John Doe1",
"username": "user1",
"state": "active",
"avatar_url": "http://www.gravatar.com/avatar/c922747a93b40d1ea88262bf1aebee62?s=80&d=identicon",
"web_url": "http://localhost/user1"
}
]
```
## Project Search API
Search within the specified project.
......@@ -703,7 +745,7 @@ GET /projects/:id/search
| `scope` | string | yes | The scope to search in |
| `search` | string | yes | The search query |
Search the expression within the specified scope. Currently these scopes are supported: issues, merge_requests, milestones, notes, wiki_blobs, commits, blobs.
Search the expression within the specified scope. Currently these scopes are supported: issues, merge_requests, milestones, notes, wiki_blobs, commits, blobs, users.
The response depends on the requested scope.
......@@ -1016,4 +1058,25 @@ Example response:
]
```
### Scope: users
```bash
curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/6/search?scope=users&search=doe
```
Example response:
```json
[
{
"id": 1,
"name": "John Doe1",
"username": "user1",
"state": "active",
"avatar_url": "http://www.gravatar.com/avatar/c922747a93b40d1ea88262bf1aebee62?s=80&d=identicon",
"web_url": "http://localhost/user1"
}
]
```
[ce-41763]: https://gitlab.com/gitlab-org/gitlab-ce/issues/41763
......@@ -9,7 +9,9 @@ module GlobalService
override :execute
def execute
if ::Gitlab::CurrentSettings.elasticsearch_search?
::Gitlab::Elastic::SearchResults.new(current_user, params[:search], elastic_projects, elastic_global)
::Gitlab::Elastic::SearchResults.new(current_user, params[:search],
elastic_projects, projects,
elastic_global)
else
super
end
......
......@@ -3,6 +3,8 @@
module EE
module Search
module GroupService
extend ::Gitlab::Utils::Override
def elastic_projects
@elastic_projects ||= projects.pluck(:id) # rubocop:disable CodeReuse/ActiveRecord
end
......@@ -10,6 +12,15 @@ def elastic_projects
def elastic_global
false
end
override :execute
def execute
return super unless ::Gitlab::CurrentSettings.elasticsearch_search?
::Gitlab::Elastic::GroupSearchResults.new(
current_user, elastic_projects, projects, group, params[:search],
elastic_global, default_project_filter: default_project_filter)
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Elastic
# Always prefer to use the full class namespace when specifying a
# superclass inside a module, because autoloading can occur in a
# different order between execution environments.
class GroupSearchResults < Gitlab::Elastic::SearchResults
delegate :users, to: :generic_search_results
delegate :limited_users_count, to: :generic_search_results
attr_reader :group, :default_project_filter
def initialize(current_user, limit_project_ids, limit_projects, group, query, public_and_internal_projects, default_project_filter: false, per_page: 20)
super(current_user, query, limit_project_ids, limit_projects, public_and_internal_projects)
@default_project_filter = default_project_filter
@group = group
end
def objects(scope, page = nil)
case scope
when 'users'
users.page(page).per(per_page)
else
super
end
end
def generic_search_results
@generic_search_results ||= Gitlab::GroupSearchResults.new(current_user, limit_projects, group, query, default_project_filter: default_project_filter)
end
end
end
end
......@@ -8,6 +8,9 @@ module Elastic
class ProjectSearchResults < Gitlab::Elastic::SearchResults
attr_reader :project, :repository_ref
delegate :users, to: :generic_search_results
delegate :limited_users_count, to: :generic_search_results
def initialize(current_user, query, project_id, repository_ref = nil)
@current_user = current_user
@project = Project.find(project_id)
......@@ -26,11 +29,17 @@ def objects(scope, page = nil)
wiki_blobs.page(page).per(per_page)
when 'commits'
commits(page: page, per_page: per_page)
when 'users'
users.page(page).per(per_page)
else
super
end
end
def generic_search_results
@generic_search_results ||= Gitlab::ProjectSearchResults.new(current_user, project, query, repository_ref)
end
def blobs_count
@blobs_count ||= blobs.total_count
end
......
......@@ -7,11 +7,15 @@ class SearchResults
# Limit search results by passed project ids
# It allows us to search only for projects user has access to
attr_reader :limit_project_ids
attr_reader :limit_project_ids, :limit_projects
def initialize(current_user, query, limit_project_ids, public_and_internal_projects = true)
delegate :users, to: :generic_search_results
delegate :limited_users_count, to: :generic_search_results
def initialize(current_user, query, limit_project_ids, limit_projects = nil, public_and_internal_projects = true)
@current_user = current_user
@limit_project_ids = limit_project_ids
@limit_projects = limit_projects
@query = query
@public_and_internal_projects = public_and_internal_projects
end
......@@ -32,11 +36,17 @@ def objects(scope, page = nil)
wiki_blobs.page(page).per(per_page)
when 'commits'
commits(page: page, per_page: per_page)
when 'users'
users.page(page).per(per_page)
else
Kaminari.paginate_array([])
end
end
def generic_search_results
@generic_search_results ||= Gitlab::SearchResults.new(current_user, limit_projects, query)
end
def projects_count
@projects_count ||= projects.total_count
end
......
require 'spec_helper'
describe Gitlab::Elastic::GroupSearchResults do
set(:user) { create(:user) }
set(:group) { create(:group) }
set(:guest) { create(:user).tap { |u| group.add_user(u, Gitlab::Access::GUEST) } }
before do
stub_ee_application_setting(elasticsearch_search: true, elasticsearch_indexing: true)
end
context 'user search' do
subject(:results) { described_class.new(user, nil, nil, group, guest.username, nil) }
before do
expect(Gitlab::GroupSearchResults).to receive(:new).and_call_original
end
it { expect(results.objects('users')).to eq([guest]) }
it { expect(results.limited_users_count).to eq(1) }
end
end
......@@ -224,4 +224,15 @@
expect(results.issues_count).to eq 3
end
end
context 'user search' do
subject(:results) { described_class.new(user, project.owner.username, project.id) }
before do
expect(Gitlab::ProjectSearchResults).to receive(:new).and_call_original
end
it { expect(results.objects('users')).to eq([project.owner]) }
it { expect(results.limited_users_count).to eq(1) }
end
end
......@@ -5,17 +5,17 @@ module Helpers
module SearchHelpers
def self.global_search_scopes
# This is a separate method so that EE can redefine it.
%w(projects issues merge_requests milestones snippet_titles snippet_blobs)
%w(projects issues merge_requests milestones snippet_titles snippet_blobs users)
end
def self.group_search_scopes
# This is a separate method so that EE can redefine it.
%w(projects issues merge_requests milestones)
%w(projects issues merge_requests milestones users)
end
def self.project_search_scopes
# This is a separate method so that EE can redefine it.
%w(issues merge_requests milestones notes wiki_blobs commits blobs)
%w(issues merge_requests milestones notes wiki_blobs commits blobs users)
end
end
end
......
......@@ -17,7 +17,8 @@ class Search < Grape::API
blobs: Entities::Blob,
wiki_blobs: Entities::Blob,
snippet_titles: Entities::Snippet,
snippet_blobs: Entities::Snippet
snippet_blobs: Entities::Snippet,
users: Entities::UserBasic
}.freeze
def search(additional_params = {})
......@@ -51,6 +52,12 @@ def verify_search_scope!
# Defining this method here as a noop allows us to easily extend it in
# EE, without having to modify this file directly.
end
def check_users_search_allowed!
if params[:scope].to_sym == :users && Feature.disabled?(:users_search, default_enabled: true)
render_api_error!({ error: _("Scope not supported with disabled 'users_search' feature!") }, 400)
end
end
end
resource :search do
......@@ -67,6 +74,7 @@ def verify_search_scope!
end
get do
verify_search_scope!
check_users_search_allowed!
present search, with: entity
end
......@@ -87,6 +95,7 @@ def verify_search_scope!
end
get ':id/(-/)search' do
verify_search_scope!
check_users_search_allowed!
present search(group_id: user_group.id), with: entity
end
......@@ -106,6 +115,8 @@ def verify_search_scope!
use :pagination
end
get ':id/(-/)search' do
check_users_search_allowed!
present search(project_id: user_project.id), with: entity
end
end
......
# frozen_string_literal: true
module Gitlab
class GroupSearchResults < SearchResults
def initialize(current_user, limit_projects, group, query, default_project_filter: false, per_page: 20)
super(current_user, limit_projects, query, default_project_filter: default_project_filter, per_page: per_page)
@group = group
end
# rubocop:disable CodeReuse/ActiveRecord
def users
# 1: get all groups the current user has access to
groups = GroupsFinder.new(current_user).execute.joins(:users)
# 2: Get the group's whole hierarchy
group_users = @group.direct_and_indirect_users
# 3: get all users the current user has access to (->
# `SearchResults#users`), which also applies the query.
users = super
# 4: filter for users that belong to the previously selected groups
users
.where(id: group_users.select('id'))
.where(id: groups.select('members.user_id'))
end
# rubocop:enable CodeReuse/ActiveRecord
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