2019-10-16 05:07:51 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2014-01-15 09:32:07 -05:00
|
|
|
require 'spec_helper'
|
|
|
|
|
2020-06-16 14:09:01 -04:00
|
|
|
RSpec.describe IssuesFinder do
|
2020-09-18 08:09:50 -04:00
|
|
|
using RSpec::Parameterized::TableSyntax
|
2019-03-19 13:25:10 -04:00
|
|
|
include_context 'IssuesFinder context'
|
2014-01-15 09:32:07 -05:00
|
|
|
|
2016-05-12 07:20:09 -04:00
|
|
|
describe '#execute' do
|
2019-03-19 13:25:10 -04:00
|
|
|
include_context 'IssuesFinder#execute context'
|
2016-12-12 03:43:56 -05:00
|
|
|
|
2014-12-05 10:25:22 -05:00
|
|
|
context 'scope: all' do
|
2016-05-12 07:20:09 -04:00
|
|
|
let(:scope) { 'all' }
|
2014-01-15 09:32:07 -05:00
|
|
|
|
2016-05-12 07:20:09 -04:00
|
|
|
it 'returns all issues' do
|
2021-01-07 10:10:33 -05:00
|
|
|
expect(issues).to contain_exactly(issue1, issue2, issue3, issue4, issue5)
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'user does not have read permissions' do
|
|
|
|
let(:search_user) { user2 }
|
|
|
|
|
|
|
|
context 'when filtering by project id' do
|
|
|
|
let(:params) { { project_id: project1.id } }
|
|
|
|
|
|
|
|
it 'returns no issues' do
|
|
|
|
expect(issues).to be_empty
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when filtering by group id' do
|
|
|
|
let(:params) { { group_id: group.id } }
|
|
|
|
|
|
|
|
it 'returns no issues' do
|
|
|
|
expect(issues).to be_empty
|
|
|
|
end
|
|
|
|
end
|
2014-12-05 10:25:22 -05:00
|
|
|
end
|
2014-01-15 09:32:07 -05:00
|
|
|
|
2019-04-07 14:35:16 -04:00
|
|
|
context 'assignee filtering' do
|
|
|
|
let(:issuables) { issues }
|
2016-05-12 07:20:09 -04:00
|
|
|
|
2019-04-07 14:35:16 -04:00
|
|
|
it_behaves_like 'assignee ID filter' do
|
|
|
|
let(:params) { { assignee_id: user.id } }
|
2021-01-07 10:10:33 -05:00
|
|
|
let(:expected_issuables) { [issue1, issue2, issue5] }
|
2016-05-12 07:20:09 -04:00
|
|
|
end
|
2019-03-19 13:25:10 -04:00
|
|
|
|
2019-09-17 08:06:48 -04:00
|
|
|
it_behaves_like 'assignee NOT ID filter' do
|
|
|
|
let(:params) { { not: { assignee_id: user.id } } }
|
|
|
|
let(:expected_issuables) { [issue3, issue4] }
|
|
|
|
end
|
|
|
|
|
2021-05-26 17:10:49 -04:00
|
|
|
it_behaves_like 'assignee OR filter' do
|
|
|
|
let(:params) { { or: { assignee_id: [user.id, user2.id] } } }
|
|
|
|
let(:expected_issuables) { [issue1, issue2, issue3, issue5] }
|
|
|
|
end
|
|
|
|
|
2021-04-14 11:09:04 -04:00
|
|
|
context 'when assignee_id does not exist' do
|
|
|
|
it_behaves_like 'assignee NOT ID filter' do
|
|
|
|
let(:params) { { not: { assignee_id: -100 } } }
|
|
|
|
let(:expected_issuables) { [issue1, issue2, issue3, issue4, issue5] }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-09-17 08:06:48 -04:00
|
|
|
context 'filter by username' do
|
2020-02-16 22:09:00 -05:00
|
|
|
let_it_be(:user3) { create(:user) }
|
2019-09-17 08:06:48 -04:00
|
|
|
|
2019-04-07 14:35:16 -04:00
|
|
|
before do
|
|
|
|
project2.add_developer(user3)
|
2020-01-28 10:08:36 -05:00
|
|
|
issue2.assignees = [user2]
|
|
|
|
issue3.assignees = [user3]
|
2019-04-07 14:35:16 -04:00
|
|
|
end
|
2019-03-19 13:25:10 -04:00
|
|
|
|
2019-09-17 08:06:48 -04:00
|
|
|
it_behaves_like 'assignee username filter' do
|
2020-01-28 10:08:36 -05:00
|
|
|
let(:params) { { assignee_username: [user2.username] } }
|
|
|
|
let(:expected_issuables) { [issue2] }
|
2019-09-17 08:06:48 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it_behaves_like 'assignee NOT username filter' do
|
2020-01-28 10:08:36 -05:00
|
|
|
before do
|
|
|
|
issue2.assignees = [user2]
|
|
|
|
end
|
|
|
|
|
|
|
|
let(:params) { { not: { assignee_username: [user.username, user2.username] } } }
|
|
|
|
let(:expected_issuables) { [issue3, issue4] }
|
2019-09-17 08:06:48 -04:00
|
|
|
end
|
2021-04-14 11:09:04 -04:00
|
|
|
|
2021-05-26 17:10:49 -04:00
|
|
|
it_behaves_like 'assignee OR filter' do
|
|
|
|
let(:params) { { or: { assignee_username: [user2.username, user3.username] } } }
|
|
|
|
let(:expected_issuables) { [issue2, issue3] }
|
|
|
|
end
|
|
|
|
|
2021-04-14 11:09:04 -04:00
|
|
|
context 'when assignee_username does not exist' do
|
|
|
|
it_behaves_like 'assignee NOT username filter' do
|
|
|
|
before do
|
|
|
|
issue2.assignees = [user2]
|
|
|
|
end
|
|
|
|
|
|
|
|
let(:params) { { not: { assignee_username: 'non_existent_username' } } }
|
|
|
|
let(:expected_issuables) { [issue1, issue2, issue3, issue4, issue5] }
|
|
|
|
end
|
|
|
|
end
|
2019-03-19 13:25:10 -04:00
|
|
|
end
|
|
|
|
|
2019-04-07 14:35:16 -04:00
|
|
|
it_behaves_like 'no assignee filter' do
|
2020-02-16 22:09:00 -05:00
|
|
|
let_it_be(:user3) { create(:user) }
|
2019-04-07 14:35:16 -04:00
|
|
|
let(:expected_issuables) { [issue4] }
|
2018-10-25 22:47:14 -04:00
|
|
|
end
|
2018-10-25 03:02:28 -04:00
|
|
|
|
2019-04-07 14:35:16 -04:00
|
|
|
it_behaves_like 'any assignee filter' do
|
2021-01-07 10:10:33 -05:00
|
|
|
let(:expected_issuables) { [issue1, issue2, issue3, issue5] }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'filtering by release' do
|
|
|
|
context 'when the release tag is none' do
|
|
|
|
let(:params) { { release_tag: 'none' } }
|
|
|
|
|
|
|
|
it 'returns issues without releases' do
|
|
|
|
expect(issues).to contain_exactly(issue2, issue3, issue4, issue5)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the release tag exists' do
|
|
|
|
let(:params) { { project_id: project1.id, release_tag: release.tag } }
|
|
|
|
|
|
|
|
it 'returns the issues associated with that release' do
|
|
|
|
expect(issues).to contain_exactly(issue1)
|
|
|
|
end
|
2018-10-25 22:47:14 -04:00
|
|
|
end
|
2018-10-25 03:02:28 -04:00
|
|
|
end
|
|
|
|
|
2019-08-31 11:20:19 -04:00
|
|
|
context 'filtering by projects' do
|
|
|
|
context 'when projects are passed in a list of ids' do
|
|
|
|
let(:params) { { projects: [project1.id] } }
|
|
|
|
|
|
|
|
it 'returns the issue belonging to the projects' do
|
2021-01-07 10:10:33 -05:00
|
|
|
expect(issues).to contain_exactly(issue1, issue5)
|
2019-08-31 11:20:19 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when projects are passed in a subquery' do
|
|
|
|
let(:params) { { projects: Project.id_in(project1.id) } }
|
|
|
|
|
|
|
|
it 'returns the issue belonging to the projects' do
|
2021-01-07 10:10:33 -05:00
|
|
|
expect(issues).to contain_exactly(issue1, issue5)
|
2019-08-31 11:20:19 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-01-24 01:06:24 -05:00
|
|
|
context 'filtering by group_id' do
|
|
|
|
let(:params) { { group_id: group.id } }
|
|
|
|
|
|
|
|
context 'when include_subgroup param not set' do
|
|
|
|
it 'returns all group issues' do
|
2021-01-07 10:10:33 -05:00
|
|
|
expect(issues).to contain_exactly(issue1, issue5)
|
2018-01-24 01:06:24 -05:00
|
|
|
end
|
2019-08-31 11:20:19 -04:00
|
|
|
|
|
|
|
context 'when projects outside the group are passed' do
|
|
|
|
let(:params) { { group_id: group.id, projects: [project2.id] } }
|
|
|
|
|
|
|
|
it 'returns no issues' do
|
|
|
|
expect(issues).to be_empty
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when projects of the group are passed' do
|
|
|
|
let(:params) { { group_id: group.id, projects: [project1.id] } }
|
|
|
|
|
|
|
|
it 'returns the issue within the group and projects' do
|
2021-01-07 10:10:33 -05:00
|
|
|
expect(issues).to contain_exactly(issue1, issue5)
|
2019-08-31 11:20:19 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when projects of the group are passed as a subquery' do
|
|
|
|
let(:params) { { group_id: group.id, projects: Project.id_in(project1.id) } }
|
|
|
|
|
|
|
|
it 'returns the issue within the group and projects' do
|
2021-01-07 10:10:33 -05:00
|
|
|
expect(issues).to contain_exactly(issue1, issue5)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when release_tag is passed as a parameter' do
|
|
|
|
let(:params) { { group_id: group.id, release_tag: 'dne-release-tag' } }
|
|
|
|
|
|
|
|
it 'ignores the release_tag parameter' do
|
|
|
|
expect(issues).to contain_exactly(issue1, issue5)
|
2019-08-31 11:20:19 -04:00
|
|
|
end
|
|
|
|
end
|
2018-01-24 01:06:24 -05:00
|
|
|
end
|
|
|
|
|
2019-07-24 05:20:54 -04:00
|
|
|
context 'when include_subgroup param is true' do
|
2018-01-24 01:06:24 -05:00
|
|
|
before do
|
|
|
|
params[:include_subgroups] = true
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns all group and subgroup issues' do
|
2021-01-07 10:10:33 -05:00
|
|
|
expect(issues).to contain_exactly(issue1, issue4, issue5)
|
2018-01-24 01:06:24 -05:00
|
|
|
end
|
2019-08-31 11:20:19 -04:00
|
|
|
|
|
|
|
context 'when mixed projects are passed' do
|
|
|
|
let(:params) { { group_id: group.id, projects: [project2.id, project3.id] } }
|
|
|
|
|
|
|
|
it 'returns the issue within the group and projects' do
|
|
|
|
expect(issues).to contain_exactly(issue4)
|
|
|
|
end
|
|
|
|
end
|
2018-01-24 01:06:24 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-02-22 10:10:48 -05:00
|
|
|
context 'filtering by author' do
|
|
|
|
context 'by author ID' do
|
|
|
|
let(:params) { { author_id: user2.id } }
|
2016-05-12 07:20:09 -04:00
|
|
|
|
2021-02-22 10:10:48 -05:00
|
|
|
it 'returns issues created by that user' do
|
|
|
|
expect(issues).to contain_exactly(issue3)
|
|
|
|
end
|
2016-05-12 07:20:09 -04:00
|
|
|
end
|
2014-12-05 10:25:22 -05:00
|
|
|
|
2021-02-22 10:10:48 -05:00
|
|
|
context 'using OR' do
|
|
|
|
let(:issue6) { create(:issue, project: project2) }
|
|
|
|
let(:params) { { or: { author_username: [issue3.author.username, issue6.author.username] } } }
|
|
|
|
|
|
|
|
it 'returns issues created by any of the given users' do
|
|
|
|
expect(issues).to contain_exactly(issue3, issue6)
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when feature flag is disabled' do
|
|
|
|
before do
|
|
|
|
stub_feature_flags(or_issuable_queries: false)
|
|
|
|
end
|
2019-09-17 08:06:48 -04:00
|
|
|
|
2021-02-22 10:10:48 -05:00
|
|
|
it 'does not add any filter' do
|
|
|
|
expect(issues).to contain_exactly(issue1, issue2, issue3, issue4, issue5, issue6)
|
|
|
|
end
|
|
|
|
end
|
2019-09-17 08:06:48 -04:00
|
|
|
end
|
|
|
|
|
2021-02-22 10:10:48 -05:00
|
|
|
context 'filtering by NOT author ID' do
|
|
|
|
let(:params) { { not: { author_id: user2.id } } }
|
|
|
|
|
|
|
|
it 'returns issues not created by that user' do
|
|
|
|
expect(issues).to contain_exactly(issue1, issue2, issue4, issue5)
|
|
|
|
end
|
2019-10-23 05:06:03 -04:00
|
|
|
end
|
|
|
|
|
2021-02-22 10:10:48 -05:00
|
|
|
context 'filtering by nonexistent author ID and issue term using CTE for search' do
|
|
|
|
let(:params) do
|
|
|
|
{
|
|
|
|
author_id: 'does-not-exist',
|
|
|
|
search: 'git',
|
|
|
|
attempt_group_search_optimizations: true
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns no results' do
|
|
|
|
expect(issues).to be_empty
|
|
|
|
end
|
2019-10-23 05:06:03 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-05-12 07:20:09 -04:00
|
|
|
context 'filtering by milestone' do
|
|
|
|
let(:params) { { milestone_title: milestone.title } }
|
|
|
|
|
|
|
|
it 'returns issues assigned to that milestone' do
|
|
|
|
expect(issues).to contain_exactly(issue1)
|
|
|
|
end
|
2015-10-06 12:57:13 -04:00
|
|
|
end
|
|
|
|
|
2019-09-17 08:06:48 -04:00
|
|
|
context 'filtering by not milestone' do
|
|
|
|
let(:params) { { not: { milestone_title: milestone.title } } }
|
|
|
|
|
|
|
|
it 'returns issues not assigned to that milestone' do
|
2021-01-07 10:10:33 -05:00
|
|
|
expect(issues).to contain_exactly(issue2, issue3, issue4, issue5)
|
2019-09-17 08:06:48 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-07-07 11:08:49 -04:00
|
|
|
context 'filtering by group milestone' do
|
|
|
|
let!(:group) { create(:group, :public) }
|
|
|
|
let(:group_milestone) { create(:milestone, group: group) }
|
|
|
|
let!(:group_member) { create(:group_member, group: group, user: user) }
|
|
|
|
let(:params) { { milestone_title: group_milestone.title } }
|
|
|
|
|
|
|
|
before do
|
2020-08-04 17:09:56 -04:00
|
|
|
project2.update!(namespace: group)
|
|
|
|
issue2.update!(milestone: group_milestone)
|
|
|
|
issue3.update!(milestone: group_milestone)
|
2017-07-07 11:08:49 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns issues assigned to that group milestone' do
|
|
|
|
expect(issues).to contain_exactly(issue2, issue3)
|
|
|
|
end
|
2019-09-17 08:06:48 -04:00
|
|
|
|
|
|
|
context 'using NOT' do
|
|
|
|
let(:params) { { not: { milestone_title: group_milestone.title } } }
|
|
|
|
|
|
|
|
it 'returns issues not assigned to that group milestone' do
|
2021-01-07 10:10:33 -05:00
|
|
|
expect(issues).to contain_exactly(issue1, issue4, issue5)
|
2019-09-17 08:06:48 -04:00
|
|
|
end
|
|
|
|
end
|
2017-07-07 11:08:49 -04:00
|
|
|
end
|
|
|
|
|
2016-05-12 07:20:09 -04:00
|
|
|
context 'filtering by no milestone' do
|
2018-10-26 08:37:52 -04:00
|
|
|
let(:params) { { milestone_title: 'None' } }
|
2016-05-12 07:20:09 -04:00
|
|
|
|
|
|
|
it 'returns issues with no milestone' do
|
2021-01-07 10:10:33 -05:00
|
|
|
expect(issues).to contain_exactly(issue2, issue3, issue4, issue5)
|
2016-05-12 07:20:09 -04:00
|
|
|
end
|
2018-10-26 08:37:52 -04:00
|
|
|
|
|
|
|
it 'returns issues with no milestone (deprecated)' do
|
|
|
|
params[:milestone_title] = Milestone::None.title
|
|
|
|
|
2021-01-07 10:10:33 -05:00
|
|
|
expect(issues).to contain_exactly(issue2, issue3, issue4, issue5)
|
2018-10-26 08:37:52 -04:00
|
|
|
end
|
2015-10-06 12:57:13 -04:00
|
|
|
end
|
|
|
|
|
2018-10-03 07:00:03 -04:00
|
|
|
context 'filtering by any milestone' do
|
2018-10-26 08:37:52 -04:00
|
|
|
let(:params) { { milestone_title: 'Any' } }
|
2018-10-03 07:00:03 -04:00
|
|
|
|
|
|
|
it 'returns issues with any assigned milestone' do
|
2019-08-09 05:59:38 -04:00
|
|
|
expect(issues).to contain_exactly(issue1)
|
2018-10-03 07:00:03 -04:00
|
|
|
end
|
2018-10-26 08:37:52 -04:00
|
|
|
|
|
|
|
it 'returns issues with any assigned milestone (deprecated)' do
|
|
|
|
params[:milestone_title] = Milestone::Any.title
|
|
|
|
|
2019-08-09 05:59:38 -04:00
|
|
|
expect(issues).to contain_exactly(issue1)
|
2018-10-26 08:37:52 -04:00
|
|
|
end
|
2018-10-03 07:00:03 -04:00
|
|
|
end
|
|
|
|
|
2016-05-11 12:38:34 -04:00
|
|
|
context 'filtering by upcoming milestone' do
|
|
|
|
let(:params) { { milestone_title: Milestone::Upcoming.name } }
|
|
|
|
|
2018-11-15 01:56:51 -05:00
|
|
|
let!(:group) { create(:group, :public) }
|
|
|
|
let!(:group_member) { create(:group_member, group: group, user: user) }
|
|
|
|
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project_no_upcoming_milestones) { create(:project, :public) }
|
|
|
|
let(:project_next_1_1) { create(:project, :public) }
|
|
|
|
let(:project_next_8_8) { create(:project, :public) }
|
2018-11-15 01:56:51 -05:00
|
|
|
let(:project_in_group) { create(:project, :public, namespace: group) }
|
2016-05-11 12:38:34 -04:00
|
|
|
|
2019-09-17 08:06:48 -04:00
|
|
|
let(:yesterday) { Date.current - 1.day }
|
|
|
|
let(:tomorrow) { Date.current + 1.day }
|
|
|
|
let(:two_days_from_now) { Date.current + 2.days }
|
|
|
|
let(:ten_days_from_now) { Date.current + 10.days }
|
2016-05-11 12:38:34 -04:00
|
|
|
|
|
|
|
let(:milestones) do
|
|
|
|
[
|
|
|
|
create(:milestone, :closed, project: project_no_upcoming_milestones),
|
|
|
|
create(:milestone, project: project_next_1_1, title: '1.1', due_date: two_days_from_now),
|
2018-11-15 01:56:51 -05:00
|
|
|
create(:milestone, project: project_next_1_1, title: '8.9', due_date: ten_days_from_now),
|
|
|
|
create(:milestone, project: project_next_8_8, title: '1.2', due_date: yesterday),
|
|
|
|
create(:milestone, project: project_next_8_8, title: '8.8', due_date: tomorrow),
|
|
|
|
create(:milestone, group: group, title: '9.9', due_date: tomorrow)
|
2016-05-11 12:38:34 -04:00
|
|
|
]
|
|
|
|
end
|
|
|
|
|
|
|
|
before do
|
2019-09-17 08:06:48 -04:00
|
|
|
@created_issues = milestones.map do |milestone|
|
2018-11-15 01:56:51 -05:00
|
|
|
create(:issue, project: milestone.project || project_in_group, milestone: milestone, author: user, assignees: [user])
|
2016-05-11 12:38:34 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-11-15 01:56:51 -05:00
|
|
|
it 'returns issues in the upcoming milestone for each project or group' do
|
|
|
|
expect(issues.map { |issue| issue.milestone.title }).to contain_exactly('1.1', '8.8', '9.9')
|
|
|
|
expect(issues.map { |issue| issue.milestone.due_date }).to contain_exactly(tomorrow, two_days_from_now, tomorrow)
|
2016-05-11 12:38:34 -04:00
|
|
|
end
|
2019-09-17 08:06:48 -04:00
|
|
|
|
|
|
|
context 'using NOT' do
|
|
|
|
let(:params) { { not: { milestone_title: Milestone::Upcoming.name } } }
|
|
|
|
|
2020-05-06 23:09:46 -04:00
|
|
|
it 'returns issues not in upcoming milestones for each project or group, but must have a due date' do
|
|
|
|
target_issues = @created_issues.select do |issue|
|
|
|
|
issue.milestone&.due_date && issue.milestone.due_date <= Date.current
|
|
|
|
end
|
2019-09-17 08:06:48 -04:00
|
|
|
|
2020-05-06 23:09:46 -04:00
|
|
|
expect(issues).to contain_exactly(*target_issues)
|
2019-09-17 08:06:48 -04:00
|
|
|
end
|
|
|
|
end
|
2016-05-11 12:38:34 -04:00
|
|
|
end
|
|
|
|
|
2017-03-10 14:01:05 -05:00
|
|
|
context 'filtering by started milestone' do
|
|
|
|
let(:params) { { milestone_title: Milestone::Started.name } }
|
|
|
|
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project_no_started_milestones) { create(:project, :public) }
|
|
|
|
let(:project_started_1_and_2) { create(:project, :public) }
|
|
|
|
let(:project_started_8) { create(:project, :public) }
|
2017-03-10 14:01:05 -05:00
|
|
|
|
2019-09-17 08:06:48 -04:00
|
|
|
let(:yesterday) { Date.current - 1.day }
|
|
|
|
let(:tomorrow) { Date.current + 1.day }
|
|
|
|
let(:two_days_ago) { Date.current - 2.days }
|
|
|
|
let(:three_days_ago) { Date.current - 3.days }
|
2017-03-10 14:01:05 -05:00
|
|
|
|
|
|
|
let(:milestones) do
|
|
|
|
[
|
|
|
|
create(:milestone, project: project_no_started_milestones, start_date: tomorrow),
|
|
|
|
create(:milestone, project: project_started_1_and_2, title: '1.0', start_date: two_days_ago),
|
|
|
|
create(:milestone, project: project_started_1_and_2, title: '2.0', start_date: yesterday),
|
|
|
|
create(:milestone, project: project_started_1_and_2, title: '3.0', start_date: tomorrow),
|
2019-03-11 10:15:05 -04:00
|
|
|
create(:milestone, :closed, project: project_started_1_and_2, title: '4.0', start_date: three_days_ago),
|
|
|
|
create(:milestone, :closed, project: project_started_8, title: '6.0', start_date: three_days_ago),
|
2017-03-10 14:01:05 -05:00
|
|
|
create(:milestone, project: project_started_8, title: '7.0'),
|
|
|
|
create(:milestone, project: project_started_8, title: '8.0', start_date: yesterday),
|
|
|
|
create(:milestone, project: project_started_8, title: '9.0', start_date: tomorrow)
|
|
|
|
]
|
|
|
|
end
|
|
|
|
|
|
|
|
before do
|
|
|
|
milestones.each do |milestone|
|
2017-05-04 08:11:15 -04:00
|
|
|
create(:issue, project: milestone.project, milestone: milestone, author: user, assignees: [user])
|
2017-03-10 14:01:05 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns issues in the started milestones for each project' do
|
|
|
|
expect(issues.map { |issue| issue.milestone.title }).to contain_exactly('1.0', '2.0', '8.0')
|
|
|
|
expect(issues.map { |issue| issue.milestone.start_date }).to contain_exactly(two_days_ago, yesterday, yesterday)
|
|
|
|
end
|
2019-09-17 08:06:48 -04:00
|
|
|
|
|
|
|
context 'using NOT' do
|
|
|
|
let(:params) { { not: { milestone_title: Milestone::Started.name } } }
|
|
|
|
|
|
|
|
it 'returns issues not in the started milestones for each project' do
|
2020-05-06 23:09:46 -04:00
|
|
|
target_issues = Issue.where(milestone: Milestone.not_started)
|
2019-09-17 08:06:48 -04:00
|
|
|
|
2020-05-06 23:09:46 -04:00
|
|
|
expect(issues).to contain_exactly(*target_issues)
|
2019-09-17 08:06:48 -04:00
|
|
|
end
|
|
|
|
end
|
2017-03-10 14:01:05 -05:00
|
|
|
end
|
|
|
|
|
2020-09-03 08:08:47 -04:00
|
|
|
shared_examples ':label_name parameter' do
|
|
|
|
context 'filtering by label' do
|
|
|
|
let(:params) { { label_name: label.title } }
|
2016-04-13 06:05:10 -04:00
|
|
|
|
2020-09-03 08:08:47 -04:00
|
|
|
it 'returns issues with that label' do
|
|
|
|
expect(issues).to contain_exactly(issue2)
|
|
|
|
end
|
2019-09-17 08:06:48 -04:00
|
|
|
|
2020-09-03 08:08:47 -04:00
|
|
|
context 'using NOT' do
|
|
|
|
let(:params) { { not: { label_name: label.title } } }
|
2019-09-17 08:06:48 -04:00
|
|
|
|
2020-09-03 08:08:47 -04:00
|
|
|
it 'returns issues that do not have that label' do
|
2021-01-07 10:10:33 -05:00
|
|
|
expect(issues).to contain_exactly(issue1, issue3, issue4, issue5)
|
2020-09-03 08:08:47 -04:00
|
|
|
end
|
2019-09-17 08:06:48 -04:00
|
|
|
|
2020-09-03 08:08:47 -04:00
|
|
|
# IssuableFinder first filters using the outer params (the ones not inside the `not` key.)
|
|
|
|
# Afterwards, it applies the `not` params to that resultset. This means that things inside the `not` param
|
|
|
|
# do not take precedence over the outer params with the same name.
|
|
|
|
context 'shadowing the same outside param' do
|
|
|
|
let(:params) { { label_name: label2.title, not: { label_name: label.title } } }
|
2019-09-17 08:06:48 -04:00
|
|
|
|
2020-09-03 08:08:47 -04:00
|
|
|
it 'does not take precedence over labels outside NOT' do
|
|
|
|
expect(issues).to contain_exactly(issue3)
|
|
|
|
end
|
2019-09-17 08:06:48 -04:00
|
|
|
end
|
|
|
|
|
2020-09-03 08:08:47 -04:00
|
|
|
context 'further filtering outside params' do
|
|
|
|
let(:params) { { label_name: label2.title, not: { assignee_username: user2.username } } }
|
2019-09-17 08:06:48 -04:00
|
|
|
|
2020-09-03 08:08:47 -04:00
|
|
|
it 'further filters on the returned resultset' do
|
|
|
|
expect(issues).to be_empty
|
|
|
|
end
|
2019-09-17 08:06:48 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2016-04-13 06:05:10 -04:00
|
|
|
|
2020-09-03 08:08:47 -04:00
|
|
|
context 'filtering by multiple labels' do
|
|
|
|
let(:params) { { label_name: [label.title, label2.title].join(',') } }
|
|
|
|
let(:label2) { create(:label, project: project2) }
|
2016-04-13 06:05:10 -04:00
|
|
|
|
2020-09-03 08:08:47 -04:00
|
|
|
before do
|
|
|
|
create(:label_link, label: label2, target: issue2)
|
|
|
|
end
|
2016-04-13 06:05:10 -04:00
|
|
|
|
2020-09-03 08:08:47 -04:00
|
|
|
it 'returns the unique issues with all those labels' do
|
|
|
|
expect(issues).to contain_exactly(issue2)
|
|
|
|
end
|
2019-09-17 08:06:48 -04:00
|
|
|
|
2020-09-03 08:08:47 -04:00
|
|
|
context 'using NOT' do
|
|
|
|
let(:params) { { not: { label_name: [label.title, label2.title].join(',') } } }
|
2019-09-17 08:06:48 -04:00
|
|
|
|
2020-09-03 08:08:47 -04:00
|
|
|
it 'returns issues that do not have any of the labels provided' do
|
2021-01-07 10:10:33 -05:00
|
|
|
expect(issues).to contain_exactly(issue1, issue4, issue5)
|
2020-09-03 08:08:47 -04:00
|
|
|
end
|
2019-09-17 08:06:48 -04:00
|
|
|
end
|
|
|
|
end
|
2018-10-29 05:50:18 -04:00
|
|
|
|
2020-09-03 08:08:47 -04:00
|
|
|
context 'filtering by a label that includes any or none in the title' do
|
|
|
|
let(:params) { { label_name: [label.title, label2.title].join(',') } }
|
|
|
|
let(:label) { create(:label, title: 'any foo', project: project2) }
|
|
|
|
let(:label2) { create(:label, title: 'bar none', project: project2) }
|
2018-10-29 05:50:18 -04:00
|
|
|
|
2020-09-03 08:08:47 -04:00
|
|
|
before do
|
|
|
|
create(:label_link, label: label2, target: issue2)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns the unique issues with all those labels' do
|
|
|
|
expect(issues).to contain_exactly(issue2)
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'using NOT' do
|
|
|
|
let(:params) { { not: { label_name: [label.title, label2.title].join(',') } } }
|
2018-10-29 05:50:18 -04:00
|
|
|
|
2020-09-03 08:08:47 -04:00
|
|
|
it 'returns issues that do not have ANY ONE of the labels provided' do
|
2021-01-07 10:10:33 -05:00
|
|
|
expect(issues).to contain_exactly(issue1, issue4, issue5)
|
2020-09-03 08:08:47 -04:00
|
|
|
end
|
|
|
|
end
|
2016-05-12 07:20:09 -04:00
|
|
|
end
|
2019-09-17 08:06:48 -04:00
|
|
|
|
2020-09-03 08:08:47 -04:00
|
|
|
context 'filtering by no label' do
|
|
|
|
let(:params) { { label_name: described_class::Params::FILTER_NONE } }
|
2019-09-17 08:06:48 -04:00
|
|
|
|
2020-09-03 08:08:47 -04:00
|
|
|
it 'returns issues with no labels' do
|
2021-01-07 10:10:33 -05:00
|
|
|
expect(issues).to contain_exactly(issue1, issue4, issue5)
|
2019-09-17 08:06:48 -04:00
|
|
|
end
|
|
|
|
end
|
2016-04-13 06:05:10 -04:00
|
|
|
|
2020-09-03 08:08:47 -04:00
|
|
|
context 'filtering by any label' do
|
|
|
|
let(:params) { { label_name: described_class::Params::FILTER_ANY } }
|
2016-05-12 07:20:09 -04:00
|
|
|
|
2020-09-03 08:08:47 -04:00
|
|
|
it 'returns issues that have one or more label' do
|
|
|
|
create_list(:label_link, 2, label: create(:label, project: project2), target: issue3)
|
|
|
|
|
|
|
|
expect(issues).to contain_exactly(issue2, issue3)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the same label exists on project and group levels' do
|
|
|
|
let(:issue1) { create(:issue, project: project1) }
|
|
|
|
let(:issue2) { create(:issue, project: project1) }
|
|
|
|
|
|
|
|
# Skipping validation to reproduce a "real-word" scenario.
|
|
|
|
# We still have legacy labels on PRD that have the same title on the group and project levels, example: `bug`
|
|
|
|
let(:project_label) { build(:label, title: 'somelabel', project: project1).tap { |r| r.save!(validate: false) } }
|
|
|
|
let(:group_label) { create(:group_label, title: 'somelabel', group: project1.group) }
|
|
|
|
|
|
|
|
let(:params) { { label_name: 'somelabel' } }
|
|
|
|
|
|
|
|
before do
|
|
|
|
create(:label_link, label: group_label, target: issue1)
|
|
|
|
create(:label_link, label: project_label, target: issue2)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'finds both issue records' do
|
|
|
|
expect(issues).to contain_exactly(issue1, issue2)
|
|
|
|
end
|
2016-05-12 07:20:09 -04:00
|
|
|
end
|
2015-10-06 12:57:13 -04:00
|
|
|
end
|
|
|
|
|
2020-09-03 08:08:47 -04:00
|
|
|
context 'when `optimized_issuable_label_filter` feature flag is off' do
|
|
|
|
before do
|
|
|
|
stub_feature_flags(optimized_issuable_label_filter: false)
|
|
|
|
end
|
2018-10-29 05:50:18 -04:00
|
|
|
|
2020-09-03 08:08:47 -04:00
|
|
|
it_behaves_like ':label_name parameter'
|
|
|
|
end
|
2018-10-29 05:50:18 -04:00
|
|
|
|
2020-09-03 08:08:47 -04:00
|
|
|
context 'when `optimized_issuable_label_filter` feature flag is on' do
|
|
|
|
before do
|
|
|
|
stub_feature_flags(optimized_issuable_label_filter: true)
|
2018-10-29 05:50:18 -04:00
|
|
|
end
|
2020-09-03 08:08:47 -04:00
|
|
|
|
|
|
|
it_behaves_like ':label_name parameter'
|
2018-10-29 05:50:18 -04:00
|
|
|
end
|
|
|
|
|
2016-06-27 16:22:19 -04:00
|
|
|
context 'filtering by issue term' do
|
|
|
|
let(:params) { { search: 'git' } }
|
|
|
|
|
|
|
|
it 'returns issues with title and description match for search term' do
|
|
|
|
expect(issues).to contain_exactly(issue1, issue2)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-01-13 11:24:31 -05:00
|
|
|
context 'filtering by issue term in title' do
|
|
|
|
let(:params) { { search: 'git', in: 'title' } }
|
|
|
|
|
|
|
|
it 'returns issues with title match for search term' do
|
|
|
|
expect(issues).to contain_exactly(issue1)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-02-17 13:28:32 -05:00
|
|
|
context 'filtering by issues iids' do
|
2021-01-07 10:10:33 -05:00
|
|
|
let(:params) { { iids: [issue3.iid] } }
|
2016-06-27 16:22:19 -04:00
|
|
|
|
2021-01-07 10:10:33 -05:00
|
|
|
it 'returns issues where iids match' do
|
|
|
|
expect(issues).to contain_exactly(issue3, issue5)
|
2016-06-27 16:22:19 -04:00
|
|
|
end
|
2019-09-17 08:06:48 -04:00
|
|
|
|
|
|
|
context 'using NOT' do
|
2021-01-07 10:10:33 -05:00
|
|
|
let(:params) { { not: { iids: [issue3.iid] } } }
|
2019-09-17 08:06:48 -04:00
|
|
|
|
|
|
|
it 'returns issues with no iids match' do
|
|
|
|
expect(issues).to contain_exactly(issue1, issue2, issue4)
|
|
|
|
end
|
|
|
|
end
|
2016-06-27 16:22:19 -04:00
|
|
|
end
|
|
|
|
|
2016-11-30 03:11:43 -05:00
|
|
|
context 'filtering by state' do
|
|
|
|
context 'with opened' do
|
|
|
|
let(:params) { { state: 'opened' } }
|
|
|
|
|
|
|
|
it 'returns only opened issues' do
|
2021-01-07 10:10:33 -05:00
|
|
|
expect(issues).to contain_exactly(issue1, issue2, issue3, issue4, issue5)
|
2016-11-30 03:11:43 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with closed' do
|
|
|
|
let(:params) { { state: 'closed' } }
|
|
|
|
|
|
|
|
it 'returns only closed issues' do
|
|
|
|
expect(issues).to contain_exactly(closed_issue)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with all' do
|
|
|
|
let(:params) { { state: 'all' } }
|
|
|
|
|
|
|
|
it 'returns all issues' do
|
2021-01-07 10:10:33 -05:00
|
|
|
expect(issues).to contain_exactly(issue1, issue2, issue3, closed_issue, issue4, issue5)
|
2016-11-30 03:11:43 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with invalid state' do
|
|
|
|
let(:params) { { state: 'invalid_state' } }
|
|
|
|
|
|
|
|
it 'returns all issues' do
|
2021-01-07 10:10:33 -05:00
|
|
|
expect(issues).to contain_exactly(issue1, issue2, issue3, closed_issue, issue4, issue5)
|
2016-11-30 03:11:43 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-06-13 17:02:35 -04:00
|
|
|
context 'filtering by created_at' do
|
|
|
|
context 'through created_after' do
|
|
|
|
let(:params) { { created_after: issue3.created_at } }
|
|
|
|
|
|
|
|
it 'returns issues created on or after the given date' do
|
|
|
|
expect(issues).to contain_exactly(issue3)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'through created_before' do
|
2018-02-28 06:16:29 -05:00
|
|
|
let(:params) { { created_before: issue1.created_at } }
|
2017-06-13 17:02:35 -04:00
|
|
|
|
|
|
|
it 'returns issues created on or before the given date' do
|
|
|
|
expect(issues).to contain_exactly(issue1)
|
|
|
|
end
|
|
|
|
end
|
2018-02-28 06:16:29 -05:00
|
|
|
|
|
|
|
context 'through created_after and created_before' do
|
|
|
|
let(:params) { { created_after: issue2.created_at, created_before: issue3.created_at } }
|
|
|
|
|
|
|
|
it 'returns issues created between the given dates' do
|
|
|
|
expect(issues).to contain_exactly(issue2, issue3)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'filtering by updated_at' do
|
|
|
|
context 'through updated_after' do
|
|
|
|
let(:params) { { updated_after: issue3.updated_at } }
|
|
|
|
|
|
|
|
it 'returns issues updated on or after the given date' do
|
|
|
|
expect(issues).to contain_exactly(issue3)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'through updated_before' do
|
|
|
|
let(:params) { { updated_before: issue1.updated_at } }
|
|
|
|
|
|
|
|
it 'returns issues updated on or before the given date' do
|
|
|
|
expect(issues).to contain_exactly(issue1)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'through updated_after and updated_before' do
|
|
|
|
let(:params) { { updated_after: issue2.updated_at, updated_before: issue3.updated_at } }
|
|
|
|
|
|
|
|
it 'returns issues updated between the given dates' do
|
|
|
|
expect(issues).to contain_exactly(issue2, issue3)
|
|
|
|
end
|
|
|
|
end
|
2017-06-13 17:02:35 -04:00
|
|
|
end
|
|
|
|
|
2019-01-17 12:49:07 -05:00
|
|
|
context 'filtering by closed_at' do
|
|
|
|
let!(:closed_issue1) { create(:issue, project: project1, state: :closed, closed_at: 1.week.ago) }
|
|
|
|
let!(:closed_issue2) { create(:issue, project: project2, state: :closed, closed_at: 1.week.from_now) }
|
|
|
|
let!(:closed_issue3) { create(:issue, project: project2, state: :closed, closed_at: 2.weeks.from_now) }
|
|
|
|
|
|
|
|
context 'through closed_after' do
|
|
|
|
let(:params) { { state: :closed, closed_after: closed_issue3.closed_at } }
|
|
|
|
|
|
|
|
it 'returns issues closed on or after the given date' do
|
|
|
|
expect(issues).to contain_exactly(closed_issue3)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'through closed_before' do
|
|
|
|
let(:params) { { state: :closed, closed_before: closed_issue1.closed_at } }
|
|
|
|
|
|
|
|
it 'returns issues closed on or before the given date' do
|
|
|
|
expect(issues).to contain_exactly(closed_issue1)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'through closed_after and closed_before' do
|
|
|
|
let(:params) { { state: :closed, closed_after: closed_issue2.closed_at, closed_before: closed_issue3.closed_at } }
|
|
|
|
|
|
|
|
it 'returns issues closed between the given dates' do
|
|
|
|
expect(issues).to contain_exactly(closed_issue2, closed_issue3)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-08-30 03:48:55 -04:00
|
|
|
context 'filtering by reaction name' do
|
2018-10-26 23:18:31 -04:00
|
|
|
context 'user searches by no reaction' do
|
|
|
|
let(:params) { { my_reaction_emoji: 'None' } }
|
|
|
|
|
|
|
|
it 'returns issues that the user did not react to' do
|
2021-01-07 10:10:33 -05:00
|
|
|
expect(issues).to contain_exactly(issue2, issue4, issue5)
|
2018-10-26 23:18:31 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'user searches by any reaction' do
|
|
|
|
let(:params) { { my_reaction_emoji: 'Any' } }
|
|
|
|
|
|
|
|
it 'returns issues that the user reacted to' do
|
|
|
|
expect(issues).to contain_exactly(issue1, issue3)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-08-30 03:48:55 -04:00
|
|
|
context 'user searches by "thumbsup" reaction' do
|
|
|
|
let(:params) { { my_reaction_emoji: 'thumbsup' } }
|
|
|
|
|
|
|
|
it 'returns issues that the user thumbsup to' do
|
|
|
|
expect(issues).to contain_exactly(issue1)
|
|
|
|
end
|
2019-09-17 08:06:48 -04:00
|
|
|
|
|
|
|
context 'using NOT' do
|
|
|
|
let(:params) { { not: { my_reaction_emoji: 'thumbsup' } } }
|
|
|
|
|
|
|
|
it 'returns issues that the user did not thumbsup to' do
|
2021-01-07 10:10:33 -05:00
|
|
|
expect(issues).to contain_exactly(issue2, issue3, issue4, issue5)
|
2019-09-17 08:06:48 -04:00
|
|
|
end
|
|
|
|
end
|
2017-08-30 03:48:55 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
context 'user2 searches by "thumbsup" reaction' do
|
|
|
|
let(:search_user) { user2 }
|
|
|
|
|
|
|
|
let(:params) { { my_reaction_emoji: 'thumbsup' } }
|
|
|
|
|
|
|
|
it 'returns issues that the user2 thumbsup to' do
|
|
|
|
expect(issues).to contain_exactly(issue2)
|
|
|
|
end
|
2019-09-17 08:06:48 -04:00
|
|
|
|
|
|
|
context 'using NOT' do
|
|
|
|
let(:params) { { not: { my_reaction_emoji: 'thumbsup' } } }
|
|
|
|
|
|
|
|
it 'returns issues that the user2 thumbsup to' do
|
|
|
|
expect(issues).to contain_exactly(issue3)
|
|
|
|
end
|
|
|
|
end
|
2017-08-30 03:48:55 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
context 'user searches by "thumbsdown" reaction' do
|
|
|
|
let(:params) { { my_reaction_emoji: 'thumbsdown' } }
|
|
|
|
|
|
|
|
it 'returns issues that the user thumbsdown to' do
|
|
|
|
expect(issues).to contain_exactly(issue3)
|
|
|
|
end
|
2019-09-17 08:06:48 -04:00
|
|
|
|
|
|
|
context 'using NOT' do
|
|
|
|
let(:params) { { not: { my_reaction_emoji: 'thumbsdown' } } }
|
|
|
|
|
|
|
|
it 'returns issues that the user thumbsdown to' do
|
2021-01-07 10:10:33 -05:00
|
|
|
expect(issues).to contain_exactly(issue1, issue2, issue4, issue5)
|
2019-09-17 08:06:48 -04:00
|
|
|
end
|
|
|
|
end
|
2017-08-30 03:48:55 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-02-25 06:00:24 -05:00
|
|
|
context 'filtering by confidential' do
|
2020-02-16 22:09:00 -05:00
|
|
|
let_it_be(:confidential_issue) { create(:issue, project: project1, confidential: true) }
|
2019-02-25 06:00:24 -05:00
|
|
|
|
|
|
|
context 'no filtering' do
|
|
|
|
it 'returns all issues' do
|
2021-01-07 10:10:33 -05:00
|
|
|
expect(issues).to contain_exactly(issue1, issue2, issue3, issue4, issue5, confidential_issue)
|
2019-02-25 06:00:24 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'user filters confidential issues' do
|
|
|
|
let(:params) { { confidential: true } }
|
|
|
|
|
|
|
|
it 'returns only confdential issues' do
|
|
|
|
expect(issues).to contain_exactly(confidential_issue)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'user filters only public issues' do
|
|
|
|
let(:params) { { confidential: false } }
|
|
|
|
|
|
|
|
it 'returns only confdential issues' do
|
2021-01-07 10:10:33 -05:00
|
|
|
expect(issues).to contain_exactly(issue1, issue2, issue3, issue4, issue5)
|
2019-02-25 06:00:24 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-08-04 20:09:52 -04:00
|
|
|
context 'filtering by issue type' do
|
|
|
|
let_it_be(:incident_issue) { create(:incident, project: project1) }
|
|
|
|
|
|
|
|
context 'no type given' do
|
|
|
|
let(:params) { { issue_types: [] } }
|
|
|
|
|
|
|
|
it 'returns all issues' do
|
2021-01-07 10:10:33 -05:00
|
|
|
expect(issues).to contain_exactly(incident_issue, issue1, issue2, issue3, issue4, issue5)
|
2020-08-04 20:09:52 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'incident type' do
|
|
|
|
let(:params) { { issue_types: ['incident'] } }
|
|
|
|
|
|
|
|
it 'returns incident issues' do
|
|
|
|
expect(issues).to contain_exactly(incident_issue)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'issue type' do
|
|
|
|
let(:params) { { issue_types: ['issue'] } }
|
|
|
|
|
|
|
|
it 'returns all issues with type issue' do
|
2021-01-07 10:10:33 -05:00
|
|
|
expect(issues).to contain_exactly(issue1, issue2, issue3, issue4, issue5)
|
2020-08-04 20:09:52 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'multiple params' do
|
|
|
|
let(:params) { { issue_types: %w(issue incident) } }
|
|
|
|
|
|
|
|
it 'returns all issues' do
|
2021-01-07 10:10:33 -05:00
|
|
|
expect(issues).to contain_exactly(incident_issue, issue1, issue2, issue3, issue4, issue5)
|
2020-08-04 20:09:52 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'without array' do
|
|
|
|
let(:params) { { issue_types: 'incident' } }
|
|
|
|
|
|
|
|
it 'returns incident issues' do
|
|
|
|
expect(issues).to contain_exactly(incident_issue)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'invalid params' do
|
|
|
|
let(:params) { { issue_types: ['nonsense'] } }
|
|
|
|
|
|
|
|
it 'returns no issues' do
|
|
|
|
expect(issues).to eq(Issue.none)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-05-12 07:20:09 -04:00
|
|
|
context 'when the user is unauthorized' do
|
|
|
|
let(:search_user) { nil }
|
|
|
|
|
|
|
|
it 'returns no results' do
|
|
|
|
expect(issues).to be_empty
|
|
|
|
end
|
2014-12-05 10:25:22 -05:00
|
|
|
end
|
|
|
|
|
2016-05-12 07:20:09 -04:00
|
|
|
context 'when the user can see some, but not all, issues' do
|
|
|
|
let(:search_user) { user2 }
|
|
|
|
|
|
|
|
it 'returns only issues they can see' do
|
|
|
|
expect(issues).to contain_exactly(issue2, issue3)
|
|
|
|
end
|
2014-12-05 10:25:22 -05:00
|
|
|
end
|
2016-12-12 03:43:56 -05:00
|
|
|
|
|
|
|
it 'finds issues user can access due to group' do
|
|
|
|
group = create(:group)
|
2017-08-02 15:55:11 -04:00
|
|
|
project = create(:project, group: group)
|
2016-12-12 03:43:56 -05:00
|
|
|
issue = create(:issue, project: project)
|
|
|
|
group.add_user(user, :owner)
|
|
|
|
|
|
|
|
expect(issues).to include(issue)
|
|
|
|
end
|
2014-02-10 08:23:19 -05:00
|
|
|
end
|
|
|
|
|
2014-12-05 10:25:22 -05:00
|
|
|
context 'personal scope' do
|
2018-05-13 22:07:53 -04:00
|
|
|
let(:scope) { 'assigned_to_me' }
|
2016-05-12 07:20:09 -04:00
|
|
|
|
|
|
|
it 'returns issue assigned to the user' do
|
2021-01-07 10:10:33 -05:00
|
|
|
expect(issues).to contain_exactly(issue1, issue2, issue5)
|
2014-12-05 10:25:22 -05:00
|
|
|
end
|
|
|
|
|
2016-05-12 07:20:09 -04:00
|
|
|
context 'filtering by project' do
|
|
|
|
let(:params) { { project_id: project1.id } }
|
|
|
|
|
|
|
|
it 'returns issues assigned to the user in that project' do
|
2021-01-07 10:10:33 -05:00
|
|
|
expect(issues).to contain_exactly(issue1, issue5)
|
2016-05-12 07:20:09 -04:00
|
|
|
end
|
2014-12-05 10:25:22 -05:00
|
|
|
end
|
2014-02-10 08:23:19 -05:00
|
|
|
end
|
2016-12-12 03:43:56 -05:00
|
|
|
|
|
|
|
context 'when project restricts issues' do
|
|
|
|
let(:scope) { nil }
|
|
|
|
|
|
|
|
it "doesn't return team-only issues to non team members" do
|
2017-08-02 15:55:11 -04:00
|
|
|
project = create(:project, :public, :issues_private)
|
2016-12-12 03:43:56 -05:00
|
|
|
issue = create(:issue, project: project)
|
|
|
|
|
|
|
|
expect(issues).not_to include(issue)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "doesn't return issues if feature disabled" do
|
2018-01-24 01:06:24 -05:00
|
|
|
[project1, project2, project3].each do |project|
|
2016-12-12 03:43:56 -05:00
|
|
|
project.project_feature.update!(issues_access_level: ProjectFeature::DISABLED)
|
|
|
|
end
|
|
|
|
|
|
|
|
expect(issues.count).to eq 0
|
|
|
|
end
|
|
|
|
end
|
2019-04-09 11:38:58 -04:00
|
|
|
|
|
|
|
context 'external authorization' do
|
|
|
|
it_behaves_like 'a finder with external authorization service' do
|
|
|
|
let!(:subject) { create(:issue, project: project) }
|
|
|
|
let(:project_params) { { project_id: project.id } }
|
|
|
|
end
|
|
|
|
end
|
2020-11-13 10:09:24 -05:00
|
|
|
|
|
|
|
context 'filtering by due date' do
|
|
|
|
let_it_be(:issue_overdue) { create(:issue, project: project1, due_date: 2.days.ago) }
|
|
|
|
let_it_be(:issue_due_soon) { create(:issue, project: project1, due_date: 2.days.from_now) }
|
|
|
|
|
|
|
|
let(:scope) { 'all' }
|
|
|
|
let(:base_params) { { project_id: project1.id } }
|
|
|
|
|
|
|
|
context 'with param set to no due date' do
|
|
|
|
let(:params) { base_params.merge(due_date: Issue::NoDueDate.name) }
|
|
|
|
|
|
|
|
it 'returns issues with no due date' do
|
2021-01-07 10:10:33 -05:00
|
|
|
expect(issues).to contain_exactly(issue1, issue5)
|
2020-11-13 10:09:24 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with param set to overdue' do
|
|
|
|
let(:params) { base_params.merge(due_date: Issue::Overdue.name) }
|
|
|
|
|
|
|
|
it 'returns overdue issues' do
|
|
|
|
expect(issues).to contain_exactly(issue_overdue)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with param set to next month and previous two weeks' do
|
|
|
|
let(:params) { base_params.merge(due_date: Issue::DueNextMonthAndPreviousTwoWeeks.name) }
|
|
|
|
|
|
|
|
it 'returns issues from the previous two weeks and next month' do
|
|
|
|
expect(issues).to contain_exactly(issue_overdue, issue_due_soon)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with invalid param' do
|
|
|
|
let(:params) { base_params.merge(due_date: 'foo') }
|
|
|
|
|
|
|
|
it 'returns no issues' do
|
|
|
|
expect(issues).to be_empty
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2016-12-12 03:43:56 -05:00
|
|
|
end
|
|
|
|
|
2017-08-24 12:17:04 -04:00
|
|
|
describe '#row_count', :request_store do
|
2020-04-01 11:07:45 -04:00
|
|
|
let_it_be(:admin) { create(:admin) }
|
|
|
|
|
2021-01-08 10:10:26 -05:00
|
|
|
context 'when admin mode is enabled', :enable_admin_mode do
|
|
|
|
it 'returns the number of rows for the default state' do
|
|
|
|
finder = described_class.new(admin)
|
|
|
|
|
|
|
|
expect(finder.row_count).to eq(5)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns the number of rows for a given state' do
|
|
|
|
finder = described_class.new(admin, state: 'closed')
|
2017-08-24 12:17:04 -04:00
|
|
|
|
2021-01-08 10:10:26 -05:00
|
|
|
expect(finder.row_count).to be_zero
|
|
|
|
end
|
2017-08-24 12:17:04 -04:00
|
|
|
end
|
|
|
|
|
2021-01-08 10:10:26 -05:00
|
|
|
context 'when admin mode is disabled' do
|
|
|
|
it 'returns no rows' do
|
|
|
|
finder = described_class.new(admin)
|
2017-08-24 12:17:04 -04:00
|
|
|
|
2021-01-08 10:10:26 -05:00
|
|
|
expect(finder.row_count).to be_zero
|
|
|
|
end
|
2017-08-24 12:17:04 -04:00
|
|
|
end
|
2020-10-14 14:08:47 -04:00
|
|
|
|
|
|
|
it 'returns -1 if the query times out' do
|
|
|
|
finder = described_class.new(admin)
|
|
|
|
|
|
|
|
expect_next_instance_of(described_class) do |subfinder|
|
|
|
|
expect(subfinder).to receive(:execute).and_raise(ActiveRecord::QueryCanceled)
|
|
|
|
end
|
|
|
|
|
|
|
|
expect(finder.row_count).to eq(-1)
|
|
|
|
end
|
2017-08-24 12:17:04 -04:00
|
|
|
end
|
|
|
|
|
2017-06-29 07:43:56 -04:00
|
|
|
describe '#with_confidentiality_access_check' do
|
2017-06-20 11:38:14 -04:00
|
|
|
let(:guest) { create(:user) }
|
2019-12-17 13:07:48 -05:00
|
|
|
|
2020-02-16 22:09:00 -05:00
|
|
|
let_it_be(:authorized_user) { create(:user) }
|
|
|
|
let_it_be(:project) { create(:project, namespace: authorized_user.namespace) }
|
|
|
|
let_it_be(:public_issue) { create(:issue, project: project) }
|
|
|
|
let_it_be(:confidential_issue) { create(:issue, project: project, confidential: true) }
|
2017-06-20 11:38:14 -04:00
|
|
|
|
|
|
|
context 'when no project filter is given' do
|
|
|
|
let(:params) { {} }
|
|
|
|
|
|
|
|
context 'for an anonymous user' do
|
2017-06-29 07:43:56 -04:00
|
|
|
subject { described_class.new(nil, params).with_confidentiality_access_check }
|
2017-06-20 11:38:14 -04:00
|
|
|
|
|
|
|
it 'returns only public issues' do
|
|
|
|
expect(subject).to include(public_issue)
|
|
|
|
expect(subject).not_to include(confidential_issue)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'for a user without project membership' do
|
2017-06-29 07:43:56 -04:00
|
|
|
subject { described_class.new(user, params).with_confidentiality_access_check }
|
2017-06-20 11:38:14 -04:00
|
|
|
|
|
|
|
it 'returns only public issues' do
|
|
|
|
expect(subject).to include(public_issue)
|
|
|
|
expect(subject).not_to include(confidential_issue)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'for a guest user' do
|
2017-06-29 07:43:56 -04:00
|
|
|
subject { described_class.new(guest, params).with_confidentiality_access_check }
|
2017-06-20 11:38:14 -04:00
|
|
|
|
|
|
|
before do
|
|
|
|
project.add_guest(guest)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns only public issues' do
|
|
|
|
expect(subject).to include(public_issue)
|
|
|
|
expect(subject).not_to include(confidential_issue)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'for a project member with access to view confidential issues' do
|
2017-06-29 07:43:56 -04:00
|
|
|
subject { described_class.new(authorized_user, params).with_confidentiality_access_check }
|
2016-12-12 03:43:56 -05:00
|
|
|
|
2017-06-20 11:38:14 -04:00
|
|
|
it 'returns all issues' do
|
|
|
|
expect(subject).to include(public_issue, confidential_issue)
|
|
|
|
end
|
|
|
|
end
|
2019-03-19 13:25:10 -04:00
|
|
|
|
|
|
|
context 'for an admin' do
|
|
|
|
let(:admin_user) { create(:user, :admin) }
|
|
|
|
|
|
|
|
subject { described_class.new(admin_user, params).with_confidentiality_access_check }
|
|
|
|
|
2021-01-08 10:10:26 -05:00
|
|
|
context 'when admin mode is enabled', :enable_admin_mode do
|
|
|
|
it 'returns all issues' do
|
|
|
|
expect(subject).to include(public_issue, confidential_issue)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when admin mode is disabled' do
|
|
|
|
it 'returns only public issues' do
|
|
|
|
expect(subject).to include(public_issue)
|
|
|
|
expect(subject).not_to include(confidential_issue)
|
|
|
|
end
|
2019-03-19 13:25:10 -04:00
|
|
|
end
|
|
|
|
end
|
2016-12-12 03:43:56 -05:00
|
|
|
end
|
|
|
|
|
2017-06-20 11:38:14 -04:00
|
|
|
context 'when searching within a specific project' do
|
|
|
|
let(:params) { { project_id: project.id } }
|
|
|
|
|
|
|
|
context 'for an anonymous user' do
|
2017-06-29 07:43:56 -04:00
|
|
|
subject { described_class.new(nil, params).with_confidentiality_access_check }
|
2017-06-20 11:38:14 -04:00
|
|
|
|
|
|
|
it 'returns only public issues' do
|
|
|
|
expect(subject).to include(public_issue)
|
|
|
|
expect(subject).not_to include(confidential_issue)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not filter by confidentiality' do
|
|
|
|
expect(Issue).not_to receive(:where).with(a_string_matching('confidential'), anything)
|
|
|
|
|
|
|
|
subject
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'for a user without project membership' do
|
2017-06-29 07:43:56 -04:00
|
|
|
subject { described_class.new(user, params).with_confidentiality_access_check }
|
2017-06-20 11:38:14 -04:00
|
|
|
|
|
|
|
it 'returns only public issues' do
|
|
|
|
expect(subject).to include(public_issue)
|
|
|
|
expect(subject).not_to include(confidential_issue)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'filters by confidentiality' do
|
2019-05-07 07:08:25 -04:00
|
|
|
expect(subject.to_sql).to match("issues.confidential")
|
2017-06-20 11:38:14 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'for a guest user' do
|
2017-06-29 07:43:56 -04:00
|
|
|
subject { described_class.new(guest, params).with_confidentiality_access_check }
|
2017-06-20 11:38:14 -04:00
|
|
|
|
|
|
|
before do
|
|
|
|
project.add_guest(guest)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns only public issues' do
|
|
|
|
expect(subject).to include(public_issue)
|
|
|
|
expect(subject).not_to include(confidential_issue)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'filters by confidentiality' do
|
2019-05-07 07:08:25 -04:00
|
|
|
expect(subject.to_sql).to match("issues.confidential")
|
2017-06-20 11:38:14 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'for a project member with access to view confidential issues' do
|
2017-06-29 07:43:56 -04:00
|
|
|
subject { described_class.new(authorized_user, params).with_confidentiality_access_check }
|
2017-06-20 11:38:14 -04:00
|
|
|
|
|
|
|
it 'returns all issues' do
|
|
|
|
expect(subject).to include(public_issue, confidential_issue)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not filter by confidentiality' do
|
|
|
|
expect(Issue).not_to receive(:where).with(a_string_matching('confidential'), anything)
|
|
|
|
|
|
|
|
subject
|
|
|
|
end
|
|
|
|
end
|
2019-03-19 13:25:10 -04:00
|
|
|
|
|
|
|
context 'for an admin' do
|
|
|
|
let(:admin_user) { create(:user, :admin) }
|
|
|
|
|
|
|
|
subject { described_class.new(admin_user, params).with_confidentiality_access_check }
|
|
|
|
|
2021-01-08 10:10:26 -05:00
|
|
|
context 'when admin mode is enabled', :enable_admin_mode do
|
|
|
|
it 'returns all issues' do
|
|
|
|
expect(subject).to include(public_issue, confidential_issue)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not filter by confidentiality' do
|
|
|
|
expect(Issue).not_to receive(:where).with(a_string_matching('confidential'), anything)
|
|
|
|
|
|
|
|
subject
|
|
|
|
end
|
2019-03-19 13:25:10 -04:00
|
|
|
end
|
|
|
|
|
2021-01-08 10:10:26 -05:00
|
|
|
context 'when admin mode is disabled' do
|
|
|
|
it 'returns only public issues' do
|
|
|
|
expect(subject).to include(public_issue)
|
|
|
|
expect(subject).not_to include(confidential_issue)
|
|
|
|
end
|
2019-03-19 13:25:10 -04:00
|
|
|
|
2021-01-08 10:10:26 -05:00
|
|
|
it 'filters by confidentiality' do
|
|
|
|
expect(subject.to_sql).to match("issues.confidential")
|
|
|
|
end
|
2019-03-19 13:25:10 -04:00
|
|
|
end
|
|
|
|
end
|
2016-12-12 03:43:56 -05:00
|
|
|
end
|
2014-01-15 09:32:07 -05:00
|
|
|
end
|
2018-11-29 07:52:48 -05:00
|
|
|
|
Extend CTE search optimisation to projects
When we use the `search` param on an `IssuableFinder`, we can run into
issues. We have trigram indexes to support these searches. On
GitLab.com, we often see Postgres's optimiser prioritise the (global)
trigram indexes over the index on `project_id`. For group and project
searches, we know that it will be quicker to filter by `project_id`
first, as it returns fewer rows in most cases.
For group issues search, we ran into this issue previously, and went
through the following iterations:
1. Use a CTE on the project IDs as an optimisation fence. This prevents
the planner from disregarding the index on `project_id`.
Unfortunately it breaks some types of sorting, like priority and
popularity, as they sort on a joined table.
2. Use a subquery for listing issues, and a CTE for counts. The subquery
- in the case of group lists - didn't help as much as the CTE, but
was faster than not including it. We can safely use a CTE for counts
as they don't have sorting.
Now, however, we're seeing the same issue in a project context. The
subquery doesn't help at all there (it would only return one row, after
all). In an attempt to keep total code complexity under control, this
commit removes the subquery optimisation and applies the CTE
optimisation only for sorts we know that are safe.
This means that for more complicated sorts (like priority and
popularity), the search will continue to be very slow. If this is a
high-priority issue, we can consider introducing further optimisations,
but this finder is already very complicated and additional complexity
has a cost.
The group CTE optimisation is controlled by the same feature flag as
before, `attempt_group_search_optimizations`, which is enabled by
default. The new project CTE optimisation is controlled by a new feature
flag, `attempt_project_search_optimizations`, which is disabled by
default.
2019-04-03 05:46:13 -04:00
|
|
|
describe '#use_cte_for_search?' do
|
2018-11-29 07:52:48 -05:00
|
|
|
let(:finder) { described_class.new(nil, params) }
|
|
|
|
|
|
|
|
context 'when there is no search param' do
|
|
|
|
let(:params) { { attempt_group_search_optimizations: true } }
|
|
|
|
|
|
|
|
it 'returns false' do
|
Extend CTE search optimisation to projects
When we use the `search` param on an `IssuableFinder`, we can run into
issues. We have trigram indexes to support these searches. On
GitLab.com, we often see Postgres's optimiser prioritise the (global)
trigram indexes over the index on `project_id`. For group and project
searches, we know that it will be quicker to filter by `project_id`
first, as it returns fewer rows in most cases.
For group issues search, we ran into this issue previously, and went
through the following iterations:
1. Use a CTE on the project IDs as an optimisation fence. This prevents
the planner from disregarding the index on `project_id`.
Unfortunately it breaks some types of sorting, like priority and
popularity, as they sort on a joined table.
2. Use a subquery for listing issues, and a CTE for counts. The subquery
- in the case of group lists - didn't help as much as the CTE, but
was faster than not including it. We can safely use a CTE for counts
as they don't have sorting.
Now, however, we're seeing the same issue in a project context. The
subquery doesn't help at all there (it would only return one row, after
all). In an attempt to keep total code complexity under control, this
commit removes the subquery optimisation and applies the CTE
optimisation only for sorts we know that are safe.
This means that for more complicated sorts (like priority and
popularity), the search will continue to be very slow. If this is a
high-priority issue, we can consider introducing further optimisations,
but this finder is already very complicated and additional complexity
has a cost.
The group CTE optimisation is controlled by the same feature flag as
before, `attempt_group_search_optimizations`, which is enabled by
default. The new project CTE optimisation is controlled by a new feature
flag, `attempt_project_search_optimizations`, which is disabled by
default.
2019-04-03 05:46:13 -04:00
|
|
|
expect(finder.use_cte_for_search?).to be_falsey
|
2018-11-29 07:52:48 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
Extend CTE search optimisation to projects
When we use the `search` param on an `IssuableFinder`, we can run into
issues. We have trigram indexes to support these searches. On
GitLab.com, we often see Postgres's optimiser prioritise the (global)
trigram indexes over the index on `project_id`. For group and project
searches, we know that it will be quicker to filter by `project_id`
first, as it returns fewer rows in most cases.
For group issues search, we ran into this issue previously, and went
through the following iterations:
1. Use a CTE on the project IDs as an optimisation fence. This prevents
the planner from disregarding the index on `project_id`.
Unfortunately it breaks some types of sorting, like priority and
popularity, as they sort on a joined table.
2. Use a subquery for listing issues, and a CTE for counts. The subquery
- in the case of group lists - didn't help as much as the CTE, but
was faster than not including it. We can safely use a CTE for counts
as they don't have sorting.
Now, however, we're seeing the same issue in a project context. The
subquery doesn't help at all there (it would only return one row, after
all). In an attempt to keep total code complexity under control, this
commit removes the subquery optimisation and applies the CTE
optimisation only for sorts we know that are safe.
This means that for more complicated sorts (like priority and
popularity), the search will continue to be very slow. If this is a
high-priority issue, we can consider introducing further optimisations,
but this finder is already very complicated and additional complexity
has a cost.
The group CTE optimisation is controlled by the same feature flag as
before, `attempt_group_search_optimizations`, which is enabled by
default. The new project CTE optimisation is controlled by a new feature
flag, `attempt_project_search_optimizations`, which is disabled by
default.
2019-04-03 05:46:13 -04:00
|
|
|
context 'when the force_cte param is falsey' do
|
2018-11-29 07:52:48 -05:00
|
|
|
let(:params) { { search: 'foo' } }
|
|
|
|
|
|
|
|
it 'returns false' do
|
Extend CTE search optimisation to projects
When we use the `search` param on an `IssuableFinder`, we can run into
issues. We have trigram indexes to support these searches. On
GitLab.com, we often see Postgres's optimiser prioritise the (global)
trigram indexes over the index on `project_id`. For group and project
searches, we know that it will be quicker to filter by `project_id`
first, as it returns fewer rows in most cases.
For group issues search, we ran into this issue previously, and went
through the following iterations:
1. Use a CTE on the project IDs as an optimisation fence. This prevents
the planner from disregarding the index on `project_id`.
Unfortunately it breaks some types of sorting, like priority and
popularity, as they sort on a joined table.
2. Use a subquery for listing issues, and a CTE for counts. The subquery
- in the case of group lists - didn't help as much as the CTE, but
was faster than not including it. We can safely use a CTE for counts
as they don't have sorting.
Now, however, we're seeing the same issue in a project context. The
subquery doesn't help at all there (it would only return one row, after
all). In an attempt to keep total code complexity under control, this
commit removes the subquery optimisation and applies the CTE
optimisation only for sorts we know that are safe.
This means that for more complicated sorts (like priority and
popularity), the search will continue to be very slow. If this is a
high-priority issue, we can consider introducing further optimisations,
but this finder is already very complicated and additional complexity
has a cost.
The group CTE optimisation is controlled by the same feature flag as
before, `attempt_group_search_optimizations`, which is enabled by
default. The new project CTE optimisation is controlled by a new feature
flag, `attempt_project_search_optimizations`, which is disabled by
default.
2019-04-03 05:46:13 -04:00
|
|
|
expect(finder.use_cte_for_search?).to be_falsey
|
2018-11-29 07:52:48 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-09-08 05:08:31 -04:00
|
|
|
context 'when all conditions are met' do
|
|
|
|
context "uses group search optimization" do
|
|
|
|
let(:params) { { search: 'foo', attempt_group_search_optimizations: true } }
|
2018-11-29 07:52:48 -05:00
|
|
|
|
2020-09-08 05:08:31 -04:00
|
|
|
it 'returns true' do
|
|
|
|
expect(finder.use_cte_for_search?).to be_truthy
|
2021-05-17 08:10:23 -04:00
|
|
|
expect(finder.execute.to_sql).to match(/^WITH "issues" AS #{Gitlab::Database::AsWithMaterialized.materialized_if_supported}/)
|
Extend CTE search optimisation to projects
When we use the `search` param on an `IssuableFinder`, we can run into
issues. We have trigram indexes to support these searches. On
GitLab.com, we often see Postgres's optimiser prioritise the (global)
trigram indexes over the index on `project_id`. For group and project
searches, we know that it will be quicker to filter by `project_id`
first, as it returns fewer rows in most cases.
For group issues search, we ran into this issue previously, and went
through the following iterations:
1. Use a CTE on the project IDs as an optimisation fence. This prevents
the planner from disregarding the index on `project_id`.
Unfortunately it breaks some types of sorting, like priority and
popularity, as they sort on a joined table.
2. Use a subquery for listing issues, and a CTE for counts. The subquery
- in the case of group lists - didn't help as much as the CTE, but
was faster than not including it. We can safely use a CTE for counts
as they don't have sorting.
Now, however, we're seeing the same issue in a project context. The
subquery doesn't help at all there (it would only return one row, after
all). In an attempt to keep total code complexity under control, this
commit removes the subquery optimisation and applies the CTE
optimisation only for sorts we know that are safe.
This means that for more complicated sorts (like priority and
popularity), the search will continue to be very slow. If this is a
high-priority issue, we can consider introducing further optimisations,
but this finder is already very complicated and additional complexity
has a cost.
The group CTE optimisation is controlled by the same feature flag as
before, `attempt_group_search_optimizations`, which is enabled by
default. The new project CTE optimisation is controlled by a new feature
flag, `attempt_project_search_optimizations`, which is disabled by
default.
2019-04-03 05:46:13 -04:00
|
|
|
end
|
2018-11-29 07:52:48 -05:00
|
|
|
end
|
|
|
|
|
2020-09-08 05:08:31 -04:00
|
|
|
context "uses project search optimization" do
|
|
|
|
let(:params) { { search: 'foo', attempt_project_search_optimizations: true } }
|
2018-11-29 07:52:48 -05:00
|
|
|
|
Extend CTE search optimisation to projects
When we use the `search` param on an `IssuableFinder`, we can run into
issues. We have trigram indexes to support these searches. On
GitLab.com, we often see Postgres's optimiser prioritise the (global)
trigram indexes over the index on `project_id`. For group and project
searches, we know that it will be quicker to filter by `project_id`
first, as it returns fewer rows in most cases.
For group issues search, we ran into this issue previously, and went
through the following iterations:
1. Use a CTE on the project IDs as an optimisation fence. This prevents
the planner from disregarding the index on `project_id`.
Unfortunately it breaks some types of sorting, like priority and
popularity, as they sort on a joined table.
2. Use a subquery for listing issues, and a CTE for counts. The subquery
- in the case of group lists - didn't help as much as the CTE, but
was faster than not including it. We can safely use a CTE for counts
as they don't have sorting.
Now, however, we're seeing the same issue in a project context. The
subquery doesn't help at all there (it would only return one row, after
all). In an attempt to keep total code complexity under control, this
commit removes the subquery optimisation and applies the CTE
optimisation only for sorts we know that are safe.
This means that for more complicated sorts (like priority and
popularity), the search will continue to be very slow. If this is a
high-priority issue, we can consider introducing further optimisations,
but this finder is already very complicated and additional complexity
has a cost.
The group CTE optimisation is controlled by the same feature flag as
before, `attempt_group_search_optimizations`, which is enabled by
default. The new project CTE optimisation is controlled by a new feature
flag, `attempt_project_search_optimizations`, which is disabled by
default.
2019-04-03 05:46:13 -04:00
|
|
|
it 'returns true' do
|
|
|
|
expect(finder.use_cte_for_search?).to be_truthy
|
2021-05-17 08:10:23 -04:00
|
|
|
expect(finder.execute.to_sql).to match(/^WITH "issues" AS #{Gitlab::Database::AsWithMaterialized.materialized_if_supported}/)
|
Extend CTE search optimisation to projects
When we use the `search` param on an `IssuableFinder`, we can run into
issues. We have trigram indexes to support these searches. On
GitLab.com, we often see Postgres's optimiser prioritise the (global)
trigram indexes over the index on `project_id`. For group and project
searches, we know that it will be quicker to filter by `project_id`
first, as it returns fewer rows in most cases.
For group issues search, we ran into this issue previously, and went
through the following iterations:
1. Use a CTE on the project IDs as an optimisation fence. This prevents
the planner from disregarding the index on `project_id`.
Unfortunately it breaks some types of sorting, like priority and
popularity, as they sort on a joined table.
2. Use a subquery for listing issues, and a CTE for counts. The subquery
- in the case of group lists - didn't help as much as the CTE, but
was faster than not including it. We can safely use a CTE for counts
as they don't have sorting.
Now, however, we're seeing the same issue in a project context. The
subquery doesn't help at all there (it would only return one row, after
all). In an attempt to keep total code complexity under control, this
commit removes the subquery optimisation and applies the CTE
optimisation only for sorts we know that are safe.
This means that for more complicated sorts (like priority and
popularity), the search will continue to be very slow. If this is a
high-priority issue, we can consider introducing further optimisations,
but this finder is already very complicated and additional complexity
has a cost.
The group CTE optimisation is controlled by the same feature flag as
before, `attempt_group_search_optimizations`, which is enabled by
default. The new project CTE optimisation is controlled by a new feature
flag, `attempt_project_search_optimizations`, which is disabled by
default.
2019-04-03 05:46:13 -04:00
|
|
|
end
|
2018-11-29 07:52:48 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2020-09-18 08:09:50 -04:00
|
|
|
|
|
|
|
describe '#parent_param=' do
|
|
|
|
let(:finder) { described_class.new(nil) }
|
|
|
|
|
|
|
|
subject { finder.parent_param = obj }
|
|
|
|
|
|
|
|
where(:klass, :param) do
|
|
|
|
:Project | :project_id
|
|
|
|
:Group | :group_id
|
|
|
|
end
|
|
|
|
|
|
|
|
with_them do
|
|
|
|
let(:obj) { Object.const_get(klass, false).new }
|
|
|
|
|
|
|
|
it 'sets the params' do
|
|
|
|
subject
|
|
|
|
|
|
|
|
expect(finder.params[param]).to eq(obj)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'unexpected parent' do
|
|
|
|
let(:obj) { MergeRequest.new }
|
|
|
|
|
|
|
|
it 'raises an error' do
|
|
|
|
expect { subject }.to raise_error('Unexpected parent: MergeRequest')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2014-01-15 09:32:07 -05:00
|
|
|
end
|