diff options
21 files changed, 282 insertions, 345 deletions
diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6 index 45aa6050aed..f55db02f0fd 100644 --- a/app/assets/javascripts/dispatcher.js.es6 +++ b/app/assets/javascripts/dispatcher.js.es6 @@ -74,7 +74,7 @@ const ShortcutsBlob = require('./shortcuts_blob'); case 'projects:merge_requests:index': case 'projects:issues:index': if (gl.FilteredSearchManager) { - new gl.FilteredSearchManager(); + new gl.FilteredSearchManager(page === 'projects:issues:index' ? 'issues' : 'merge_requests'); } Issuable.init(); new gl.IssuableBulkActions({ diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 index 572c221929a..9e92d544bef 100644 --- a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 @@ -37,23 +37,18 @@ require('./filtered_search_dropdown'); } renderContent() { - const dropdownData = [{ - icon: 'fa-pencil', - hint: 'author:', - tag: '<@author>', - }, { - icon: 'fa-user', - hint: 'assignee:', - tag: '<@assignee>', - }, { - icon: 'fa-clock-o', - hint: 'milestone:', - tag: '<%milestone>', - }, { - icon: 'fa-tag', - hint: 'label:', - tag: '<~label>', - }]; + const dropdownData = []; + + [].forEach.call(this.input.parentElement.querySelectorAll('.dropdown-menu'), (dropdownMenu) => { + const { icon, hint, tag } = dropdownMenu.dataset; + if (icon && hint && tag) { + dropdownData.push({ + icon: `fa-${icon}`, + hint, + tag: `<${tag}>`, + }); + } + }); this.droplab.changeHookList(this.hookId, this.dropdown, [droplabFilter], this.config); this.droplab.setData(this.hookId, dropdownData); diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 index e8c2df03a46..fbc72a3001a 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 @@ -52,8 +52,9 @@ } renderContent(forceShowList = false) { - if (forceShowList && this.getCurrentHook().list.hidden) { - this.getCurrentHook().list.show(); + const currentHook = this.getCurrentHook(); + if (forceShowList && currentHook && currentHook.list.hidden) { + currentHook.list.show(); } } @@ -92,18 +93,24 @@ } hideDropdown() { - this.getCurrentHook().list.hide(); + const currentHook = this.getCurrentHook(); + if (currentHook) { + currentHook.list.hide(); + } } resetFilters() { const hook = this.getCurrentHook(); - const data = hook.list.data; - const results = data.map((o) => { - const updated = o; - updated.droplab_hidden = false; - return updated; - }); - hook.list.render(results); + + if (hook) { + const data = hook.list.data; + const results = data.map((o) => { + const updated = o; + updated.droplab_hidden = false; + return updated; + }); + hook.list.render(results); + } } } diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 index 8ce4cf4fc36..cecd3518ce3 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 @@ -2,10 +2,12 @@ (() => { class FilteredSearchDropdownManager { - constructor(baseEndpoint = '') { + constructor(baseEndpoint = '', page) { this.baseEndpoint = baseEndpoint.replace(/\/$/, ''); this.tokenizer = gl.FilteredSearchTokenizer; + this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys; this.filteredSearchInput = document.querySelector('.filtered-search'); + this.page = page; this.setupMapping(); @@ -150,7 +152,7 @@ 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 && this.mapping[match.key]; const shouldOpenHintDropdown = !match && this.currentDropdown !== 'hint'; diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 13a9bf59246..bbafead0305 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -1,12 +1,13 @@ (() => { class FilteredSearchManager { - constructor() { + constructor(page) { this.filteredSearchInput = document.querySelector('.filtered-search'); this.clearSearchButton = document.querySelector('.clear-search'); + this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys; if (this.filteredSearchInput) { 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.loadSearchParamsFromURL(); @@ -117,8 +118,8 @@ const keyParam = decodeURIComponent(split[0]); const value = split[1]; - // Check if it matches edge conditions listed in gl.FilteredSearchTokenKeys - const condition = gl.FilteredSearchTokenKeys.searchByConditionUrl(p); + // Check if it matches edge conditions listed in this.filteredSearchTokenKeys + const condition = this.filteredSearchTokenKeys.searchByConditionUrl(p); if (condition) { inputValues.push(`${condition.tokenKey}:${condition.value}`); @@ -126,7 +127,7 @@ // Sanitize value since URL converts spaces into + // Replace before decode so that we know what was originally + versus the encoded + const sanitizedValue = value ? decodeURIComponent(value.replace(/\+/g, ' ')) : value; - const match = gl.FilteredSearchTokenKeys.searchByKeyParam(keyParam); + const match = this.filteredSearchTokenKeys.searchByKeyParam(keyParam); if (match) { const indexOf = keyParam.indexOf('_'); @@ -171,9 +172,9 @@ paths.push(`state=${currentState}`); tokens.forEach((token) => { - const condition = gl.FilteredSearchTokenKeys + const condition = this.filteredSearchTokenKeys .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; let tokenPath = ''; diff --git a/app/assets/javascripts/search_autocomplete.js.es6 b/app/assets/javascripts/search_autocomplete.js.es6 index 6250e75d407..6fd5345a0a6 100644 --- a/app/assets/javascripts/search_autocomplete.js.es6 +++ b/app/assets/javascripts/search_autocomplete.js.es6 @@ -169,10 +169,10 @@ url: issuesPath + "/?author_username=" + userName }, 'separator', { text: 'Merge requests assigned to me', - url: mrPath + "/?assignee_id=" + userId + url: mrPath + "/?assignee_username=" + userName }, { text: "Merge requests I've created", - url: mrPath + "/?author_id=" + userId + url: mrPath + "/?author_username=" + userName } ]; if (!name) { diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 2bf3542d089..75971faa93e 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -50,6 +50,17 @@ class Projects::MergeRequestsController < Projects::ApplicationController @labels = LabelsFinder.new(current_user, labels_params).execute 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| format.html format.json do diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml index 144b3a9c8c8..83e6c026ba7 100644 --- a/app/views/projects/merge_requests/index.html.haml +++ b/app/views/projects/merge_requests/index.html.haml @@ -5,18 +5,19 @@ = render "projects/issues/head" = render 'projects/last_push' +- content_for :page_specific_javascripts do + = page_specific_javascript_bundle_tag('filtered_search') + %div{ class: container_class } .top-area = render 'shared/issuable/nav', type: :merge_requests .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)) - 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 New Merge Request - = render 'shared/issuable/filter', type: :merge_requests + = render 'shared/issuable/search_bar', type: :merge_requests .merge-requests-holder = render 'merge_requests' diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index 6e417aa2251..8e04b50bb8a 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -32,7 +32,7 @@ {{hint}} %span.js-filter-tag.dropdown-light-content {{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 } %li.filter-dropdown-item %button.btn.btn-link.dropdown-user @@ -42,7 +42,7 @@ {{name}} %span.dropdown-light-content @{{username}} - #js-dropdown-assignee.dropdown-menu + #js-dropdown-assignee.dropdown-menu{ data: { icon: 'user', hint: 'assignee', tag: '@assignee' } } %ul{ 'data-dropdown' => true } %li.filter-dropdown-item{ 'data-value' => 'none' } %button.btn.btn-link @@ -57,7 +57,7 @@ {{name}} %span.dropdown-light-content @{{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 } %li.filter-dropdown-item{ 'data-value' => 'none' } %button.btn.btn-link @@ -70,7 +70,7 @@ %li.filter-dropdown-item %button.btn.btn-link.js-data-value {{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 } %li.filter-dropdown-item{ 'data-value' => 'none' } %button.btn.btn-link diff --git a/changelogs/unreleased/add-filtered-search-to-mr.yml b/changelogs/unreleased/add-filtered-search-to-mr.yml new file mode 100644 index 00000000000..e3577e2aec7 --- /dev/null +++ b/changelogs/unreleased/add-filtered-search-to-mr.yml @@ -0,0 +1,4 @@ +--- +title: Add filtered search to MR page +merge_request: +author: diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature index 5aa592e9067..bcde497553b 100644 --- a/features/project/merge_requests.feature +++ b/features/project/merge_requests.feature @@ -294,13 +294,6 @@ Feature: Project Merge Requests 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 Scenario: I can unsubscribe from merge request Given I visit merge request page "Bug NS-04" Then I should see that I am subscribed diff --git a/spec/features/issues/filtered_search/dropdown_label_spec.rb b/spec/features/issues/filtered_search/dropdown_label_spec.rb index c6a88e1b7b0..ab3b868fd3a 100644 --- a/spec/features/issues/filtered_search/dropdown_label_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_label_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe 'Dropdown label', js: true, feature: true do + include FilteredSearchHelpers + let(:project) { create(:empty_project) } let(:user) { create(:user) } let(:filtered_search) { find('.filtered-search') } @@ -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') } 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) init_label_search filtered_search.send_keys(label) diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb index 64f448a83b7..0420e64d42c 100644 --- a/spec/features/issues/filtered_search/filter_issues_spec.rb +++ b/spec/features/issues/filtered_search/filter_issues_spec.rb @@ -1,6 +1,7 @@ require 'rails_helper' describe 'Filter issues', js: true, feature: true do + include FilteredSearchHelpers include WaitForAjax let!(:group) { create(:group) } @@ -17,19 +18,6 @@ describe 'Filter issues', js: true, feature: true do 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(: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 page.within '.issues-list' do diff --git a/spec/features/merge_requests/filter_by_labels_spec.rb b/spec/features/merge_requests/filter_by_labels_spec.rb index 4c60329865c..55f3c1863ff 100644 --- a/spec/features/merge_requests/filter_by_labels_spec.rb +++ b/spec/features/merge_requests/filter_by_labels_spec.rb @@ -1,6 +1,8 @@ require 'rails_helper' feature 'Issue filtering by Labels', feature: true, js: true do + include FilteredSearchHelpers + include MergeRequestHelpers include WaitForAjax let(:project) { create(:project, :public) } @@ -32,123 +34,77 @@ feature 'Issue filtering by Labels', feature: true, js: true do context 'filter by label bug' do before do - select_labels('bug') + input_filtered_search('label:~bug') end it 'apply the filter' do expect(page).to have_content "Bugfix1" expect(page).to have_content "Bugfix2" 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 context 'filter by label feature' do before do - select_labels('feature') + input_filtered_search('label:~feature') end it 'applies the filter' do expect(page).to have_content "Feature1" expect(page).not_to have_content "Bugfix2" 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 context 'filter by label enhancement' do before do - select_labels('enhancement') + input_filtered_search('label:~enhancement') end it 'applies the filter' do expect(page).to have_content "Bugfix2" expect(page).not_to have_content "Feature1" 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 context 'filter by label enhancement and bug in issues list' do before do - select_labels('bug', 'enhancement') + input_filtered_search('label:~bug label:~enhancement') end it 'applies the filters' do expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) expect(page).to have_content "Bugfix2" 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 - context 'remove filtered labels' do + context 'clear button' do before do - page.within '.labels-filter' do - 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 + input_filtered_search('label:~bug') end it 'allows user to remove filtered labels' do - first('.js-label-filter-remove').click - wait_for_ajax + first('.clear-search').click + filtered_search.send_keys(:enter) - expect(find('.filtered-labels', visible: false)).not_to have_content 'bug' - expect(find('.labels-filter')).not_to have_content 'bug' + expect(page).to have_issuable_counts(open: 3, closed: 0, all: 3) + expect(page).to have_content "Bugfix2" + expect(page).to have_content "Feature1" + expect(page).to have_content "Bugfix1" end end - context 'dropdown filtering' do + context 'filter dropdown' do it 'filters by label name' do - page.within '.labels-filter' do - click_button 'Label' - 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 + init_label_search + filtered_search.send_keys('~bug') - def select_labels(*labels) - page.find('.js-label-select').click - wait_for_ajax - labels.each do |label| - execute_script("$('.dropdown-menu-labels li:contains(\"#{label}\") a').click()") + page.within '.filter-dropdown' do + expect(page).not_to have_content 'enhancement' + expect(page).to have_content 'bug' + end end - page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click - wait_for_ajax end end diff --git a/spec/features/merge_requests/filter_by_milestone_spec.rb b/spec/features/merge_requests/filter_by_milestone_spec.rb index f6e9230c8da..5608cda28f8 100644 --- a/spec/features/merge_requests/filter_by_milestone_spec.rb +++ b/spec/features/merge_requests/filter_by_milestone_spec.rb @@ -1,10 +1,18 @@ require 'rails_helper' feature 'Merge Request filtering by Milestone', feature: true do + include FilteredSearchHelpers + include MergeRequestHelpers + let(:project) { create(:project, :public) } let!(:user) { create(:user)} 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 project.team << [user, :master] login_as(user) @@ -15,42 +23,42 @@ feature 'Merge Request filtering by Milestone', feature: true do create(:merge_request, :simple, source_project: project, milestone: milestone) 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_css('.merge-request', count: 1) end 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, :simple, source_project: project, milestone: milestone) visit_merge_requests(project) - filter_by_milestone(Milestone::Upcoming.title) + input_filtered_search('milestone:upcoming') expect(page).to have_css('.merge-request', count: 0) end - it 'shows issues in future' do + it 'shows merge requests in future' do milestone = create(:milestone, project: project, due_date: Date.tomorrow) create(:merge_request, :with_diffs, source_project: project) create(:merge_request, :simple, source_project: project, milestone: milestone) 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_css('.merge-request', count: 1) 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) create(:merge_request, :with_diffs, source_project: project) create(:merge_request, :simple, source_project: project, milestone: milestone) visit_merge_requests(project) - filter_by_milestone(Milestone::Upcoming.title) + input_filtered_search('milestone:upcoming') expect(page).to have_css('.merge-request', count: 0) end @@ -61,7 +69,7 @@ feature 'Merge Request filtering by Milestone', feature: true do create(:merge_request, :simple, source_project: 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_css('.merge-request', count: 1) @@ -77,19 +85,10 @@ feature 'Merge Request filtering by Milestone', feature: true do create(:merge_request, :simple, source_project: 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_css('.merge-request', count: 1) 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 diff --git a/spec/features/merge_requests/filter_merge_requests_spec.rb b/spec/features/merge_requests/filter_merge_requests_spec.rb index 4642b5a530d..6579a88d4ab 100644 --- a/spec/features/merge_requests/filter_merge_requests_spec.rb +++ b/spec/features/merge_requests/filter_merge_requests_spec.rb @@ -1,11 +1,13 @@ require 'rails_helper' describe 'Filter merge requests', feature: true do + include FilteredSearchHelpers + include MergeRequestHelpers include WaitForAjax let!(:project) { create(:project) } let!(:group) { create(:group) } - let!(:user) { create(:user)} + let!(:user) { create(:user) } let!(:milestone) { create(:milestone, project: project) } let!(:label) { create(:label, project: project) } let!(:wontfix) { create(:label, project: project, title: "Won't fix") } @@ -15,183 +17,134 @@ describe 'Filter merge requests', feature: true do group.add_developer(user) login_as(user) create(:merge_request, source_project: project, target_project: project) + + visit namespace_project_merge_requests_path(project.namespace, project) end describe 'for assignee from mr#index' do - before do - visit namespace_project_merge_requests_path(project.namespace, project) + let(:search_query) { "assignee:@#{user.username}" } - find('.js-assignee-search').click - - find('.dropdown-menu-user-link', text: user.username).click + before do + input_filtered_search(search_query) - wait_for_ajax + expect_mr_list_count(0) end context 'assignee', js: true do it 'updates to current user' do - expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name) + expect_filtered_search_input(search_query) end it 'does not change when closed link is clicked' do find('.issues-state-filters a', text: "Closed").click - expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name) + expect_filtered_search_input(search_query) end it 'does not change when all link is clicked' do find('.issues-state-filters a', text: "All").click - expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name) + expect_filtered_search_input(search_query) end end end describe 'for milestone from mr#index' do - before do - visit namespace_project_merge_requests_path(project.namespace, project) - - find('.js-milestone-select').click + let(:search_query) { "milestone:%#{milestone.title}" } - find('.milestone-filter .dropdown-content a', text: milestone.title).click + before do + input_filtered_search(search_query) - wait_for_ajax + expect_mr_list_count(0) end context 'milestone', js: true do it 'updates to current milestone' do - expect(find('.js-milestone-select .dropdown-toggle-text')).to have_content(milestone.title) + expect_filtered_search_input(search_query) end it 'does not change when closed link is clicked' do find('.issues-state-filters a', text: "Closed").click - expect(find('.js-milestone-select .dropdown-toggle-text')).to have_content(milestone.title) + expect_filtered_search_input(search_query) end it 'does not change when all link is clicked' do find('.issues-state-filters a', text: "All").click - expect(find('.js-milestone-select .dropdown-toggle-text')).to have_content(milestone.title) + expect_filtered_search_input(search_query) end end end describe 'for label from mr#index', js: true do - before do - visit namespace_project_merge_requests_path(project.namespace, project) - find('.js-label-select').click - wait_for_ajax - end - - it 'filters by any label' do - find('.dropdown-menu-labels a', text: 'Any Label').click - page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click - wait_for_ajax - - expect(find('.labels-filter')).to have_content 'Label' - end - it 'filters by no label' do - find('.dropdown-menu-labels a', text: 'No Label').click - page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click - wait_for_ajax + input_filtered_search('label:none') - page.within '.labels-filter' do - expect(page).to have_content 'Labels' - end - expect(find('.js-label-select .dropdown-toggle-text')).to have_content('Labels') + expect_mr_list_count(1) + expect_filtered_search_input('label:none') end it 'filters by a label' do - find('.dropdown-menu-labels a', text: label.title).click - page.within '.labels-filter' do - expect(page).to have_content label.title - end - expect(find('.js-label-select .dropdown-toggle-text')).to have_content(label.title) + input_filtered_search("label:~#{label.title}") + + expect_mr_list_count(0) + expect_filtered_search_input("label:~#{label.title}") end it "filters by `won't fix` and another label" do - page.within '.labels-filter' do - click_link wontfix.title - expect(page).to have_content wontfix.title - click_link label.title - end + input_filtered_search("label:~\"#{wontfix.title}\" label:~#{label.title}") - expect(find('.js-label-select .dropdown-toggle-text')).to have_content("#{wontfix.title} +1 more") + expect_mr_list_count(0) + expect_filtered_search_input("label:~\"#{wontfix.title}\" label:~#{label.title}") end it "filters by `won't fix` label followed by another label after page load" do - page.within '.labels-filter' do - click_link wontfix.title - expect(page).to have_content wontfix.title - end - - find('body').click - - expect(find('.filtered-labels')).to have_content(wontfix.title) - - find('.js-label-select').click - wait_for_ajax - find('.dropdown-menu-labels a', text: label.title).click - - find('body').click + input_filtered_search("label:~\"#{wontfix.title}\"") - expect(find('.filtered-labels')).to have_content(wontfix.title) - expect(find('.filtered-labels')).to have_content(label.title) + expect_mr_list_count(0) + expect_filtered_search_input("label:~\"#{wontfix.title}\"") - find('.js-label-select').click - wait_for_ajax + input_filtered_search_keys(" label:~#{label.title}") - expect(find('.dropdown-menu-labels li', text: wontfix.title)).to have_css('.is-active') - expect(find('.dropdown-menu-labels li', text: label.title)).to have_css('.is-active') - end + expect_filtered_search_input("label:~\"#{wontfix.title}\" label:~#{label.title}") - it "selects and unselects `won't fix`" do - find('.dropdown-menu-labels a', text: wontfix.title).click - find('.dropdown-menu-labels a', text: wontfix.title).click - # Close label dropdown to load - find('body').click - expect(page).not_to have_css('.filtered-labels') + expect_mr_list_count(0) + expect_filtered_search_input("label:~\"#{wontfix.title}\" label:~#{label.title}") end end describe 'for assignee and label from issues#index' do - before do - visit namespace_project_merge_requests_path(project.namespace, project) - - find('.js-assignee-search').click + let(:search_query) { "assignee:@#{user.username} label:~#{label.title}" } - find('.dropdown-menu-user-link', text: user.username).click + before do + input_filtered_search("assignee:@#{user.username}") - expect(page).not_to have_selector('.mr-list .merge-request') + expect_mr_list_count(1) + expect_filtered_search_input("assignee:@#{user.username}") - find('.js-label-select').click + input_filtered_search_keys(" label:~#{label.title}") - find('.dropdown-menu-labels .dropdown-content a', text: label.title).click - page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click + expect_mr_list_count(1) - wait_for_ajax + find("#state-opened[href=\"#{URI.parse(current_url).path}?assignee_username=#{user.username}&label_name%5B%5D=#{label.title}&scope=all&state=opened\"]") end context 'assignee and label', js: true do it 'updates to current assignee and label' do - expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name) - expect(find('.js-label-select .dropdown-toggle-text')).to have_content(label.title) + expect_filtered_search_input(search_query) end it 'does not change when closed link is clicked' do find('.issues-state-filters a', text: "Closed").click - expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name) - expect(find('.js-label-select .dropdown-toggle-text')).to have_content(label.title) + expect_filtered_search_input(search_query) end it 'does not change when all link is clicked' do find('.issues-state-filters a', text: "All").click - expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name) - expect(find('.js-label-select .dropdown-toggle-text')).to have_content(label.title) + expect_filtered_search_input(search_query) end end end @@ -203,11 +156,11 @@ describe 'Filter merge requests', feature: true do bug_label = create(:label, project: project, title: 'bug') milestone = create(:milestone, title: "8", project: project) - mr = create(:merge_request, - title: "Bug 2", - source_project: project, - target_project: project, - source_branch: "bug2", + mr = create(:merge_request, + title: "Bug 2", + source_project: project, + target_project: project, + source_branch: "bug2", milestone: milestone, author: user, assignee: user) @@ -218,15 +171,13 @@ describe 'Filter merge requests', feature: true do context 'only text', js: true do it 'filters merge requests by searched text' do - fill_in 'issuable_search', with: 'Bug' + input_filtered_search('bug') - page.within '.mr-list' do - expect(page).to have_selector('.merge-request', count: 2) - end + expect_mr_list_count(2) end it 'does not show any merge requests' do - fill_in 'issuable_search', with: 'testing' + input_filtered_search('testing') page.within '.mr-list' do expect(page).not_to have_selector('.merge-request') @@ -234,82 +185,49 @@ describe 'Filter merge requests', feature: true do end end - context 'text and dropdown options', js: true do + context 'filters and searches', js: true do it 'filters by text and label' do - fill_in 'issuable_search', with: 'Bug' + input_filtered_search('Bug') - expect(page).to have_issuable_counts(open: 2, closed: 0, all: 2) - page.within '.mr-list' do - expect(page).to have_selector('.merge-request', count: 2) - end + expect_mr_list_count(2) + expect_filtered_search_input('Bug') - click_button 'Label' - page.within '.labels-filter' do - click_link 'bug' - end - find('.dropdown-menu-close-icon').click + input_filtered_search_keys(' label:~bug') - expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) - page.within '.mr-list' do - expect(page).to have_selector('.merge-request', count: 1) - end + expect_mr_list_count(1) end it 'filters by text and milestone' do - fill_in 'issuable_search', with: 'Bug' + input_filtered_search('Bug') - expect(page).to have_issuable_counts(open: 2, closed: 0, all: 2) - page.within '.mr-list' do - expect(page).to have_selector('.merge-request', count: 2) - end + expect_mr_list_count(2) + expect_filtered_search_input('Bug') - click_button 'Milestone' - page.within '.milestone-filter' do - click_link '8' - end + input_filtered_search_keys(' milestone:%8') - expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) - page.within '.mr-list' do - expect(page).to have_selector('.merge-request', count: 1) - end + expect_mr_list_count(1) end it 'filters by text and assignee' do - fill_in 'issuable_search', with: 'Bug' + input_filtered_search('Bug') - expect(page).to have_issuable_counts(open: 2, closed: 0, all: 2) - page.within '.mr-list' do - expect(page).to have_selector('.merge-request', count: 2) - end + expect_mr_list_count(2) + expect_filtered_search_input('Bug') - click_button 'Assignee' - page.within '.dropdown-menu-assignee' do - click_link user.name - end + input_filtered_search_keys(" assignee:@#{user.username}") - expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) - page.within '.mr-list' do - expect(page).to have_selector('.merge-request', count: 1) - end + expect_mr_list_count(1) end it 'filters by text and author' do - fill_in 'issuable_search', with: 'Bug' + input_filtered_search('Bug') - expect(page).to have_issuable_counts(open: 2, closed: 0, all: 2) - page.within '.mr-list' do - expect(page).to have_selector('.merge-request', count: 2) - end + expect_mr_list_count(2) + expect_filtered_search_input('Bug') - click_button 'Author' - page.within '.dropdown-menu-author' do - click_link user.name - end + input_filtered_search_keys(" author:@#{user.username}") - expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) - page.within '.mr-list' do - expect(page).to have_selector('.merge-request', count: 1) - end + expect_mr_list_count(1) end end end @@ -328,18 +246,9 @@ describe 'Filter merge requests', feature: true do end it 'is able to filter and sort merge requests' do - click_button 'Label' - wait_for_ajax - page.within '.labels-filter' do - click_link 'bug' - end - find('.dropdown-menu-close-icon').click - wait_for_ajax + input_filtered_search('label:~bug') - expect(page).to have_issuable_counts(open: 2, closed: 0, all: 2) - page.within '.mr-list' do - expect(page).to have_selector('.merge-request', count: 2) - end + expect_mr_list_count(2) click_button 'Last created' page.within '.dropdown-menu-sort' do @@ -352,4 +261,38 @@ describe 'Filter merge requests', feature: true do end end end + + describe 'filter by assignee id', js: true do + it 'filter by current user' do + visit namespace_project_merge_requests_path(project.namespace, project, assignee_id: user.id) + + expect_filtered_search_input("assignee:@#{user.username}") + end + + it 'filter by new user' do + new_user = create(:user) + project.add_developer(new_user) + + visit namespace_project_merge_requests_path(project.namespace, project, assignee_id: new_user.id) + + expect_filtered_search_input("assignee:@#{new_user.username}") + end + end + + describe 'filter by author id', js: true do + it 'filter by current user' do + visit namespace_project_merge_requests_path(project.namespace, project, author_id: user.id) + + expect_filtered_search_input("author:@#{user.username}") + end + + it 'filter by new user' do + new_user = create(:user) + project.add_developer(new_user) + + visit namespace_project_merge_requests_path(project.namespace, project, author_id: new_user.id) + + expect_filtered_search_input("author:@#{new_user.username}") + end + end end diff --git a/spec/features/merge_requests/reset_filters_spec.rb b/spec/features/merge_requests/reset_filters_spec.rb index 3a7ece7e1d6..58f11499e3f 100644 --- a/spec/features/merge_requests/reset_filters_spec.rb +++ b/spec/features/merge_requests/reset_filters_spec.rb @@ -1,17 +1,20 @@ require 'rails_helper' feature 'Issues filter reset button', feature: true, js: true do + include FilteredSearchHelpers + include MergeRequestHelpers include WaitForAjax include IssueHelpers - let!(:project) { create(:project, :public) } - let!(:user) { create(:user)} - let!(:milestone) { create(:milestone, project: project) } - let!(:bug) { create(:label, project: project, name: 'bug')} + let!(:project) { create(:project, :public) } + let!(:user) { create(:user) } + let!(:milestone) { create(:milestone, project: project) } + 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!(: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 mr2.labels << bug @@ -50,7 +53,7 @@ feature 'Issues filter reset button', feature: true, js: true do context 'when author filter has been applied' 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) reset_filters @@ -60,7 +63,7 @@ feature 'Issues filter reset button', feature: true, js: true do context 'when assignee filter has been applied' 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) reset_filters @@ -70,7 +73,7 @@ feature 'Issues filter reset button', feature: true, js: true do context 'when all filters have been applied' 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) reset_filters @@ -82,15 +85,7 @@ feature 'Issues filter reset button', feature: true, js: true do it 'the reset link should not be visible' do visit_merge_requests(project) 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 - - 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 diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb index 0fe5a897565..7da05defa81 100644 --- a/spec/features/search_spec.rb +++ b/spec/features/search_spec.rb @@ -186,7 +186,7 @@ describe "Search", feature: true do sleep 2 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 it 'takes user to her MR page when MR authored is clicked' do @@ -194,7 +194,7 @@ describe "Search", feature: true do sleep 2 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 diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js index cb8754581c0..aaf058bd755 100644 --- a/spec/javascripts/search_autocomplete_spec.js +++ b/spec/javascripts/search_autocomplete_spec.js @@ -89,8 +89,8 @@ require('vendor/fuzzaldrin-plus'); var a1, a2, a3, a4, issuesAssignedToMeLink, issuesIHaveCreatedLink, mrsAssignedToMeLink, mrsIHaveCreatedLink; issuesAssignedToMeLink = issuesPath + "/?assignee_username=" + userName; issuesIHaveCreatedLink = issuesPath + "/?author_username=" + userName; - mrsAssignedToMeLink = mrsPath + "/?assignee_id=" + userId; - mrsIHaveCreatedLink = mrsPath + "/?author_id=" + userId; + mrsAssignedToMeLink = mrsPath + "/?assignee_username=" + userName; + mrsIHaveCreatedLink = mrsPath + "/?author_username=" + userName; a1 = "a[href='" + issuesAssignedToMeLink + "']"; a2 = "a[href='" + issuesIHaveCreatedLink + "']"; a3 = "a[href='" + mrsAssignedToMeLink + "']"; diff --git a/spec/support/filtered_search_helpers.rb b/spec/support/filtered_search_helpers.rb new file mode 100644 index 00000000000..58f6636e680 --- /dev/null +++ b/spec/support/filtered_search_helpers.rb @@ -0,0 +1,37 @@ +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 diff --git a/spec/support/merge_request_helpers.rb b/spec/support/merge_request_helpers.rb index d5801c8272f..326b85eabd0 100644 --- a/spec/support/merge_request_helpers.rb +++ b/spec/support/merge_request_helpers.rb @@ -10,4 +10,13 @@ module MergeRequestHelpers def last_merge_request page.all('ul.mr-list > li').last.text 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 |