Commit f493f885 authored by Sean McGivern's avatar Sean McGivern

Merge branch 'assignees_filter_issues' into 'master'

Search for issues with multiple assignees

See merge request !1889
parents 3c287055 f7599ef0
...@@ -18,6 +18,13 @@ const weightConditions = [{ ...@@ -18,6 +18,13 @@ const weightConditions = [{
value: 'any', value: 'any',
}]; }];
const alternativeTokenKeys = [{
key: 'assignee',
type: 'string',
param: 'username',
symbol: '@',
}];
class FilteredSearchTokenKeysIssuesEE extends gl.FilteredSearchTokenKeys { class FilteredSearchTokenKeysIssuesEE extends gl.FilteredSearchTokenKeys {
static init(availableFeatures) { static init(availableFeatures) {
this.availableFeatures = availableFeatures; this.availableFeatures = availableFeatures;
...@@ -30,6 +37,7 @@ class FilteredSearchTokenKeysIssuesEE extends gl.FilteredSearchTokenKeys { ...@@ -30,6 +37,7 @@ class FilteredSearchTokenKeysIssuesEE extends gl.FilteredSearchTokenKeys {
if (this.availableFeatures && this.availableFeatures.multipleAssignees) { if (this.availableFeatures && this.availableFeatures.multipleAssignees) {
const assigneeTokenKey = tokenKeys.find(tk => tk.key === 'assignee'); const assigneeTokenKey = tokenKeys.find(tk => tk.key === 'assignee');
assigneeTokenKey.type = 'array'; assigneeTokenKey.type = 'array';
assigneeTokenKey.param = 'username[]';
} }
tokenKeys.push(weightTokenKey); tokenKeys.push(weightTokenKey);
...@@ -42,7 +50,7 @@ class FilteredSearchTokenKeysIssuesEE extends gl.FilteredSearchTokenKeys { ...@@ -42,7 +50,7 @@ class FilteredSearchTokenKeysIssuesEE extends gl.FilteredSearchTokenKeys {
} }
static getAlternatives() { static getAlternatives() {
return super.getAlternatives(); return alternativeTokenKeys.concat(super.getAlternatives());
} }
static getConditions() { static getConditions() {
...@@ -62,8 +70,8 @@ class FilteredSearchTokenKeysIssuesEE extends gl.FilteredSearchTokenKeys { ...@@ -62,8 +70,8 @@ class FilteredSearchTokenKeysIssuesEE extends gl.FilteredSearchTokenKeys {
static searchByKeyParam(keyParam) { static searchByKeyParam(keyParam) {
const tokenKeys = FilteredSearchTokenKeysIssuesEE.get(); const tokenKeys = FilteredSearchTokenKeysIssuesEE.get();
const alternativeTokenKeys = FilteredSearchTokenKeysIssuesEE.getAlternatives(); const alternatives = FilteredSearchTokenKeysIssuesEE.getAlternatives();
const tokenKeysWithAlternative = tokenKeys.concat(alternativeTokenKeys); const tokenKeysWithAlternative = tokenKeys.concat(alternatives);
return tokenKeysWithAlternative.find((tokenKey) => { return tokenKeysWithAlternative.find((tokenKey) => {
let tokenKeyParam = tokenKey.key; let tokenKeyParam = tokenKey.key;
......
...@@ -26,7 +26,7 @@ class IssuableFinder ...@@ -26,7 +26,7 @@ class IssuableFinder
IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page state].freeze IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page state].freeze
SCALAR_PARAMS = %i(scope state group_id project_id milestone_title assignee_id search label_name sort assignee_username author_id author_username authorized_only due_date iids non_archived weight).freeze SCALAR_PARAMS = %i(scope state group_id project_id milestone_title assignee_id search label_name sort assignee_username author_id author_username authorized_only due_date iids non_archived weight).freeze
ARRAY_PARAMS = { label_name: [], iids: [] }.freeze ARRAY_PARAMS = { label_name: [], iids: [], assignee_username: [] }.freeze
VALID_PARAMS = (SCALAR_PARAMS + [ARRAY_PARAMS]).freeze VALID_PARAMS = (SCALAR_PARAMS + [ARRAY_PARAMS]).freeze
attr_accessor :current_user, :params attr_accessor :current_user, :params
...@@ -309,18 +309,6 @@ class IssuableFinder ...@@ -309,18 +309,6 @@ class IssuableFinder
params[:sort] ? items.sort(params[:sort], excluded_labels: label_names) : items.reorder(id: :desc) params[:sort] ? items.sort(params[:sort], excluded_labels: label_names) : items.reorder(id: :desc)
end end
def by_assignee(items)
if assignee
items = items.where(assignee_id: assignee.id)
elsif no_assignee?
items = items.where(assignee_id: nil)
elsif assignee_id? || assignee_username? # assignee not found
items = items.none
end
items
end
def by_author(items) def by_author(items)
if author if author
items = items.where(author_id: author.id) items = items.where(author_id: author.id)
......
...@@ -95,7 +95,13 @@ class IssuesFinder < IssuableFinder ...@@ -95,7 +95,13 @@ class IssuesFinder < IssuableFinder
end end
def by_assignee(items) def by_assignee(items)
if assignee if assignees.any?
assignees.each do |assignee|
items = items.assigned_to(assignee)
end
items
elsif assignee && assignees.empty?
items.assigned_to(assignee) items.assigned_to(assignee)
elsif no_assignee? elsif no_assignee?
items.unassigned items.unassigned
...@@ -106,6 +112,19 @@ class IssuesFinder < IssuableFinder ...@@ -106,6 +112,19 @@ class IssuesFinder < IssuableFinder
end end
end end
def assignees
return @assignees if defined?(@assignees)
@assignees =
if params[:assignee_ids]
User.where(id: params[:assignee_ids])
elsif params[:assignee_username]
User.where(username: params[:assignee_username])
else
[]
end
end
def item_project_ids(items) def item_project_ids(items)
items&.reorder(nil)&.select(:project_id) items&.reorder(nil)&.select(:project_id)
end end
......
...@@ -24,6 +24,18 @@ class MergeRequestsFinder < IssuableFinder ...@@ -24,6 +24,18 @@ class MergeRequestsFinder < IssuableFinder
private private
def by_assignee(items)
if assignee
items = items.where(assignee_id: assignee.id)
elsif no_assignee?
items = items.where(assignee_id: nil)
elsif assignee_id? || assignee_username? # assignee not found
items = items.none
end
items
end
def item_project_ids(items) def item_project_ids(items)
items&.reorder(nil)&.select(:target_project_id) items&.reorder(nil)&.select(:target_project_id)
end end
......
---
title: Search for issues with multiple assignees
merge_request:
author:
...@@ -196,7 +196,17 @@ describe 'Filter issues', js: true do ...@@ -196,7 +196,17 @@ describe 'Filter issues', js: true do
end end
it 'filters issues by multiple assignees' do it 'filters issues by multiple assignees' do
skip('to be tested, issue #26546') create(:issue, project: project, author: user, assignees: [user2, user])
input_filtered_search("assignee:@#{user.username} assignee:@#{user2.username}")
expect_tokens([
assignee_token(user.name),
assignee_token(user2.name)
])
expect_issues_list_count(1)
expect_filtered_search_input_empty
end end
end end
......
...@@ -59,6 +59,36 @@ describe IssuesFinder do ...@@ -59,6 +59,36 @@ describe IssuesFinder do
end end
end end
context 'filtering by assignee IDs' do
set(:user3) { create(:user) }
let(:params) { { assignee_ids: [user2.id, user3.id] } }
before do
project2.add_developer(user3)
issue3.assignees = [user2, user3]
end
it 'returns issues assigned to those users' do
expect(issues).to contain_exactly(issue3)
end
end
context 'filtering by assignee usernames' do
set(:user3) { create(:user) }
let(:params) { { assignee_username: [user2.username, user3.username] } }
before do
project2.add_developer(user3)
issue3.assignees = [user2, user3]
end
it 'returns issues assigned to those users' do
expect(issues).to contain_exactly(issue3)
end
end
context 'filtering by author ID' do context 'filtering by author ID' do
let(:params) { { author_id: user2.id } } let(:params) { { author_id: user2.id } }
......
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