Commit cd42cccf authored by Phil Hughes's avatar Phil Hughes

Merge branch '39042-filter-merge-request-approved-by-user' into 'master'

Add "Approved By" in filtered MR search

Closes #39042

See merge request gitlab-org/gitlab!30335
parents 0cebbe86 27faa010
...@@ -74,6 +74,7 @@ ...@@ -74,6 +74,7 @@
user: User.new(username: '{{username}}', name: '{{name}}'), user: User.new(username: '{{username}}', name: '{{name}}'),
avatar: { lazy: true, url: '{{avatar_url}}' } avatar: { lazy: true, url: '{{avatar_url}}' }
= render_if_exists 'shared/issuable/approver_dropdown' = render_if_exists 'shared/issuable/approver_dropdown'
= render_if_exists 'shared/issuable/approved_by_dropdown'
#js-dropdown-milestone.filtered-search-input-dropdown-menu.dropdown-menu #js-dropdown-milestone.filtered-search-input-dropdown-menu.dropdown-menu
%ul{ data: { dropdown: true } } %ul{ data: { dropdown: true } }
%li.filter-dropdown-item{ data: { value: 'None' } } %li.filter-dropdown-item{ data: { value: 'None' } }
......
import addExtraTokensForMergeRequests from '~/filtered_search/add_extra_tokens_for_merge_requests'; import addExtraTokensForMergeRequests from '~/filtered_search/add_extra_tokens_for_merge_requests';
import { __ } from '~/locale'; import { __ } from '~/locale';
export default IssuableTokenKeys => { const approvers = {
addExtraTokensForMergeRequests(IssuableTokenKeys); condition: [
const approversConditions = [
{ {
url: 'approver_usernames[]=None', url: 'approver_usernames[]=None',
tokenKey: 'approver', tokenKey: 'approver',
...@@ -29,9 +27,8 @@ export default IssuableTokenKeys => { ...@@ -29,9 +27,8 @@ export default IssuableTokenKeys => {
value: __('Any'), value: __('Any'),
operator: '!=', operator: '!=',
}, },
]; ],
token: {
const approversToken = {
formattedKey: __('Approver'), formattedKey: __('Approver'),
key: 'approver', key: 'approver',
type: 'array', type: 'array',
...@@ -39,10 +36,54 @@ export default IssuableTokenKeys => { ...@@ -39,10 +36,54 @@ export default IssuableTokenKeys => {
symbol: '@', symbol: '@',
icon: 'approval', icon: 'approval',
tag: '@approver', tag: '@approver',
}; },
const approversTokenPosition = 2; };
const approvedBy = {
condition: [
{
url: 'approved_by_usernames[]=None',
tokenKey: 'approved-by',
value: __('None'),
operator: '=',
},
{
url: 'not[approved_by_usernames][]=None',
tokenKey: 'approved-by',
value: __('None'),
operator: '!=',
},
{
url: 'approved_by_usernames[]=Any',
tokenKey: 'approved-by',
value: __('Any'),
operator: '=',
},
{
url: 'not[approved_by_usernames][]=Any',
tokenKey: 'approved-by',
value: __('Any'),
operator: '!=',
},
],
token: {
formattedKey: __('Approved-By'),
key: 'approved-by',
type: 'array',
param: 'usernames[]',
symbol: '@',
icon: 'approval',
tag: '@approved-by',
},
};
export default IssuableTokenKeys => {
addExtraTokensForMergeRequests(IssuableTokenKeys);
const tokenPosition = 2;
const combinedTokens = [approvers.token, approvedBy.token];
const combinedConditions = [approvers.condition, approvedBy.condition];
IssuableTokenKeys.tokenKeys.splice(approversTokenPosition, 0, approversToken); IssuableTokenKeys.tokenKeys.splice(tokenPosition, 0, ...combinedTokens);
IssuableTokenKeys.tokenKeysWithAlternative.splice(approversTokenPosition, 0, approversToken); IssuableTokenKeys.tokenKeysWithAlternative.splice(tokenPosition, 0, ...combinedTokens);
IssuableTokenKeys.conditions.push(...approversConditions); IssuableTokenKeys.conditions.push(...combinedConditions);
}; };
...@@ -47,6 +47,12 @@ export default class AvailableDropdownMappings { ...@@ -47,6 +47,12 @@ export default class AvailableDropdownMappings {
element: this.container.querySelector('#js-dropdown-approver'), element: this.container.querySelector('#js-dropdown-approver'),
}; };
ceMappings['approved-by'] = {
reference: null,
gl: DropdownUser,
element: this.container.querySelector('#js-dropdown-approved-by'),
};
ceMappings.weight = { ceMappings.weight = {
reference: null, reference: null,
gl: DropdownWeight, gl: DropdownWeight,
......
/* eslint-disable import/prefer-default-export */ /* eslint-disable import/prefer-default-export */
import { USER_TOKEN_TYPES as CE_USER_TOKEN_TYPES } from '~/filtered_search/constants'; import { USER_TOKEN_TYPES as CE_USER_TOKEN_TYPES } from '~/filtered_search/constants';
export const USER_TOKEN_TYPES = [...CE_USER_TOKEN_TYPES, 'approver']; export const USER_TOKEN_TYPES = [...CE_USER_TOKEN_TYPES, 'approver', 'approved-by'];
#js-dropdown-approved-by.filtered-search-input-dropdown-menu.dropdown-menu
%ul{ data: { dropdown: true } }
%li.filter-dropdown-item{ data: { value: _('None') } }
%button.btn.btn-link{ type: 'button' }
= _('None')
%li.filter-dropdown-item{ data: { value: _('Any') } }
%button.btn.btn-link{ type: 'button' }
= _('Any')
%li.divider.droplab-item-ignore
- if current_user
= render 'shared/issuable/user_dropdown_item',
user: current_user
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
= render 'shared/issuable/user_dropdown_item',
user: User.new(username: '{{username}}', name: '{{name}}'),
avatar: { lazy: true, url: '{{avatar_url}}' }
---
title: Add Approved By in filtered MR search
merge_request: 30335
author:
type: added
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
describe 'Merge Requests > User filters by approvers', :js do describe 'Merge Requests > User filters', :js do
include FilteredSearchHelpers include FilteredSearchHelpers
let(:project) { create(:project, :public, :repository) } let(:project) { create(:project, :public, :repository) }
...@@ -30,68 +30,132 @@ describe 'Merge Requests > User filters by approvers', :js do ...@@ -30,68 +30,132 @@ describe 'Merge Requests > User filters by approvers', :js do
visit project_merge_requests_path(project) visit project_merge_requests_path(project)
end end
context 'filtering by approver:none' do context 'by "approvers"' do
it 'applies the filter' do context 'filtering by approver:none' do
input_filtered_search('approver:=none') it 'applies the filter' do
input_filtered_search('approver:=none')
expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
expect(page).not_to have_content 'Bugfix1' expect(page).not_to have_content 'Bugfix1'
expect(page).not_to have_content 'Bugfix2' expect(page).not_to have_content 'Bugfix2'
expect(page).not_to have_content 'Bugfix4' expect(page).not_to have_content 'Bugfix4'
expect(page).to have_content 'Bugfix3' expect(page).to have_content 'Bugfix3'
end
end end
end
context 'filtering by approver:any' do context 'filtering by approver:any' do
it 'applies the filter' do it 'applies the filter' do
input_filtered_search('approver:=any') input_filtered_search('approver:=any')
expect(page).to have_issuable_counts(open: 3, closed: 0, all: 3) expect(page).to have_issuable_counts(open: 3, closed: 0, all: 3)
expect(page).to have_content 'Bugfix1' expect(page).to have_content 'Bugfix1'
expect(page).to have_content 'Bugfix2' expect(page).to have_content 'Bugfix2'
expect(page).to have_content 'Bugfix4' expect(page).to have_content 'Bugfix4'
expect(page).not_to have_content 'Bugfix3' expect(page).not_to have_content 'Bugfix3'
end
end end
end
context 'filtering by approver:@username' do context 'filtering by approver:@username' do
it 'applies the filter' do it 'applies the filter' do
input_filtered_search("approver:=@#{first_user.username}") input_filtered_search("approver:=@#{first_user.username}")
expect(page).to have_issuable_counts(open: 2, closed: 0, all: 2) expect(page).to have_issuable_counts(open: 2, closed: 0, all: 2)
expect(page).to have_content 'Bugfix1' expect(page).to have_content 'Bugfix1'
expect(page).to have_content 'Bugfix2' expect(page).to have_content 'Bugfix2'
expect(page).not_to have_content 'Bugfix3' expect(page).not_to have_content 'Bugfix3'
expect(page).not_to have_content 'Bugfix4' expect(page).not_to have_content 'Bugfix4'
end
end
context 'filtering by multiple approvers' do
it 'applies the filter' do
input_filtered_search("approver:=@#{first_user.username} approver:=@#{user.username}")
expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
expect(page).to have_content 'Bugfix2'
expect(page).not_to have_content 'Bugfix1'
expect(page).not_to have_content 'Bugfix3'
expect(page).not_to have_content 'Bugfix4'
end
end end
end
context 'filtering by multiple approvers' do context 'filtering by an approver from a group' do
it 'applies the filter' do it 'applies the filter' do
input_filtered_search("approver:=@#{first_user.username} approver:=@#{user.username}") input_filtered_search("approver:=@#{group_user.username}")
expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
expect(page).to have_content 'Bugfix2' expect(page).to have_content 'Bugfix4'
expect(page).not_to have_content 'Bugfix1' expect(page).not_to have_content 'Bugfix1'
expect(page).not_to have_content 'Bugfix3' expect(page).not_to have_content 'Bugfix2'
expect(page).not_to have_content 'Bugfix4' expect(page).not_to have_content 'Bugfix3'
end
end end
end end
context 'filtering by an approver from a group' do context 'by "approved by"' do
it 'applies the filter' do let!(:merge_request_with_first_user_approval) do
input_filtered_search("approver:=@#{group_user.username}") create(:merge_request, source_project: project, title: 'Bugfix5').tap do |mr|
create(:approval, merge_request: mr, user: first_user)
end
end
let!(:merge_request_with_group_user_approved) do
group = create(:group)
group.add_developer(group_user)
create(:merge_request, source_project: project, title: 'Bugfix6', approval_groups: [group], source_branch: 'bugfix6').tap do |mr|
create(:approval, merge_request: mr, user: group_user)
end
end
context 'filtering by approved-by:none' do
it 'applies the filter' do
input_filtered_search('approved-by:=none')
expect(page).to have_issuable_counts(open: 4, closed: 0, all: 4)
expect(page).not_to have_content 'Bugfix5'
expect(page).to have_content 'Bugfix3'
end
end
context 'filtering by approved-by:any' do
it 'applies the filter' do
input_filtered_search('approved-by:=any')
expect(page).to have_issuable_counts(open: 2, closed: 0, all: 2)
expect(page).to have_content 'Bugfix5'
expect(page).not_to have_content 'Bugfix3'
end
end
context 'filtering by approved-by:@username' do
it 'applies the filter' do
input_filtered_search("approved-by:=@#{first_user.username}")
expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
expect(page).to have_content 'Bugfix5'
expect(page).not_to have_content 'Bugfix3'
end
end
context 'filtering by an approver from a group' do
it 'applies the filter' do
input_filtered_search("approved-by:=@#{group_user.username}")
expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
expect(page).to have_content 'Bugfix4' expect(page).to have_content 'Bugfix6'
expect(page).not_to have_content 'Bugfix1' expect(page).not_to have_content 'Bugfix5'
expect(page).not_to have_content 'Bugfix2' expect(page).not_to have_content 'Bugfix3'
expect(page).not_to have_content 'Bugfix3' end
end end
end end
end end
...@@ -2488,6 +2488,9 @@ msgstr "" ...@@ -2488,6 +2488,9 @@ msgstr ""
msgid "Approved the current merge request." msgid "Approved the current merge request."
msgstr "" msgstr ""
msgid "Approved-By"
msgstr ""
msgid "Approver" msgid "Approver"
msgstr "" msgstr ""
......
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