Commit 1ba856f5 authored by Ash McKenzie's avatar Ash McKenzie

Merge branch '238311-add-api-issues-confidentiality-filter' into 'master'

Search API - filter issues by confidentiality

See merge request gitlab-org/gitlab!44451
parents ca29a313 be82f19b
...@@ -100,7 +100,7 @@ module SearchHelper ...@@ -100,7 +100,7 @@ module SearchHelper
end end
def search_service def search_service
@search_service ||= ::SearchService.new(current_user, params) @search_service ||= ::SearchService.new(current_user, params.merge(confidential: Gitlab::Utils.to_boolean(params[:confidential])))
end end
private private
......
...@@ -24,6 +24,7 @@ GET /search ...@@ -24,6 +24,7 @@ GET /search
| `scope` | string | yes | The scope to search in | | `scope` | string | yes | The scope to search in |
| `search` | string | yes | The search query | | `search` | string | yes | The search query |
| `state` | string | no | Filter by state. Issues and merge requests are supported; it is ignored for other scopes. | | `state` | string | no | Filter by state. Issues and merge requests are supported; it is ignored for other scopes. |
| `confidential` | boolean | no | Filter by confidentiality. Issues scope is supported; it is ignored for other scopes. This parameter is behind a [feature flag (`search_filter_by_confidential`)](../administration/feature_flags.md). |
Search the expression within the specified scope. Currently these scopes are supported: projects, issues, merge_requests, milestones, snippet_titles, users. Search the expression within the specified scope. Currently these scopes are supported: projects, issues, merge_requests, milestones, snippet_titles, users.
...@@ -433,6 +434,7 @@ GET /groups/:id/search ...@@ -433,6 +434,7 @@ GET /groups/:id/search
| `scope` | string | yes | The scope to search in | | `scope` | string | yes | The scope to search in |
| `search` | string | yes | The search query | | `search` | string | yes | The search query |
| `state` | string | no | Filter by state. Issues and merge requests are supported; it is ignored for other scopes. | | `state` | string | no | Filter by state. Issues and merge requests are supported; it is ignored for other scopes. |
| `confidential` | boolean | no | Filter by confidentiality. Issues scope is supported; it is ignored for other scopes. This parameter is behind a [feature flag (`search_filter_by_confidential`)](../administration/feature_flags.md). |
Search the expression within the specified scope. Currently these scopes are supported: projects, issues, merge_requests, milestones, users. Search the expression within the specified scope. Currently these scopes are supported: projects, issues, merge_requests, milestones, users.
...@@ -812,6 +814,7 @@ GET /projects/:id/search ...@@ -812,6 +814,7 @@ GET /projects/:id/search
| `search` | string | yes | The search query | | `search` | string | yes | The search query |
| `ref` | string | no | The name of a repository branch or tag to search on. The project's default branch is used by default. This is only applicable for scopes: commits, blobs, and wiki_blobs. | | `ref` | string | no | The name of a repository branch or tag to search on. The project's default branch is used by default. This is only applicable for scopes: commits, blobs, and wiki_blobs. |
| `state` | string | no | Filter by state. Issues and merge requests are supported; it is ignored for other scopes. | | `state` | string | no | Filter by state. Issues and merge requests are supported; it is ignored for other scopes. |
| `confidential` | boolean | no | Filter by confidentiality. Issues scope is supported; it is ignored for other scopes. This parameter is behind a [feature flag (`search_filter_by_confidential`)](../administration/feature_flags.md). |
Search the expression within the specified scope. Currently these scopes are supported: issues, merge_requests, milestones, notes, wiki_blobs, commits, blobs, users. Search the expression within the specified scope. Currently these scopes are supported: issues, merge_requests, milestones, notes, wiki_blobs, commits, blobs, users.
......
...@@ -36,10 +36,9 @@ module Elastic ...@@ -36,10 +36,9 @@ module Elastic
def confidentiality_filter(query_hash, options) def confidentiality_filter(query_hash, options)
current_user = options[:current_user] current_user = options[:current_user]
project_ids = options[:project_ids] project_ids = options[:project_ids]
confidential_filter = options[:confidential]
if Feature.enabled?(:search_filter_by_confidential) && confidential_filter.present? && %w(yes no).include?(confidential_filter) if [true, false].include?(options[:confidential]) && Feature.enabled?(:search_filter_by_confidential)
query_hash[:query][:bool][:filter] << { term: { confidential: confidential_filter == 'yes' } } query_hash[:query][:bool][:filter] << { term: { confidential: options[:confidential] } }
end end
return query_hash if current_user&.can_read_all_resources? return query_hash if current_user&.can_read_all_resources?
......
...@@ -33,6 +33,7 @@ module API ...@@ -33,6 +33,7 @@ module API
scope: params[:scope], scope: params[:scope],
search: params[:search], search: params[:search],
state: params[:state], state: params[:state],
confidential: params[:confidential],
snippets: snippets?, snippets: snippets?,
page: params[:page], page: params[:page],
per_page: params[:per_page] per_page: params[:per_page]
...@@ -75,6 +76,7 @@ module API ...@@ -75,6 +76,7 @@ module API
desc: 'The scope of the search', desc: 'The scope of the search',
values: Helpers::SearchHelpers.global_search_scopes values: Helpers::SearchHelpers.global_search_scopes
optional :state, type: String, desc: 'Filter results by state', values: Helpers::SearchHelpers.search_states optional :state, type: String, desc: 'Filter results by state', values: Helpers::SearchHelpers.search_states
optional :confidential, type: Boolean, desc: 'Filter results by confidentiality'
use :pagination use :pagination
end end
get do get do
...@@ -96,6 +98,7 @@ module API ...@@ -96,6 +98,7 @@ module API
desc: 'The scope of the search', desc: 'The scope of the search',
values: Helpers::SearchHelpers.group_search_scopes values: Helpers::SearchHelpers.group_search_scopes
optional :state, type: String, desc: 'Filter results by state', values: Helpers::SearchHelpers.search_states optional :state, type: String, desc: 'Filter results by state', values: Helpers::SearchHelpers.search_states
optional :confidential, type: Boolean, desc: 'Filter results by confidentiality'
use :pagination use :pagination
end end
get ':id/(-/)search' do get ':id/(-/)search' do
...@@ -118,6 +121,7 @@ module API ...@@ -118,6 +121,7 @@ module API
values: Helpers::SearchHelpers.project_search_scopes values: Helpers::SearchHelpers.project_search_scopes
optional :ref, type: String, desc: 'The name of a repository branch or tag. If not given, the default branch is used' optional :ref, type: String, desc: 'The name of a repository branch or tag. If not given, the default branch is used'
optional :state, type: String, desc: 'Filter results by state', values: Helpers::SearchHelpers.search_states optional :state, type: String, desc: 'Filter results by state', values: Helpers::SearchHelpers.search_states
optional :confidential, type: Boolean, desc: 'Filter results by confidentiality'
use :pagination use :pagination
end end
get ':id/(-/)search' do get ':id/(-/)search' do
......
...@@ -219,8 +219,8 @@ module Gitlab ...@@ -219,8 +219,8 @@ module Gitlab
params[:state] = filters[:state] if filters.key?(:state) params[:state] = filters[:state] if filters.key?(:state)
if Feature.enabled?(:search_filter_by_confidential) && filters.key?(:confidential) && %w(yes no).include?(filters[:confidential]) if [true, false].include?(filters[:confidential]) && Feature.enabled?(:search_filter_by_confidential)
params[:confidential] = filters[:confidential] == 'yes' params[:confidential] = filters[:confidential]
end end
end end
end end
......
...@@ -448,4 +448,33 @@ RSpec.describe SearchHelper do ...@@ -448,4 +448,33 @@ RSpec.describe SearchHelper do
end end
end end
end end
describe '#search_service' do
using RSpec::Parameterized::TableSyntax
subject { search_service }
before do
allow(self).to receive(:current_user).and_return(:the_current_user)
end
where(:confidential, :expected) do
'0' | false
'1' | true
'yes' | true
'no' | false
true | true
false | false
end
let(:params) {{ confidential: confidential }}
with_them do
it 'transforms confidentiality param' do
expect(::SearchService).to receive(:new).with(:the_current_user, { confidential: expected })
subject
end
end
end
end end
...@@ -58,6 +58,17 @@ RSpec.describe API::Search do ...@@ -58,6 +58,17 @@ RSpec.describe API::Search do
end end
end end
shared_examples 'filter by confidentiality' do |scope:, search:|
it 'respects confidentiality filtering' do
get api(endpoint, user), params: { scope: scope, search: search, confidential: confidential.to_s }
documents = Gitlab::Json.parse(response.body)
expect(documents.count).to eq(1)
expect(documents.first['confidential']).to eq(confidential)
end
end
describe 'GET /search' do describe 'GET /search' do
let(:endpoint) { '/search' } let(:endpoint) { '/search' }
...@@ -137,6 +148,26 @@ RSpec.describe API::Search do ...@@ -137,6 +148,26 @@ RSpec.describe API::Search do
include_examples 'filter by state', scope: :issues, search: 'awesome' include_examples 'filter by state', scope: :issues, search: 'awesome'
end end
end end
context 'filter by confidentiality' do
before do
stub_feature_flags(search_filter_by_confidential: true)
create(:issue, project: project, author: user, title: 'awesome non-confidential issue')
create(:issue, :confidential, project: project, author: user, title: 'awesome confidential issue')
end
context 'confidential: true' do
let(:confidential) { true }
include_examples 'filter by confidentiality', scope: :issues, search: 'awesome'
end
context 'confidential: false' do
let(:confidential) { false }
include_examples 'filter by confidentiality', scope: :issues, search: 'awesome'
end
end
end end
context 'for merge_requests scope' do context 'for merge_requests scope' do
......
...@@ -24,7 +24,7 @@ RSpec.shared_examples 'search results filtered by confidential' do ...@@ -24,7 +24,7 @@ RSpec.shared_examples 'search results filtered by confidential' do
end end
context 'confidential filter' do context 'confidential filter' do
let(:filters) { { confidential: 'yes' } } let(:filters) { { confidential: true } }
context 'when Feature search_filter_by_confidential enabled' do context 'when Feature search_filter_by_confidential enabled' do
it 'returns only confidential results', :aggregate_failures do it 'returns only confidential results', :aggregate_failures do
...@@ -46,7 +46,7 @@ RSpec.shared_examples 'search results filtered by confidential' do ...@@ -46,7 +46,7 @@ RSpec.shared_examples 'search results filtered by confidential' do
end end
context 'not confidential filter' do context 'not confidential filter' do
let(:filters) { { confidential: 'no' } } let(:filters) { { confidential: false } }
context 'when Feature search_filter_by_confidential enabled' do context 'when Feature search_filter_by_confidential enabled' do
it 'returns not confidential results', :aggregate_failures do it 'returns not confidential results', :aggregate_failures do
...@@ -66,26 +66,4 @@ RSpec.shared_examples 'search results filtered by confidential' do ...@@ -66,26 +66,4 @@ RSpec.shared_examples 'search results filtered by confidential' do
end end
end end
end end
context 'unsupported filter' do
let(:filters) { { confidential: 'goodbye' } }
context 'when Feature search_filter_by_confidential enabled' do
it 'returns confidential and not confidential results', :aggregate_failures do
expect(results.objects('issues')).to include confidential_result
expect(results.objects('issues')).to include opened_result
end
end
context 'when Feature search_filter_by_confidential not enabled' do
before do
stub_feature_flags(search_filter_by_confidential: false)
end
it 'returns confidential and not confidential results', :aggregate_failures do
expect(results.objects('issues')).to include confidential_result
expect(results.objects('issues')).to include opened_result
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