Commit 5a84b5fd authored by Alfredo Sumaran's avatar Alfredo Sumaran

Merge branch 'add-filtered-search-to-mr' into 'master'

Add filtered search to MR page

Closes #27131

See merge request !9284
parents 4f6e067e 649c095a
...@@ -74,7 +74,7 @@ const ShortcutsBlob = require('./shortcuts_blob'); ...@@ -74,7 +74,7 @@ const ShortcutsBlob = require('./shortcuts_blob');
case 'projects:merge_requests:index': case 'projects:merge_requests:index':
case 'projects:issues:index': case 'projects:issues:index':
if (gl.FilteredSearchManager) { if (gl.FilteredSearchManager) {
new gl.FilteredSearchManager(); new gl.FilteredSearchManager(page === 'projects:issues:index' ? 'issues' : 'merge_requests');
} }
Issuable.init(); Issuable.init();
new gl.IssuableBulkActions({ new gl.IssuableBulkActions({
......
...@@ -37,23 +37,18 @@ require('./filtered_search_dropdown'); ...@@ -37,23 +37,18 @@ require('./filtered_search_dropdown');
} }
renderContent() { renderContent() {
const dropdownData = [{ const dropdownData = [];
icon: 'fa-pencil',
hint: 'author:', [].forEach.call(this.input.parentElement.querySelectorAll('.dropdown-menu'), (dropdownMenu) => {
tag: '<@author>', const { icon, hint, tag } = dropdownMenu.dataset;
}, { if (icon && hint && tag) {
icon: 'fa-user', dropdownData.push({
hint: 'assignee:', icon: `fa-${icon}`,
tag: '<@assignee>', hint,
}, { tag: `<${tag}>`,
icon: 'fa-clock-o', });
hint: 'milestone:', }
tag: '<%milestone>', });
}, {
icon: 'fa-tag',
hint: 'label:',
tag: '<~label>',
}];
this.droplab.changeHookList(this.hookId, this.dropdown, [droplabFilter], this.config); this.droplab.changeHookList(this.hookId, this.dropdown, [droplabFilter], this.config);
this.droplab.setData(this.hookId, dropdownData); this.droplab.setData(this.hookId, dropdownData);
......
...@@ -52,8 +52,9 @@ ...@@ -52,8 +52,9 @@
} }
renderContent(forceShowList = false) { renderContent(forceShowList = false) {
if (forceShowList && this.getCurrentHook().list.hidden) { const currentHook = this.getCurrentHook();
this.getCurrentHook().list.show(); if (forceShowList && currentHook && currentHook.list.hidden) {
currentHook.list.show();
} }
} }
...@@ -92,18 +93,24 @@ ...@@ -92,18 +93,24 @@
} }
hideDropdown() { hideDropdown() {
this.getCurrentHook().list.hide(); const currentHook = this.getCurrentHook();
if (currentHook) {
currentHook.list.hide();
}
} }
resetFilters() { resetFilters() {
const hook = this.getCurrentHook(); const hook = this.getCurrentHook();
const data = hook.list.data;
const results = data.map((o) => { if (hook) {
const updated = o; const data = hook.list.data;
updated.droplab_hidden = false; const results = data.map((o) => {
return updated; const updated = o;
}); updated.droplab_hidden = false;
hook.list.render(results); return updated;
});
hook.list.render(results);
}
} }
} }
......
...@@ -2,10 +2,12 @@ ...@@ -2,10 +2,12 @@
(() => { (() => {
class FilteredSearchDropdownManager { class FilteredSearchDropdownManager {
constructor(baseEndpoint = '') { constructor(baseEndpoint = '', page) {
this.baseEndpoint = baseEndpoint.replace(/\/$/, ''); this.baseEndpoint = baseEndpoint.replace(/\/$/, '');
this.tokenizer = gl.FilteredSearchTokenizer; this.tokenizer = gl.FilteredSearchTokenizer;
this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys;
this.filteredSearchInput = document.querySelector('.filtered-search'); this.filteredSearchInput = document.querySelector('.filtered-search');
this.page = page;
this.setupMapping(); this.setupMapping();
...@@ -150,7 +152,7 @@ ...@@ -150,7 +152,7 @@
this.droplab = new DropLab(); this.droplab = new DropLab();
} }
const match = gl.FilteredSearchTokenKeys.searchByKey(dropdownName.toLowerCase()); const match = this.filteredSearchTokenKeys.searchByKey(dropdownName.toLowerCase());
const shouldOpenFilterDropdown = match && this.currentDropdown !== match.key const shouldOpenFilterDropdown = match && this.currentDropdown !== match.key
&& this.mapping[match.key]; && this.mapping[match.key];
const shouldOpenHintDropdown = !match && this.currentDropdown !== 'hint'; const shouldOpenHintDropdown = !match && this.currentDropdown !== 'hint';
......
(() => { (() => {
class FilteredSearchManager { class FilteredSearchManager {
constructor() { constructor(page) {
this.filteredSearchInput = document.querySelector('.filtered-search'); this.filteredSearchInput = document.querySelector('.filtered-search');
this.clearSearchButton = document.querySelector('.clear-search'); this.clearSearchButton = document.querySelector('.clear-search');
this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys;
if (this.filteredSearchInput) { if (this.filteredSearchInput) {
this.tokenizer = gl.FilteredSearchTokenizer; this.tokenizer = gl.FilteredSearchTokenizer;
this.dropdownManager = new gl.FilteredSearchDropdownManager(this.filteredSearchInput.getAttribute('data-base-endpoint') || ''); this.dropdownManager = new gl.FilteredSearchDropdownManager(this.filteredSearchInput.getAttribute('data-base-endpoint') || '', page);
this.bindEvents(); this.bindEvents();
this.loadSearchParamsFromURL(); this.loadSearchParamsFromURL();
...@@ -117,8 +118,8 @@ ...@@ -117,8 +118,8 @@
const keyParam = decodeURIComponent(split[0]); const keyParam = decodeURIComponent(split[0]);
const value = split[1]; const value = split[1];
// Check if it matches edge conditions listed in gl.FilteredSearchTokenKeys // Check if it matches edge conditions listed in this.filteredSearchTokenKeys
const condition = gl.FilteredSearchTokenKeys.searchByConditionUrl(p); const condition = this.filteredSearchTokenKeys.searchByConditionUrl(p);
if (condition) { if (condition) {
inputValues.push(`${condition.tokenKey}:${condition.value}`); inputValues.push(`${condition.tokenKey}:${condition.value}`);
...@@ -126,7 +127,7 @@ ...@@ -126,7 +127,7 @@
// Sanitize value since URL converts spaces into + // Sanitize value since URL converts spaces into +
// Replace before decode so that we know what was originally + versus the encoded + // Replace before decode so that we know what was originally + versus the encoded +
const sanitizedValue = value ? decodeURIComponent(value.replace(/\+/g, ' ')) : value; const sanitizedValue = value ? decodeURIComponent(value.replace(/\+/g, ' ')) : value;
const match = gl.FilteredSearchTokenKeys.searchByKeyParam(keyParam); const match = this.filteredSearchTokenKeys.searchByKeyParam(keyParam);
if (match) { if (match) {
const indexOf = keyParam.indexOf('_'); const indexOf = keyParam.indexOf('_');
...@@ -171,9 +172,9 @@ ...@@ -171,9 +172,9 @@
paths.push(`state=${currentState}`); paths.push(`state=${currentState}`);
tokens.forEach((token) => { tokens.forEach((token) => {
const condition = gl.FilteredSearchTokenKeys const condition = this.filteredSearchTokenKeys
.searchByConditionKeyValue(token.key, token.value.toLowerCase()); .searchByConditionKeyValue(token.key, token.value.toLowerCase());
const { param } = gl.FilteredSearchTokenKeys.searchByKey(token.key) || {}; const { param } = this.filteredSearchTokenKeys.searchByKey(token.key) || {};
const keyParam = param ? `${token.key}_${param}` : token.key; const keyParam = param ? `${token.key}_${param}` : token.key;
let tokenPath = ''; let tokenPath = '';
......
...@@ -169,10 +169,10 @@ ...@@ -169,10 +169,10 @@
url: issuesPath + "/?author_username=" + userName url: issuesPath + "/?author_username=" + userName
}, 'separator', { }, 'separator', {
text: 'Merge requests assigned to me', text: 'Merge requests assigned to me',
url: mrPath + "/?assignee_id=" + userId url: mrPath + "/?assignee_username=" + userName
}, { }, {
text: "Merge requests I've created", text: "Merge requests I've created",
url: mrPath + "/?author_id=" + userId url: mrPath + "/?author_username=" + userName
} }
]; ];
if (!name) { if (!name) {
......
...@@ -50,6 +50,17 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -50,6 +50,17 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@labels = LabelsFinder.new(current_user, labels_params).execute @labels = LabelsFinder.new(current_user, labels_params).execute
end end
@users = []
if params[:assignee_id].present?
assignee = User.find_by_id(params[:assignee_id])
@users.push(assignee) if assignee
end
if params[:author_id].present?
author = User.find_by_id(params[:author_id])
@users.push(author) if author
end
respond_to do |format| respond_to do |format|
format.html format.html
format.json do format.json do
......
...@@ -5,18 +5,19 @@ ...@@ -5,18 +5,19 @@
= render "projects/issues/head" = render "projects/issues/head"
= render 'projects/last_push' = render 'projects/last_push'
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('filtered_search')
%div{ class: container_class } %div{ class: container_class }
.top-area .top-area
= render 'shared/issuable/nav', type: :merge_requests = render 'shared/issuable/nav', type: :merge_requests
.nav-controls .nav-controls
= render 'shared/issuable/search_form', path: namespace_project_merge_requests_path(@project.namespace, @project)
- merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project)) - merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
- if merge_project - if merge_project
= link_to new_namespace_project_merge_request_path(merge_project.namespace, merge_project), class: "btn btn-new", title: "New Merge Request" do = link_to new_namespace_project_merge_request_path(merge_project.namespace, merge_project), class: "btn btn-new", title: "New Merge Request" do
New Merge Request New Merge Request
= render 'shared/issuable/filter', type: :merge_requests = render 'shared/issuable/search_bar', type: :merge_requests
.merge-requests-holder .merge-requests-holder
= render 'merge_requests' = render 'merge_requests'
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
{{hint}} {{hint}}
%span.js-filter-tag.dropdown-light-content %span.js-filter-tag.dropdown-light-content
{{tag}} {{tag}}
#js-dropdown-author.dropdown-menu #js-dropdown-author.dropdown-menu{ data: { icon: 'pencil', hint: 'author', tag: '@author' } }
%ul.filter-dropdown{ 'data-dynamic' => true, 'data-dropdown' => true } %ul.filter-dropdown{ 'data-dynamic' => true, 'data-dropdown' => true }
%li.filter-dropdown-item %li.filter-dropdown-item
%button.btn.btn-link.dropdown-user %button.btn.btn-link.dropdown-user
...@@ -42,7 +42,7 @@ ...@@ -42,7 +42,7 @@
{{name}} {{name}}
%span.dropdown-light-content %span.dropdown-light-content
@{{username}} @{{username}}
#js-dropdown-assignee.dropdown-menu #js-dropdown-assignee.dropdown-menu{ data: { icon: 'user', hint: 'assignee', tag: '@assignee' } }
%ul{ 'data-dropdown' => true } %ul{ 'data-dropdown' => true }
%li.filter-dropdown-item{ 'data-value' => 'none' } %li.filter-dropdown-item{ 'data-value' => 'none' }
%button.btn.btn-link %button.btn.btn-link
...@@ -57,7 +57,7 @@ ...@@ -57,7 +57,7 @@
{{name}} {{name}}
%span.dropdown-light-content %span.dropdown-light-content
@{{username}} @{{username}}
#js-dropdown-milestone.dropdown-menu{ 'data-dropdown' => true } #js-dropdown-milestone.dropdown-menu{ data: { icon: 'clock-o', hint: 'milestone', tag: '%milestone' } }
%ul{ 'data-dropdown' => true } %ul{ 'data-dropdown' => true }
%li.filter-dropdown-item{ 'data-value' => 'none' } %li.filter-dropdown-item{ 'data-value' => 'none' }
%button.btn.btn-link %button.btn.btn-link
...@@ -70,7 +70,7 @@ ...@@ -70,7 +70,7 @@
%li.filter-dropdown-item %li.filter-dropdown-item
%button.btn.btn-link.js-data-value %button.btn.btn-link.js-data-value
{{title}} {{title}}
#js-dropdown-label.dropdown-menu{ 'data-dropdown' => true } #js-dropdown-label.dropdown-menu{ data: { icon: 'tag', hint: 'label', tag: '~label' } }
%ul{ 'data-dropdown' => true } %ul{ 'data-dropdown' => true }
%li.filter-dropdown-item{ 'data-value' => 'none' } %li.filter-dropdown-item{ 'data-value' => 'none' }
%button.btn.btn-link %button.btn.btn-link
......
---
title: Add filtered search to MR page
merge_request:
author:
...@@ -293,13 +293,6 @@ Feature: Project Merge Requests ...@@ -293,13 +293,6 @@ Feature: Project Merge Requests
And I preview a description text like "Bug fixed :smile:" And I preview a description text like "Bug fixed :smile:"
Then I should see the Markdown write tab Then I should see the Markdown write tab
@javascript
Scenario: I search merge request
Given I click link "All"
When I fill in merge request search with "Fe"
Then I should see "Feature NS-03" in merge requests
And I should not see "Bug NS-04" in merge requests
@javascript @javascript
Scenario: I can unsubscribe from merge request Scenario: I can unsubscribe from merge request
Given I visit merge request page "Bug NS-04" Given I visit merge request page "Bug NS-04"
......
require 'spec_helper' require 'spec_helper'
describe 'Dropdown label', js: true, feature: true do describe 'Dropdown label', js: true, feature: true do
include FilteredSearchHelpers
let(:project) { create(:empty_project) } let(:project) { create(:empty_project) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:filtered_search) { find('.filtered-search') } let(:filtered_search) { find('.filtered-search') }
...@@ -17,12 +19,6 @@ describe 'Dropdown label', js: true, feature: true do ...@@ -17,12 +19,6 @@ describe 'Dropdown label', js: true, feature: true do
let!(:long_label) { create(:label, project: project, title: 'this is a very long title this is a very long title this is a very long title this is a very long title this is a very long title') } let!(:long_label) { create(:label, project: project, title: 'this is a very long title this is a very long title this is a very long title this is a very long title this is a very long title') }
end end
def init_label_search
filtered_search.set('label:')
# This ensures the dropdown is shown
expect(find(js_dropdown_label)).not_to have_css('.filter-dropdown-loading')
end
def search_for_label(label) def search_for_label(label)
init_label_search init_label_search
filtered_search.send_keys(label) filtered_search.send_keys(label)
......
require 'rails_helper' require 'rails_helper'
describe 'Filter issues', js: true, feature: true do describe 'Filter issues', js: true, feature: true do
include FilteredSearchHelpers
include WaitForAjax include WaitForAjax
let!(:group) { create(:group) } let!(:group) { create(:group) }
...@@ -17,19 +18,6 @@ describe 'Filter issues', js: true, feature: true do ...@@ -17,19 +18,6 @@ describe 'Filter issues', js: true, feature: true do
let!(:multiple_words_label) { create(:label, project: project, title: "Two words") } let!(:multiple_words_label) { create(:label, project: project, title: "Two words") }
let!(:closed_issue) { create(:issue, title: 'bug that is closed', project: project, state: :closed) } let!(:closed_issue) { create(:issue, title: 'bug that is closed', project: project, state: :closed) }
let(:filtered_search) { find('.filtered-search') }
def input_filtered_search(search_term, submit: true)
filtered_search.set(search_term)
if submit
filtered_search.send_keys(:enter)
end
end
def expect_filtered_search_input(input)
expect(find('.filtered-search').value).to eq(input)
end
def expect_no_issues_list def expect_no_issues_list
page.within '.issues-list' do page.within '.issues-list' do
......
require 'rails_helper' require 'rails_helper'
feature 'Issue filtering by Labels', feature: true, js: true do feature 'Issue filtering by Labels', feature: true, js: true do
include FilteredSearchHelpers
include MergeRequestHelpers
include WaitForAjax include WaitForAjax
let(:project) { create(:project, :public) } let(:project) { create(:project, :public) }
...@@ -32,123 +34,77 @@ feature 'Issue filtering by Labels', feature: true, js: true do ...@@ -32,123 +34,77 @@ feature 'Issue filtering by Labels', feature: true, js: true do
context 'filter by label bug' do context 'filter by label bug' do
before do before do
select_labels('bug') input_filtered_search('label:~bug')
end end
it 'apply the filter' do it 'apply the filter' do
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 "Feature1" expect(page).not_to have_content "Feature1"
expect(find('.filtered-labels')).to have_content "bug"
expect(find('.filtered-labels')).not_to have_content "feature"
expect(find('.filtered-labels')).not_to have_content "enhancement"
find('.js-label-filter-remove').click
wait_for_ajax
expect(find('.filtered-labels', visible: false)).to have_no_content "bug"
end end
end end
context 'filter by label feature' do context 'filter by label feature' do
before do before do
select_labels('feature') input_filtered_search('label:~feature')
end end
it 'applies the filter' do it 'applies the filter' do
expect(page).to have_content "Feature1" expect(page).to have_content "Feature1"
expect(page).not_to have_content "Bugfix2" expect(page).not_to have_content "Bugfix2"
expect(page).not_to have_content "Bugfix1" expect(page).not_to have_content "Bugfix1"
expect(find('.filtered-labels')).to have_content "feature"
expect(find('.filtered-labels')).not_to have_content "bug"
expect(find('.filtered-labels')).not_to have_content "enhancement"
end end
end end
context 'filter by label enhancement' do context 'filter by label enhancement' do
before do before do
select_labels('enhancement') input_filtered_search('label:~enhancement')
end end
it 'applies the filter' do it 'applies the filter' do
expect(page).to have_content "Bugfix2" expect(page).to have_content "Bugfix2"
expect(page).not_to have_content "Feature1" expect(page).not_to have_content "Feature1"
expect(page).not_to have_content "Bugfix1" expect(page).not_to have_content "Bugfix1"
expect(find('.filtered-labels')).to have_content "enhancement"
expect(find('.filtered-labels')).not_to have_content "bug"
expect(find('.filtered-labels')).not_to have_content "feature"
end end
end end
context 'filter by label enhancement and bug in issues list' do context 'filter by label enhancement and bug in issues list' do
before do before do
select_labels('bug', 'enhancement') input_filtered_search('label:~bug label:~enhancement')
end end
it 'applies the filters' do it 'applies the filters' do
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 "Bugfix2"
expect(page).not_to have_content "Feature1" expect(page).not_to have_content "Feature1"
expect(find('.filtered-labels')).to have_content "bug"
expect(find('.filtered-labels')).to have_content "enhancement"
expect(find('.filtered-labels')).not_to have_content "feature"
find('.js-label-filter-remove', match: :first).click
wait_for_ajax
expect(page).to have_content "Bugfix2"
expect(page).not_to have_content "Feature1"
expect(page).not_to have_content "Bugfix1"
expect(find('.filtered-labels')).not_to have_content "bug"
expect(find('.filtered-labels')).to have_content "enhancement"
expect(find('.filtered-labels')).not_to have_content "feature"
end end
end end
context 'remove filtered labels' do context 'clear button' do
before do before do
page.within '.labels-filter' do input_filtered_search('label:~bug')
click_button 'Label'
wait_for_ajax
click_link 'bug'
find('.dropdown-menu-close').click
end
page.within '.filtered-labels' do
expect(page).to have_content 'bug'
end
end end
it 'allows user to remove filtered labels' do it 'allows user to remove filtered labels' do
first('.js-label-filter-remove').click first('.clear-search').click
wait_for_ajax filtered_search.send_keys(:enter)
expect(find('.filtered-labels', visible: false)).not_to have_content 'bug' expect(page).to have_issuable_counts(open: 3, closed: 0, all: 3)
expect(find('.labels-filter')).not_to have_content 'bug' expect(page).to have_content "Bugfix2"
expect(page).to have_content "Feature1"
expect(page).to have_content "Bugfix1"
end end
end end
context 'dropdown filtering' do context 'filter dropdown' do
it 'filters by label name' do it 'filters by label name' do
page.within '.labels-filter' do init_label_search
click_button 'Label' filtered_search.send_keys('~bug')
wait_for_ajax
find('.dropdown-input input').set 'bug'
page.within '.dropdown-content' do
expect(page).not_to have_content 'enhancement'
expect(page).to have_content 'bug'
end
end
end
end
def select_labels(*labels) page.within '.filter-dropdown' do
page.find('.js-label-select').click expect(page).not_to have_content 'enhancement'
wait_for_ajax expect(page).to have_content 'bug'
labels.each do |label| end
execute_script("$('.dropdown-menu-labels li:contains(\"#{label}\") a').click()")
end end
page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click
wait_for_ajax
end end
end end
require 'rails_helper' require 'rails_helper'
feature 'Merge Request filtering by Milestone', feature: true do feature 'Merge Request filtering by Milestone', feature: true do
include FilteredSearchHelpers
include MergeRequestHelpers
let(:project) { create(:project, :public) } let(:project) { create(:project, :public) }
let!(:user) { create(:user)} let!(:user) { create(:user)}
let(:milestone) { create(:milestone, project: project) } let(:milestone) { create(:milestone, project: project) }
def filter_by_milestone(title)
find(".js-milestone-select").click
find(".milestone-filter a", text: title).click
end
before do before do
project.team << [user, :master] project.team << [user, :master]
login_as(user) login_as(user)
...@@ -15,42 +23,42 @@ feature 'Merge Request filtering by Milestone', feature: true do ...@@ -15,42 +23,42 @@ feature 'Merge Request filtering by Milestone', feature: true do
create(:merge_request, :simple, source_project: project, milestone: milestone) create(:merge_request, :simple, source_project: project, milestone: milestone)
visit_merge_requests(project) visit_merge_requests(project)
filter_by_milestone(Milestone::None.title) input_filtered_search('milestone: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).to have_css('.merge-request', count: 1) expect(page).to have_css('.merge-request', count: 1)
end end
context 'filters by upcoming milestone', js: true do context 'filters by upcoming milestone', js: true do
it 'does not show issues with no expiry' do it 'does not show merge requests with no expiry' do
create(:merge_request, :with_diffs, source_project: project) create(:merge_request, :with_diffs, source_project: project)
create(:merge_request, :simple, source_project: project, milestone: milestone) create(:merge_request, :simple, source_project: project, milestone: milestone)
visit_merge_requests(project) visit_merge_requests(project)
filter_by_milestone(Milestone::Upcoming.title) input_filtered_search('milestone:upcoming')
expect(page).to have_css('.merge-request', count: 0) expect(page).to have_css('.merge-request', count: 0)
end end
it 'shows issues in future' do it 'shows merge requests in future' do
milestone = create(:milestone, project: project, due_date: Date.tomorrow) milestone = create(:milestone, project: project, due_date: Date.tomorrow)
create(:merge_request, :with_diffs, source_project: project) create(:merge_request, :with_diffs, source_project: project)
create(:merge_request, :simple, source_project: project, milestone: milestone) create(:merge_request, :simple, source_project: project, milestone: milestone)
visit_merge_requests(project) visit_merge_requests(project)
filter_by_milestone(Milestone::Upcoming.title) input_filtered_search('milestone:upcoming')
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_css('.merge-request', count: 1) expect(page).to have_css('.merge-request', count: 1)
end end
it 'does not show issues in past' do it 'does not show merge requests in past' do
milestone = create(:milestone, project: project, due_date: Date.yesterday) milestone = create(:milestone, project: project, due_date: Date.yesterday)
create(:merge_request, :with_diffs, source_project: project) create(:merge_request, :with_diffs, source_project: project)
create(:merge_request, :simple, source_project: project, milestone: milestone) create(:merge_request, :simple, source_project: project, milestone: milestone)
visit_merge_requests(project) visit_merge_requests(project)
filter_by_milestone(Milestone::Upcoming.title) input_filtered_search('milestone:upcoming')
expect(page).to have_css('.merge-request', count: 0) expect(page).to have_css('.merge-request', count: 0)
end end
...@@ -61,7 +69,7 @@ feature 'Merge Request filtering by Milestone', feature: true do ...@@ -61,7 +69,7 @@ feature 'Merge Request filtering by Milestone', feature: true do
create(:merge_request, :simple, source_project: project) create(:merge_request, :simple, source_project: project)
visit_merge_requests(project) visit_merge_requests(project)
filter_by_milestone(milestone.title) input_filtered_search("milestone:%'#{milestone.title}'")
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_css('.merge-request', count: 1) expect(page).to have_css('.merge-request', count: 1)
...@@ -77,19 +85,10 @@ feature 'Merge Request filtering by Milestone', feature: true do ...@@ -77,19 +85,10 @@ feature 'Merge Request filtering by Milestone', feature: true do
create(:merge_request, :simple, source_project: project) create(:merge_request, :simple, source_project: project)
visit_merge_requests(project) visit_merge_requests(project)
filter_by_milestone(milestone.title) input_filtered_search("milestone:%\"#{milestone.title}\"")
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_css('.merge-request', count: 1) expect(page).to have_css('.merge-request', count: 1)
end end
end end
def visit_merge_requests(project)
visit namespace_project_merge_requests_path(project.namespace, project)
end
def filter_by_milestone(title)
find(".js-milestone-select").click
find(".milestone-filter a", text: title).click
end
end end
require 'rails_helper' require 'rails_helper'
feature 'Issues filter reset button', feature: true, js: true do feature 'Issues filter reset button', feature: true, js: true do
include FilteredSearchHelpers
include MergeRequestHelpers
include WaitForAjax include WaitForAjax
include IssueHelpers include IssueHelpers
let!(:project) { create(:project, :public) } let!(:project) { create(:project, :public) }
let!(:user) { create(:user)} let!(:user) { create(:user) }
let!(:milestone) { create(:milestone, project: project) } let!(:milestone) { create(:milestone, project: project) }
let!(:bug) { create(:label, project: project, name: 'bug')} let!(:bug) { create(:label, project: project, name: 'bug')}
let!(:mr1) { create(:merge_request, title: "Feature", source_project: project, target_project: project, source_branch: "Feature", milestone: milestone, author: user, assignee: user) } let!(:mr1) { create(:merge_request, title: "Feature", source_project: project, target_project: project, source_branch: "Feature", milestone: milestone, author: user, assignee: user) }
let!(:mr2) { create(:merge_request, title: "Bugfix1", source_project: project, target_project: project, source_branch: "Bugfix1") } let!(:mr2) { create(:merge_request, title: "Bugfix1", source_project: project, target_project: project, source_branch: "Bugfix1") }
let(:merge_request_css) { '.merge-request' } let(:merge_request_css) { '.merge-request' }
let(:clear_search_css) { '.filtered-search-input-container .clear-search' }
before do before do
mr2.labels << bug mr2.labels << bug
...@@ -50,7 +53,7 @@ feature 'Issues filter reset button', feature: true, js: true do ...@@ -50,7 +53,7 @@ feature 'Issues filter reset button', feature: true, js: true do
context 'when author filter has been applied' do context 'when author filter has been applied' do
it 'resets the author filter' do it 'resets the author filter' do
visit_merge_requests(project, author_id: user.id) visit_merge_requests(project, author_username: user.username)
expect(page).to have_css(merge_request_css, count: 1) expect(page).to have_css(merge_request_css, count: 1)
reset_filters reset_filters
...@@ -60,7 +63,7 @@ feature 'Issues filter reset button', feature: true, js: true do ...@@ -60,7 +63,7 @@ feature 'Issues filter reset button', feature: true, js: true do
context 'when assignee filter has been applied' do context 'when assignee filter has been applied' do
it 'resets the assignee filter' do it 'resets the assignee filter' do
visit_merge_requests(project, assignee_id: user.id) visit_merge_requests(project, assignee_username: user.username)
expect(page).to have_css(merge_request_css, count: 1) expect(page).to have_css(merge_request_css, count: 1)
reset_filters reset_filters
...@@ -70,7 +73,7 @@ feature 'Issues filter reset button', feature: true, js: true do ...@@ -70,7 +73,7 @@ feature 'Issues filter reset button', feature: true, js: true do
context 'when all filters have been applied' do context 'when all filters have been applied' do
it 'resets all filters' do it 'resets all filters' do
visit_merge_requests(project, assignee_id: user.id, author_id: user.id, milestone_title: milestone.title, label_name: bug.name, search: 'Bug') visit_merge_requests(project, assignee_username: user.username, author_username: user.username, milestone_title: milestone.title, label_name: bug.name, search: 'Bug')
expect(page).to have_css(merge_request_css, count: 0) expect(page).to have_css(merge_request_css, count: 0)
reset_filters reset_filters
...@@ -82,15 +85,7 @@ feature 'Issues filter reset button', feature: true, js: true do ...@@ -82,15 +85,7 @@ feature 'Issues filter reset button', feature: true, js: true do
it 'the reset link should not be visible' do it 'the reset link should not be visible' do
visit_merge_requests(project) visit_merge_requests(project)
expect(page).to have_css(merge_request_css, count: 2) expect(page).to have_css(merge_request_css, count: 2)
expect(page).not_to have_css '.reset_filters' expect(page).not_to have_css(clear_search_css)
end end
end end
def visit_merge_requests(project, opts = {})
visit namespace_project_merge_requests_path project.namespace, project, opts
end
def reset_filters
find('.reset-filters').click
end
end end
...@@ -186,7 +186,7 @@ describe "Search", feature: true do ...@@ -186,7 +186,7 @@ describe "Search", feature: true do
sleep 2 sleep 2
expect(page).to have_selector('.merge-requests-holder') expect(page).to have_selector('.merge-requests-holder')
expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name) expect(find('.filtered-search').value).to eq("assignee:@#{user.username}")
end end
it 'takes user to her MR page when MR authored is clicked' do it 'takes user to her MR page when MR authored is clicked' do
...@@ -194,7 +194,7 @@ describe "Search", feature: true do ...@@ -194,7 +194,7 @@ describe "Search", feature: true do
sleep 2 sleep 2
expect(page).to have_selector('.merge-requests-holder') expect(page).to have_selector('.merge-requests-holder')
expect(find('.js-author-search .dropdown-toggle-text')).to have_content(user.name) expect(find('.filtered-search').value).to eq("author:@#{user.username}")
end end
end end
......
...@@ -89,8 +89,8 @@ require('vendor/fuzzaldrin-plus'); ...@@ -89,8 +89,8 @@ require('vendor/fuzzaldrin-plus');
var a1, a2, a3, a4, issuesAssignedToMeLink, issuesIHaveCreatedLink, mrsAssignedToMeLink, mrsIHaveCreatedLink; var a1, a2, a3, a4, issuesAssignedToMeLink, issuesIHaveCreatedLink, mrsAssignedToMeLink, mrsIHaveCreatedLink;
issuesAssignedToMeLink = issuesPath + "/?assignee_username=" + userName; issuesAssignedToMeLink = issuesPath + "/?assignee_username=" + userName;
issuesIHaveCreatedLink = issuesPath + "/?author_username=" + userName; issuesIHaveCreatedLink = issuesPath + "/?author_username=" + userName;
mrsAssignedToMeLink = mrsPath + "/?assignee_id=" + userId; mrsAssignedToMeLink = mrsPath + "/?assignee_username=" + userName;
mrsIHaveCreatedLink = mrsPath + "/?author_id=" + userId; mrsIHaveCreatedLink = mrsPath + "/?author_username=" + userName;
a1 = "a[href='" + issuesAssignedToMeLink + "']"; a1 = "a[href='" + issuesAssignedToMeLink + "']";
a2 = "a[href='" + issuesIHaveCreatedLink + "']"; a2 = "a[href='" + issuesIHaveCreatedLink + "']";
a3 = "a[href='" + mrsAssignedToMeLink + "']"; a3 = "a[href='" + mrsAssignedToMeLink + "']";
......
module FilteredSearchHelpers
def filtered_search
page.find('.filtered-search')
end
def input_filtered_search(search_term, submit: true)
filtered_search.set(search_term)
if submit
filtered_search.send_keys(:enter)
end
end
def input_filtered_search_keys(search_term)
filtered_search.send_keys(search_term)
filtered_search.send_keys(:enter)
end
def expect_filtered_search_input(input)
expect(find('.filtered-search').value).to eq(input)
end
def clear_search_field
find('.filtered-search-input-container .clear-search').click
end
def reset_filters
clear_search_field
filtered_search.send_keys(:enter)
end
def init_label_search
filtered_search.set('label:')
# This ensures the dropdown is shown
expect(find('#js-dropdown-label')).not_to have_css('.filter-dropdown-loading')
end
end
...@@ -10,4 +10,13 @@ module MergeRequestHelpers ...@@ -10,4 +10,13 @@ module MergeRequestHelpers
def last_merge_request def last_merge_request
page.all('ul.mr-list > li').last.text page.all('ul.mr-list > li').last.text
end end
def expect_mr_list_count(open_count, closed_count = 0)
all_count = open_count + closed_count
expect(page).to have_issuable_counts(open: open_count, closed: closed_count, all: all_count)
page.within '.mr-list' do
expect(page).to have_selector('.merge-request', count: open_count)
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