diff options
41 files changed, 200 insertions, 312 deletions
@@ -15,6 +15,9 @@ source 'https://rubygems.org' gem 'rails', gem_versions['rails'] gem 'rails-deprecated_sanitizer', '~> 1.0.3' +# Improves copy-on-write performance for MRI +gem 'nakayoshi_fork', '~> 0.0.4' + # Responders respond_to and respond_with gem 'responders', '~> 2.0' diff --git a/Gemfile.lock b/Gemfile.lock index de1c8a4a38d..6459cdff7a3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -470,6 +470,7 @@ GEM mustermann-grape (1.0.0) mustermann (~> 1.0.0) mysql2 (0.4.10) + nakayoshi_fork (0.0.4) net-ldap (0.16.0) net-ssh (5.0.1) netrc (0.11.0) @@ -1051,6 +1052,7 @@ DEPENDENCIES mini_magick minitest (~> 5.7.0) mysql2 (~> 0.4.10) + nakayoshi_fork (~> 0.0.4) net-ldap net-ssh (~> 5.0) nokogiri (~> 1.8.2) diff --git a/Gemfile.rails4.lock b/Gemfile.rails4.lock index a05085d835d..657975da2a4 100644 --- a/Gemfile.rails4.lock +++ b/Gemfile.rails4.lock @@ -467,6 +467,7 @@ GEM mustermann-grape (1.0.0) mustermann (~> 1.0.0) mysql2 (0.4.10) + nakayoshi_fork (0.0.4) net-ldap (0.16.0) net-ssh (5.0.1) netrc (0.11.0) @@ -1042,6 +1043,7 @@ DEPENDENCIES mini_magick minitest (~> 5.7.0) mysql2 (~> 0.4.10) + nakayoshi_fork (~> 0.0.4) net-ldap net-ssh (~> 5.0) nokogiri (~> 1.8.2) diff --git a/app/assets/javascripts/init_legacy_filters.js b/app/assets/javascripts/init_legacy_filters.js deleted file mode 100644 index b6ff97d1279..00000000000 --- a/app/assets/javascripts/init_legacy_filters.js +++ /dev/null @@ -1,14 +0,0 @@ -/* eslint-disable no-new */ -import LabelsSelect from './labels_select'; -import subscriptionSelect from './subscription_select'; -import UsersSelect from './users_select'; -import issueStatusSelect from './issue_status_select'; -import MilestoneSelect from './milestone_select'; - -export default () => { - new UsersSelect(); - new LabelsSelect(); - new MilestoneSelect(); - issueStatusSelect(); - subscriptionSelect(); -}; diff --git a/app/assets/javascripts/pages/dashboard/issues/index.js b/app/assets/javascripts/pages/dashboard/issues/index.js index c4901dd1cb6..9055738f86e 100644 --- a/app/assets/javascripts/pages/dashboard/issues/index.js +++ b/app/assets/javascripts/pages/dashboard/issues/index.js @@ -1,7 +1,13 @@ import projectSelect from '~/project_select'; -import initLegacyFilters from '~/init_legacy_filters'; +import initFilteredSearch from '~/pages/search/init_filtered_search'; +import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys'; +import { FILTERED_SEARCH } from '~/pages/constants'; document.addEventListener('DOMContentLoaded', () => { + initFilteredSearch({ + page: FILTERED_SEARCH.ISSUES, + filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys, + }); + projectSelect(); - initLegacyFilters(); }); diff --git a/app/assets/javascripts/pages/dashboard/merge_requests/index.js b/app/assets/javascripts/pages/dashboard/merge_requests/index.js index c4901dd1cb6..260484726f3 100644 --- a/app/assets/javascripts/pages/dashboard/merge_requests/index.js +++ b/app/assets/javascripts/pages/dashboard/merge_requests/index.js @@ -1,7 +1,15 @@ import projectSelect from '~/project_select'; -import initLegacyFilters from '~/init_legacy_filters'; +import initFilteredSearch from '~/pages/search/init_filtered_search'; +import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys'; +import { FILTERED_SEARCH } from '~/pages/constants'; document.addEventListener('DOMContentLoaded', () => { + IssuableFilteredSearchTokenKeys.addExtraTokensForMergeRequests(); + + initFilteredSearch({ + page: FILTERED_SEARCH.MERGE_REQUESTS, + filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys, + }); + projectSelect(); - initLegacyFilters(); }); diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js index 17def77b2d7..0a4583b5861 100644 --- a/app/assets/javascripts/search_autocomplete.js +++ b/app/assets/javascripts/search_autocomplete.js @@ -253,7 +253,6 @@ export class SearchAutocomplete { } getCategoryContents() { - const userId = gon.current_user_id; const userName = gon.current_username; const { projectOptions, groupOptions, dashboardOptions } = gl; @@ -279,21 +278,21 @@ export class SearchAutocomplete { const issueItems = [ { text: s__('SearchAutocomplete|Issues assigned to me'), - url: `${issuesPath}/?assignee_id=${userId}`, + url: `${issuesPath}/?assignee_username=${userName}`, }, { text: s__("SearchAutocomplete|Issues I've created"), - url: `${issuesPath}/?author_id=${userId}`, + url: `${issuesPath}/?author_username=${userName}`, }, ]; const mergeRequestItems = [ { text: s__('SearchAutocomplete|Merge requests assigned to me'), - url: `${mrPath}/?assignee_id=${userId}`, + url: `${mrPath}/?assignee_username=${userName}`, }, { text: s__("SearchAutocomplete|Merge requests I've created"), - url: `${mrPath}/?author_id=${userId}`, + url: `${mrPath}/?author_username=${userName}`, }, ]; diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb index 5217b4be928..34a8c50fcbd 100644 --- a/app/controllers/concerns/issuable_collections.rb +++ b/app/controllers/concerns/issuable_collections.rb @@ -81,36 +81,36 @@ module IssuableCollections end def issuable_finder_for(finder_class) - finder_class.new(current_user, filter_params) + finder_class.new(current_user, finder_options) end # rubocop:disable Gitlab/ModuleWithInstanceVariables - # rubocop: disable CodeReuse/ActiveRecord - def filter_params - set_sort_order_from_cookie - set_default_state + def finder_options + params[:state] = default_state if params[:state].blank? - # Skip irrelevant Rails routing params - @filter_params = params.dup.except(:controller, :action, :namespace_id) - @filter_params[:sort] ||= default_sort_order + options = { + scope: params[:scope], + state: params[:state], + sort: set_sort_order_from_cookie || default_sort_order + } - @sort = @filter_params[:sort] + # Used by view to highlight active option + @sort = options[:sort] if @project - @filter_params[:project_id] = @project.id + options[:project_id] = @project.id elsif @group - @filter_params[:group_id] = @group.id - @filter_params[:include_subgroups] = true - @filter_params[:use_cte_for_search] = true + options[:group_id] = @group.id + options[:include_subgroups] = true + options[:use_cte_for_search] = true end - @filter_params.permit(finder_type.valid_params) + params.permit(finder_type.valid_params).merge(options) end - # rubocop: enable CodeReuse/ActiveRecord # rubocop:enable Gitlab/ModuleWithInstanceVariables - def set_default_state - params[:state] = 'opened' if params[:state].blank? + def default_state + 'opened' end def set_sort_order_from_cookie @@ -121,7 +121,7 @@ module IssuableCollections sort_value = update_cookie_value(sort_param) set_secure_cookie(remember_sorting_key, sort_value) - params[:sort] = sort_value + sort_value end def remember_sorting_key diff --git a/app/controllers/concerns/merge_requests_action.rb b/app/controllers/concerns/merge_requests_action.rb index 285f2c3a8a0..ed10f32512e 100644 --- a/app/controllers/concerns/merge_requests_action.rb +++ b/app/controllers/concerns/merge_requests_action.rb @@ -19,7 +19,7 @@ module MergeRequestsAction (MergeRequestsFinder if action_name == 'merge_requests') end - def filter_params + def finder_options super.merge(non_archived: true) end end diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index c032fb2efb5..4ce9be44403 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -4,13 +4,6 @@ class DashboardController < Dashboard::ApplicationController include IssuesAction include MergeRequestsAction - FILTER_PARAMS = [ - :author_id, - :assignee_id, - :milestone_title, - :label_name - ].freeze - before_action :event_filter, only: :activity before_action :projects, only: [:issues, :merge_requests] before_action :set_show_full_reference, only: [:issues, :merge_requests] @@ -51,10 +44,13 @@ class DashboardController < Dashboard::ApplicationController end def check_filters_presence! - @no_filters_set = FILTER_PARAMS.none? { |k| params.key?(k) } + @no_filters_set = finder_type.scalar_params.none? { |k| params.key?(k) } return unless @no_filters_set + # Call to set selected `state` and `sort` options in view + finder_options + respond_to do |format| format.html { render } format.atom { head :bad_request } diff --git a/app/controllers/root_controller.rb b/app/controllers/root_controller.rb index ebf70f25bda..7b6657e1196 100644 --- a/app/controllers/root_controller.rb +++ b/app/controllers/root_controller.rb @@ -45,9 +45,9 @@ class RootController < Dashboard::ProjectsController when 'todos' redirect_to(dashboard_todos_path) when 'issues' - redirect_to(issues_dashboard_path(assignee_id: current_user.id)) + redirect_to(issues_dashboard_path(assignee_username: current_user.username)) when 'merge_requests' - redirect_to(merge_requests_dashboard_path(assignee_id: current_user.id)) + redirect_to(merge_requests_dashboard_path(assignee_username: current_user.username)) end end diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 93bef592c65..fdc630cbf72 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -14,7 +14,9 @@ # project_id: integer # milestone_title: string # author_id: integer +# author_username: string # assignee_id: integer or 'None' or 'Any' +# assignee_username: string # search: string # label_name: string # sort: string @@ -49,25 +51,15 @@ class IssuableFinder assignee_username author_id author_username - authorized_only - group_id - iids label_name milestone_title my_reaction_emoji - non_archived - project_id - scope search - sort - state - include_subgroups - use_cte_for_search ] end def self.array_params - @array_params ||= { label_name: [], iids: [], assignee_username: [] } + @array_params ||= { label_name: [], assignee_username: [] } end def self.valid_params diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 4f91e3e4117..74042f0bae8 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -173,17 +173,7 @@ module ApplicationHelper without = options.delete(:without) add_label = options.delete(:label) - exist_opts = { - state: params[:state], - scope: params[:scope], - milestone_title: params[:milestone_title], - assignee_id: params[:assignee_id], - author_id: params[:author_id], - search: params[:search], - label_name: params[:label_name] - } - - options = exist_opts.merge(options) + options = request.query_parameters.merge(options) if without.present? without.each do |key| diff --git a/app/helpers/dashboard_helper.rb b/app/helpers/dashboard_helper.rb index 463f4145bdd..d90ef8903a7 100644 --- a/app/helpers/dashboard_helper.rb +++ b/app/helpers/dashboard_helper.rb @@ -2,11 +2,11 @@ module DashboardHelper def assigned_issues_dashboard_path - issues_dashboard_path(assignee_id: current_user.id) + issues_dashboard_path(assignee_username: current_user.username) end def assigned_mrs_dashboard_path - merge_requests_dashboard_path(assignee_id: current_user.id) + merge_requests_dashboard_path(assignee_username: current_user.username) end def dashboard_nav_links diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 4f9e1322b56..80cc568820a 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -163,15 +163,26 @@ module SearchHelper if @project.present? opts[:data]['project-id'] = @project.id opts[:data]['base-endpoint'] = project_path(@project) - else - # Group context + elsif @group.present? opts[:data]['group-id'] = @group.id opts[:data]['base-endpoint'] = group_canonical_path(@group) + else + opts[:data]['base-endpoint'] = root_dashboard_path end opts end + def search_history_storage_prefix + if @project.present? + @project.full_path + elsif @group.present? + @group.full_path + else + 'dashboard' + end + end + # Sanitize a HTML field for search display. Most tags are stripped out and the # maximum length is set to 200 characters. def search_md_sanitize(object, field) diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml index 832ba877558..fdd5c19d562 100644 --- a/app/views/dashboard/issues.html.haml +++ b/app/views/dashboard/issues.html.haml @@ -1,6 +1,6 @@ - @hide_top_links = true - page_title _("Issues") -- @breadcrumb_link = issues_dashboard_path(assignee_id: current_user.id) +- @breadcrumb_link = issues_dashboard_path(assignee_username: current_user.username) = content_for :meta_tags do = auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{current_user.name} issues") @@ -16,7 +16,7 @@ .nav-controls = render 'shared/issuable/feed_buttons' -= render 'shared/issuable/filter', type: :issues += render 'shared/issuable/search_bar', type: :issues - if current_user && @no_filters_set = render 'shared/dashboard/no_filter_selected' diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml index fba8d1cf667..77cfa1271df 100644 --- a/app/views/dashboard/merge_requests.html.haml +++ b/app/views/dashboard/merge_requests.html.haml @@ -1,6 +1,6 @@ - @hide_top_links = true - page_title _("Merge Requests") -- @breadcrumb_link = merge_requests_dashboard_path(assignee_id: current_user.id) +- @breadcrumb_link = merge_requests_dashboard_path(assignee_username: current_user.username) .page-title-holder %h1.page-title= _('Merge Requests') @@ -12,7 +12,7 @@ .top-area = render 'shared/issuable/nav', type: :merge_requests, display_count: !@no_filters_set -= render 'shared/issuable/filter', type: :merge_requests += render 'shared/issuable/search_bar', type: :merge_requests - if current_user && @no_filters_set = render 'shared/dashboard/no_filter_selected' diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml deleted file mode 100644 index c7037335866..00000000000 --- a/app/views/shared/issuable/_filter.html.haml +++ /dev/null @@ -1,32 +0,0 @@ -.issues-filters - .issues-details-filters.row-content-block.second-block - = form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search]), method: :get, class: 'filter-form js-filter-form' do - - if params[:search].present? - = hidden_field_tag :search, params[:search] - .issues-other-filters - .filter-item.inline - - if params[:author_id].present? - = hidden_field_tag(:author_id, params[:author_id]) - = dropdown_tag(user_dropdown_label(params[:author_id], "Author"), options: { toggle_class: "js-user-search js-filter-submit js-author-search", title: "Filter by author", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit", - placeholder: "Search authors", data: { any_user: "Any Author", first_user: current_user&.username, current_user: true, project_id: @project&.id, group_id: @group&.id, selected: params[:author_id], field_name: "author_id", default_label: "Author" } }) - - .filter-item.inline - - if params[:assignee_id].present? - = hidden_field_tag(:assignee_id, params[:assignee_id]) - = dropdown_tag(user_dropdown_label(params[:assignee_id], "Assignee"), options: { toggle_class: "js-user-search js-filter-submit js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit", - placeholder: "Search assignee", data: { any_user: "Any Assignee", first_user: current_user&.username, null_user: true, current_user: true, project_id: @project&.id, group_id: @group&.id, selected: params[:assignee_id], field_name: "assignee_id", default_label: "Assignee" } }) - - .filter-item.inline.milestone-filter - = render "shared/issuable/milestone_dropdown", selected: finder.milestones.try(:first), name: :milestone_title, show_any: true, show_upcoming: true, show_started: true - - .filter-item.inline.labels-filter - = render "shared/issuable/label_dropdown", selected: selected_labels, use_id: false, selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" } - - - unless @no_filters_set - .float-right - = render 'shared/sort_dropdown' - - - has_labels = @labels && @labels.any? - .row-content-block.second-block.filtered-labels{ class: ("hidden" unless has_labels) } - - if has_labels - = render 'shared/labels_row', labels: @labels diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index 95f32bd0180..824bbe3524b 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -1,7 +1,6 @@ - type = local_assigns.fetch(:type) - board = local_assigns.fetch(:board, nil) - block_css_class = type != :boards_modal ? 'row-content-block second-block' : '' -- full_path = @project.present? ? @project.full_path : @group.full_path - user_can_admin_list = board && can?(current_user, :admin_list, board.parent) - show_sorting_dropdown = local_assigns.fetch(:show_sorting_dropdown, true) @@ -10,7 +9,7 @@ - if type == :boards #js-multiple-boards-switcher.inline.boards-switcher{ "v-cloak" => true } = render_if_exists "shared/boards/switcher", board: board - = form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search]), method: :get, class: 'filter-form js-filter-form' do + = form_tag page_filter_path, method: :get, class: 'filter-form js-filter-form' do - if params[:search].present? = hidden_field_tag :search, params[:search] - if @can_bulk_update @@ -25,7 +24,7 @@ dropdown_class: "filtered-search-history-dropdown", content_class: "filtered-search-history-dropdown-content", title: "Recent searches" }) do - .js-filtered-search-history-dropdown{ data: { full_path: full_path } } + .js-filtered-search-history-dropdown{ data: { full_path: search_history_storage_prefix } } .filtered-search-box-input-container.droplab-dropdown .scroll-container %ul.tokens-container.list-unstyled diff --git a/changelogs/unreleased/52385-search-bar-for-dashboard-list.yml b/changelogs/unreleased/52385-search-bar-for-dashboard-list.yml new file mode 100644 index 00000000000..a437ae560cb --- /dev/null +++ b/changelogs/unreleased/52385-search-bar-for-dashboard-list.yml @@ -0,0 +1,5 @@ +--- +title: Use search bar for filtering in dashboard issues / MRs +merge_request: 22641 +author: Heinrich Lee Yu +type: changed diff --git a/changelogs/unreleased/sh-use-nakayoshi-fork.yml b/changelogs/unreleased/sh-use-nakayoshi-fork.yml new file mode 100644 index 00000000000..5977d9b0974 --- /dev/null +++ b/changelogs/unreleased/sh-use-nakayoshi-fork.yml @@ -0,0 +1,5 @@ +--- +title: Improve memory performance by reducing dirty pages after fork() +merge_request: 23169 +author: +type: performance diff --git a/doc/README.md b/doc/README.md index 2bad6b3eb84..a15b7a91b5e 100644 --- a/doc/README.md +++ b/doc/README.md @@ -23,7 +23,7 @@ No matter how you use GitLab, we have documentation for you. Discover features and concepts for GitLab users. </td> <td width="50%"> - <a href="administration/index.md"><strong>Administrator documentation</strong></a> **[CORE ONLY]** + <a href="administration/index.md"><strong>Administrator documentation</strong></a> <br/> Everything GitLab administrators need to know. </td> diff --git a/doc/user/search/img/dashboard_links.png b/doc/user/search/img/dashboard_links.png Binary files differnew file mode 100644 index 00000000000..2c472c7e464 --- /dev/null +++ b/doc/user/search/img/dashboard_links.png diff --git a/doc/user/search/img/issues_assigned_to_you.png b/doc/user/search/img/issues_assigned_to_you.png Binary files differindex 36c670eedd5..d2fff5e9a67 100644 --- a/doc/user/search/img/issues_assigned_to_you.png +++ b/doc/user/search/img/issues_assigned_to_you.png diff --git a/doc/user/search/img/left_menu_bar.png b/doc/user/search/img/left_menu_bar.png Binary files differdeleted file mode 100644 index d68a71cba8e..00000000000 --- a/doc/user/search/img/left_menu_bar.png +++ /dev/null diff --git a/doc/user/search/index.md b/doc/user/search/index.md index 3f9d07dacaa..78c1294346b 100644 --- a/doc/user/search/index.md +++ b/doc/user/search/index.md @@ -2,27 +2,27 @@ ## Issues and merge requests -To search through issues and merge requests in multiple projects, you can use the left-sidebar. +To search through issues and merge requests in multiple projects, you can use the **Issues** or **Merge Requests** links +in the top-right part of your screen. -Click the menu bar, then **Issues** or **Merge Requests**, which work in the same way, -therefore, the following notes are valid for both. +Both of them work in the same way, therefore, the following notes are valid for both. The number displayed on their right represents the number of issues and merge requests assigned to you. -![menu bar - issues and MRs assigned to you](img/left_menu_bar.png) +![issues and MRs dashboard links](img/dashboard_links.png) When you click **Issues**, you'll see the opened issues assigned to you straight away: ![Issues assigned to you](img/issues_assigned_to_you.png) -You can filter them by **Author**, **Assignee**, **Milestone**, and **Labels**, -searching through **Open**, **Closed**, and **All** issues. +You can search through **Open**, **Closed**, or **All** issues. -Of course, you can combine all filters together. +You can also filter the results using the search and filter field. This works in the same way as the ones found in the +per project pages described below. ### Issues and MRs assigned to you or created by you -You'll find a shortcut to issues and merge requests create by you or assigned to you +You'll also find shortcuts to issues and merge requests created by you or assigned to you on the search field on the top-right of your screen: ![shortcut to your issues and mrs](img/issues_mrs_shortcut.png) diff --git a/spec/controllers/concerns/issuable_collections_spec.rb b/spec/controllers/concerns/issuable_collections_spec.rb index d16a3464495..e93c923fd39 100644 --- a/spec/controllers/concerns/issuable_collections_spec.rb +++ b/spec/controllers/concerns/issuable_collections_spec.rb @@ -60,7 +60,7 @@ describe IssuableCollections do end end - describe '#filter_params' do + describe '#finder_options' do let(:params) do { assignee_id: '1', @@ -84,25 +84,20 @@ describe IssuableCollections do } end - it 'filters params' do + it 'only allows whitelisted params' do allow(controller).to receive(:cookies).and_return({}) - filtered_params = controller.send(:filter_params) + finder_options = controller.send(:finder_options) - expect(filtered_params).to eq({ + expect(finder_options).to eq({ 'assignee_id' => '1', 'assignee_username' => 'user1', 'author_id' => '2', 'author_username' => 'user2', - 'authorized_only' => 'true', - 'due_date' => '2017-01-01', - 'group_id' => '3', - 'iids' => '4', 'label_name' => 'foo', 'milestone_title' => 'bar', 'my_reaction_emoji' => 'thumbsup', - 'non_archived' => 'true', - 'project_id' => '5', + 'due_date' => '2017-01-01', 'scope' => 'all', 'search' => 'baz', 'sort' => 'priority', diff --git a/spec/controllers/root_controller_spec.rb b/spec/controllers/root_controller_spec.rb index 7688538a468..995f803d757 100644 --- a/spec/controllers/root_controller_spec.rb +++ b/spec/controllers/root_controller_spec.rb @@ -98,7 +98,7 @@ describe RootController do it 'redirects to their assigned issues' do get :index - expect(response).to redirect_to issues_dashboard_path(assignee_id: user.id) + expect(response).to redirect_to issues_dashboard_path(assignee_username: user.username) end end @@ -110,7 +110,7 @@ describe RootController do it 'redirects to their assigned merge requests' do get :index - expect(response).to redirect_to merge_requests_dashboard_path(assignee_id: user.id) + expect(response).to redirect_to merge_requests_dashboard_path(assignee_username: user.username) end end diff --git a/spec/features/atom/dashboard_issues_spec.rb b/spec/features/atom/dashboard_issues_spec.rb index bd4c00d97b1..5fa1a26f1a6 100644 --- a/spec/features/atom/dashboard_issues_spec.rb +++ b/spec/features/atom/dashboard_issues_spec.rb @@ -25,35 +25,35 @@ describe "Dashboard Issues Feed" do it "renders atom feed via personal access token" do personal_access_token = create(:personal_access_token, user: user) - visit issues_dashboard_path(:atom, private_token: personal_access_token.token, assignee_id: user.id) + visit issues_dashboard_path(:atom, private_token: personal_access_token.token, assignee_username: user.username) expect(response_headers['Content-Type']).to have_content('application/atom+xml') expect(body).to have_selector('title', text: "#{user.name} issues") end it "renders atom feed via feed token" do - visit issues_dashboard_path(:atom, feed_token: user.feed_token, assignee_id: user.id) + visit issues_dashboard_path(:atom, feed_token: user.feed_token, assignee_username: user.username) expect(response_headers['Content-Type']).to have_content('application/atom+xml') expect(body).to have_selector('title', text: "#{user.name} issues") end it "renders atom feed with url parameters" do - visit issues_dashboard_path(:atom, feed_token: user.feed_token, state: 'opened', assignee_id: user.id) + visit issues_dashboard_path(:atom, feed_token: user.feed_token, state: 'opened', assignee_username: user.username) link = find('link[type="application/atom+xml"]') params = CGI.parse(URI.parse(link[:href]).query) expect(params).to include('feed_token' => [user.feed_token]) expect(params).to include('state' => ['opened']) - expect(params).to include('assignee_id' => [user.id.to_s]) + expect(params).to include('assignee_username' => [user.username.to_s]) end context "issue with basic fields" do let!(:issue2) { create(:issue, author: user, assignees: [assignee], project: project2, description: 'test desc') } it "renders issue fields" do - visit issues_dashboard_path(:atom, feed_token: user.feed_token, assignee_id: assignee.id) + visit issues_dashboard_path(:atom, feed_token: user.feed_token, assignee_username: assignee.username) entry = find(:xpath, "//feed/entry[contains(summary/text(),'#{issue2.title}')]") @@ -76,7 +76,7 @@ describe "Dashboard Issues Feed" do end it "renders issue label and milestone info" do - visit issues_dashboard_path(:atom, feed_token: user.feed_token, assignee_id: assignee.id) + visit issues_dashboard_path(:atom, feed_token: user.feed_token, assignee_username: assignee.username) entry = find(:xpath, "//feed/entry[contains(summary/text(),'#{issue1.title}')]") diff --git a/spec/features/dashboard/issuables_counter_spec.rb b/spec/features/dashboard/issuables_counter_spec.rb index b431f72fcc9..fbc2e5cc3d3 100644 --- a/spec/features/dashboard/issuables_counter_spec.rb +++ b/spec/features/dashboard/issuables_counter_spec.rb @@ -45,11 +45,11 @@ describe 'Navigation bar counter', :use_clean_rails_memory_store_caching do end def issues_path - issues_dashboard_path(assignee_id: user.id) + issues_dashboard_path(assignee_username: user.username) end def merge_requests_path - merge_requests_dashboard_path(assignee_id: user.id) + merge_requests_dashboard_path(assignee_username: user.username) end def expect_counters(issuable_type, count) diff --git a/spec/features/dashboard/issues_filter_spec.rb b/spec/features/dashboard/issues_filter_spec.rb index 95e2610dd4a..c0434f767bb 100644 --- a/spec/features/dashboard/issues_filter_spec.rb +++ b/spec/features/dashboard/issues_filter_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' describe 'Dashboard Issues filtering', :js do include Spec::Support::Helpers::Features::SortingHelpers + include FilteredSearchHelpers let(:user) { create(:user) } let(:project) { create(:project) } @@ -25,27 +26,21 @@ describe 'Dashboard Issues filtering', :js do context 'filtering by milestone' do it 'shows all issues with no milestone' do - show_milestone_dropdown - - click_link 'No Milestone' + input_filtered_search("milestone:none") expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) expect(page).to have_selector('.issue', count: 1) end it 'shows all issues with the selected milestone' do - show_milestone_dropdown - - page.within '.dropdown-content' do - click_link milestone.title - end + input_filtered_search("milestone:%\"#{milestone.title}\"") expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) expect(page).to have_selector('.issue', count: 1) end it 'updates atom feed link' do - visit_issues(milestone_title: '', assignee_id: user.id) + visit_issues(milestone_title: '', assignee_username: user.username) link = find('.nav-controls a[title="Subscribe to RSS feed"]') params = CGI.parse(URI.parse(link[:href]).query) @@ -54,10 +49,10 @@ describe 'Dashboard Issues filtering', :js do expect(params).to include('feed_token' => [user.feed_token]) expect(params).to include('milestone_title' => ['']) - expect(params).to include('assignee_id' => [user.id.to_s]) + expect(params).to include('assignee_username' => [user.username.to_s]) expect(auto_discovery_params).to include('feed_token' => [user.feed_token]) expect(auto_discovery_params).to include('milestone_title' => ['']) - expect(auto_discovery_params).to include('assignee_id' => [user.id.to_s]) + expect(auto_discovery_params).to include('assignee_username' => [user.username.to_s]) end end @@ -66,10 +61,7 @@ describe 'Dashboard Issues filtering', :js do let!(:label_link) { create(:label_link, label: label, target: issue) } it 'shows all issues with the selected label' do - page.within '.labels-filter' do - find('.dropdown').click - click_link label.title - end + input_filtered_search("label:~#{label.title}") page.within 'ul.content-list' do expect(page).to have_content issue.title @@ -80,12 +72,12 @@ describe 'Dashboard Issues filtering', :js do context 'sorting' do before do - visit_issues(assignee_id: user.id) + visit_issues(assignee_username: user.username) end it 'remembers last sorting value' do sort_by('Created date') - visit_issues(assignee_id: user.id) + visit_issues(assignee_username: user.username) expect(find('.issues-filters')).to have_content('Created date') end @@ -98,11 +90,6 @@ describe 'Dashboard Issues filtering', :js do end end - def show_milestone_dropdown - click_button 'Milestone' - expect(page).to have_selector('.dropdown-content', visible: true) - end - def visit_issues(*args) visit issues_dashboard_path(*args) end diff --git a/spec/features/dashboard/issues_spec.rb b/spec/features/dashboard/issues_spec.rb index 4ae062f242a..9957bec0f0b 100644 --- a/spec/features/dashboard/issues_spec.rb +++ b/spec/features/dashboard/issues_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' RSpec.describe 'Dashboard Issues' do + include FilteredSearchHelpers + let(:current_user) { create :user } let(:user) { current_user } # Shared examples depend on this being available let!(:public_project) { create(:project, :public) } @@ -14,7 +16,7 @@ RSpec.describe 'Dashboard Issues' do before do [project, project_with_issues_disabled].each { |project| project.add_maintainer(current_user) } sign_in(current_user) - visit issues_dashboard_path(assignee_id: current_user.id) + visit issues_dashboard_path(assignee_username: current_user.username) end describe 'issues' do @@ -24,26 +26,9 @@ RSpec.describe 'Dashboard Issues' do expect(page).not_to have_content(other_issue.title) end - it 'shows checkmark when unassigned is selected for assignee', :js do - find('.js-assignee-search').click - find('li', text: 'Unassigned').click - find('.js-assignee-search').click - - expect(find('li[data-user-id="0"] a.is-active')).to be_visible - end - it 'shows issues when current user is author', :js do - execute_script("document.querySelector('#assignee_id').value=''") - find('.js-author-search', match: :first).click - - expect(find('li[data-user-id="null"] a.is-active')).to be_visible - - find('.dropdown-menu-author li a', match: :first, text: current_user.to_reference).click - find('.js-author-search', match: :first).click - - page.within '.dropdown-menu-user' do - expect(find('.dropdown-menu-author li a.is-active', match: :first, text: current_user.to_reference)).to be_visible - end + reset_filters + input_filtered_search("author:#{current_user.to_reference}") expect(page).to have_content(authored_issue.title) expect(page).to have_content(authored_issue_on_public_project.title) @@ -53,7 +38,7 @@ RSpec.describe 'Dashboard Issues' do it 'state filter tabs work' do find('#state-closed').click - expect(page).to have_current_path(issues_dashboard_url(assignee_id: current_user.id, state: 'closed'), url: true) + expect(page).to have_current_path(issues_dashboard_url(assignee_username: current_user.username, state: 'closed'), url: true) end it_behaves_like "it has an RSS button with current_user's feed token" diff --git a/spec/features/dashboard/label_filter_spec.rb b/spec/features/dashboard/label_filter_spec.rb index 6802974c2ee..2d4659d380f 100644 --- a/spec/features/dashboard/label_filter_spec.rb +++ b/spec/features/dashboard/label_filter_spec.rb @@ -1,6 +1,11 @@ require 'spec_helper' describe 'Dashboard > label filter', :js do + include FilteredSearchHelpers + + let(:filtered_search) { find('.filtered-search') } + let(:filter_dropdown) { find("#js-dropdown-label .filter-dropdown") } + let(:user) { create(:user) } let(:project) { create(:project, name: 'test', namespace: user.namespace) } let(:project2) { create(:project, name: 'test2', path: 'test2', namespace: user.namespace) } @@ -13,17 +18,15 @@ describe 'Dashboard > label filter', :js do sign_in(user) visit issues_dashboard_path + + init_label_search end context 'duplicate labels' do it 'removes duplicate labels' do - page.within('.labels-filter') do - click_button 'Label' - end + filtered_search.send_keys('bu') - page.within('.dropdown-menu-labels') do - expect(page).to have_selector('.dropdown-content a', text: 'bug', count: 1) - end + expect(filter_dropdown).to have_selector('.filter-dropdown-item', text: 'bug', count: 1) end end end diff --git a/spec/features/dashboard/merge_requests_spec.rb b/spec/features/dashboard/merge_requests_spec.rb index f51142f5790..282bf542e77 100644 --- a/spec/features/dashboard/merge_requests_spec.rb +++ b/spec/features/dashboard/merge_requests_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe 'Dashboard Merge Requests' do include Spec::Support::Helpers::Features::SortingHelpers - include FilterItemSelectHelper + include FilteredSearchHelpers include ProjectForksHelper let(:current_user) { create :user } @@ -36,7 +36,7 @@ describe 'Dashboard Merge Requests' do context 'no merge requests exist' do it 'shows an empty state' do - visit merge_requests_dashboard_path(assignee_id: current_user.id) + visit merge_requests_dashboard_path(assignee_username: current_user.username) expect(page).to have_selector('.empty-state') end @@ -79,7 +79,7 @@ describe 'Dashboard Merge Requests' do end before do - visit merge_requests_dashboard_path(assignee_id: current_user.id) + visit merge_requests_dashboard_path(assignee_username: current_user.username) end it 'shows assigned merge requests' do @@ -92,8 +92,8 @@ describe 'Dashboard Merge Requests' do end it 'shows authored merge requests', :js do - filter_item_select('Any Assignee', '.js-assignee-search') - filter_item_select(current_user.to_reference, '.js-author-search') + reset_filters + input_filtered_search("author:#{current_user.to_reference}") expect(page).to have_content(authored_merge_request.title) expect(page).to have_content(authored_merge_request_from_fork.title) @@ -104,8 +104,7 @@ describe 'Dashboard Merge Requests' do end it 'shows error message without filter', :js do - filter_item_select('Any Assignee', '.js-assignee-search') - filter_item_select('Any Author', '.js-author-search') + reset_filters expect(page).to have_content('Please select at least one filter to see results') end @@ -113,7 +112,7 @@ describe 'Dashboard Merge Requests' do it 'shows sorted merge requests' do sort_by('Created date') - visit merge_requests_dashboard_path(assignee_id: current_user.id) + visit merge_requests_dashboard_path(assignee_username: current_user.username) expect(find('.issues-filters')).to have_content('Created date') end diff --git a/spec/features/dashboard/milestone_filter_spec.rb b/spec/features/dashboard/milestone_filter_spec.rb deleted file mode 100644 index 00373050aeb..00000000000 --- a/spec/features/dashboard/milestone_filter_spec.rb +++ /dev/null @@ -1,77 +0,0 @@ -require 'spec_helper' - -describe 'Dashboard > milestone filter', :js do - include FilterItemSelectHelper - - let(:user) { create(:user) } - let(:project) { create(:project, name: 'test', namespace: user.namespace) } - let(:milestone) { create(:milestone, title: 'v1.0', project: project) } - let(:milestone2) { create(:milestone, title: 'v2.0', project: project) } - let!(:issue) { create :issue, author: user, project: project, milestone: milestone } - let!(:issue2) { create :issue, author: user, project: project, milestone: milestone2 } - - dropdown_toggle_button = '.js-milestone-select' - - before do - sign_in(user) - end - - context 'default state' do - it 'shows issues with Any Milestone' do - visit issues_dashboard_path(author_id: user.id) - - page.all('.issue-info').each do |issue_info| - expect(issue_info.text).to match(/v\d.0/) - end - end - end - - context 'filtering by milestone' do - before do - visit issues_dashboard_path(author_id: user.id) - filter_item_select('v1.0', dropdown_toggle_button) - find(dropdown_toggle_button).click - wait_for_requests - end - - it 'shows issues with Milestone v1.0' do - expect(find('.issues-list')).to have_selector('.issue', count: 1) - expect(find('.milestone-filter .dropdown-content')).to have_selector('a.is-active', count: 1) - end - - it 'should not change active Milestone unless clicked' do - page.within '.milestone-filter' do - expect(find('.dropdown-content')).to have_selector('a.is-active', count: 1) - - find('.dropdown-menu-close').click - - expect(page).not_to have_selector('.dropdown.open') - - find(dropdown_toggle_button).click - - expect(find('.dropdown-content')).to have_selector('a.is-active', count: 1) - expect(find('.dropdown-content a.is-active')).to have_content('v1.0') - end - end - end - - context 'with milestone filter in URL' do - before do - visit issues_dashboard_path(author_id: user.id, milestone_title: milestone.title) - find(dropdown_toggle_button).click - wait_for_requests - end - - it 'has milestone selected' do - expect(find('.milestone-filter .dropdown-content')).to have_css('.is-active', text: milestone.title) - end - - it 'removes milestone filter from URL after clicking "Any Milestone"' do - expect(current_url).to include("milestone_title=#{milestone.title}") - - find('.milestone-filter .dropdown-content li', text: 'Any Milestone').click - - expect(current_url).not_to include('milestone_title') - end - end -end diff --git a/spec/features/issues/user_sorts_issues_spec.rb b/spec/features/issues/user_sorts_issues_spec.rb index 7d261ec7dae..4771d2c6d28 100644 --- a/spec/features/issues/user_sorts_issues_spec.rb +++ b/spec/features/issues/user_sorts_issues_spec.rb @@ -26,7 +26,7 @@ describe "User sorts issues" do click_link('Milestone') end - visit(issues_dashboard_path(assignee_id: user.id)) + visit(issues_dashboard_path(assignee_username: user.username)) expect(find('.issues-filters a.is-active')).to have_content('Milestone') diff --git a/spec/features/merge_requests/user_sorts_merge_requests_spec.rb b/spec/features/merge_requests/user_sorts_merge_requests_spec.rb index 82cfe600d52..e163868e8e7 100644 --- a/spec/features/merge_requests/user_sorts_merge_requests_spec.rb +++ b/spec/features/merge_requests/user_sorts_merge_requests_spec.rb @@ -25,7 +25,7 @@ describe 'User sorts merge requests' do click_link('Milestone') end - visit(merge_requests_dashboard_path(assignee_id: user.id)) + visit(merge_requests_dashboard_path(assignee_username: user.username)) expect(find('.issues-filters a.is-active')).to have_content('Milestone') @@ -41,7 +41,7 @@ describe 'User sorts merge requests' do it 'fallbacks to issuable_sort cookie key when remembering the sorting option' do set_cookie('issuable_sort', 'milestone') - visit(merge_requests_dashboard_path(assignee_id: user.id)) + visit(merge_requests_dashboard_path(assignee_username: user.username)) expect(find('.issues-filters a.is-active')).to have_content('Milestone') end diff --git a/spec/features/search/user_uses_header_search_field_spec.rb b/spec/features/search/user_uses_header_search_field_spec.rb index af38f77c0c6..444de26733f 100644 --- a/spec/features/search/user_uses_header_search_field_spec.rb +++ b/spec/features/search/user_uses_header_search_field_spec.rb @@ -21,13 +21,17 @@ describe 'User uses header search field' do it 'shows assigned issues' do find('.search-input-container .dropdown-menu').click_link('Issues assigned to me') - expect(find('.js-assignee-search')).to have_content(user.name) + expect(page).to have_selector('.filtered-search') + expect_tokens([assignee_token(user.name)]) + expect_filtered_search_input_empty end it 'shows created issues' do find('.search-input-container .dropdown-menu').click_link("Issues I've created") - expect(find('.js-author-search')).to have_content(user.name) + expect(page).to have_selector('.filtered-search') + expect_tokens([author_token(user.name)]) + expect_filtered_search_input_empty end end @@ -37,13 +41,17 @@ describe 'User uses header search field' do it 'shows assigned merge requests' do find('.search-input-container .dropdown-menu').click_link('Merge requests assigned to me') - expect(find('.js-assignee-search')).to have_content(user.name) + expect(page).to have_selector('.filtered-search') + expect_tokens([assignee_token(user.name)]) + expect_filtered_search_input_empty end it 'shows created merge requests' do find('.search-input-container .dropdown-menu').click_link("Merge requests I've created") - expect(find('.js-author-search')).to have_content(user.name) + expect(page).to have_selector('.filtered-search') + expect_tokens([author_token(user.name)]) + expect_filtered_search_input_empty end end end diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb index 8bfd520528f..4945749f524 100644 --- a/spec/helpers/search_helper_spec.rb +++ b/spec/helpers/search_helper_spec.rb @@ -135,5 +135,40 @@ describe SearchHelper do expect(search_filter_input_options('')[:data]['base-endpoint']).to eq("/groups#{group_path(@group)}") end end + + context 'dashboard' do + it 'does not include group-id and project-id' do + expect(search_filter_input_options('')[:data]['project-id']).to eq(nil) + expect(search_filter_input_options('')[:data]['group-id']).to eq(nil) + end + + it 'includes dashboard base-endpoint' do + expect(search_filter_input_options('')[:data]['base-endpoint']).to eq("/dashboard") + end + end + end + + describe 'search_history_storage_prefix' do + context 'project' do + it 'returns project full_path' do + @project = create(:project, :repository) + + expect(search_history_storage_prefix).to eq(@project.full_path) + end + end + + context 'group' do + it 'returns group full_path' do + @group = create(:group, :nested, name: 'group-name') + + expect(search_history_storage_prefix).to eq(@group.full_path) + end + end + + context 'dashboard' do + it 'returns dashboard' do + expect(search_history_storage_prefix).to eq("dashboard") + end + end end end diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js index 7530fd2a43b..7a4ca587313 100644 --- a/spec/javascripts/search_autocomplete_spec.js +++ b/spec/javascripts/search_autocomplete_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable no-var, one-var, no-unused-expressions, consistent-return, no-param-reassign, default-case, no-return-assign, object-shorthand, prefer-template, vars-on-top */ +/* eslint-disable no-var, one-var, no-unused-expressions, consistent-return, no-param-reassign, default-case, no-return-assign, object-shorthand, vars-on-top */ import $ from 'jquery'; import '~/gl_dropdown'; @@ -109,16 +109,16 @@ describe('Search autocomplete dropdown', () => { assertLinks = function(list, issuesPath, mrsPath) { if (issuesPath) { - const issuesAssignedToMeLink = `a[href="${issuesPath}/?assignee_id=${userId}"]`; - const issuesIHaveCreatedLink = `a[href="${issuesPath}/?author_id=${userId}"]`; + const issuesAssignedToMeLink = `a[href="${issuesPath}/?assignee_username=${userName}"]`; + const issuesIHaveCreatedLink = `a[href="${issuesPath}/?author_username=${userName}"]`; expect(list.find(issuesAssignedToMeLink).length).toBe(1); expect(list.find(issuesAssignedToMeLink).text()).toBe('Issues assigned to me'); expect(list.find(issuesIHaveCreatedLink).length).toBe(1); expect(list.find(issuesIHaveCreatedLink).text()).toBe("Issues I've created"); } - const mrsAssignedToMeLink = `a[href="${mrsPath}/?assignee_id=${userId}"]`; - const mrsIHaveCreatedLink = `a[href="${mrsPath}/?author_id=${userId}"]`; + const mrsAssignedToMeLink = `a[href="${mrsPath}/?assignee_username=${userName}"]`; + const mrsIHaveCreatedLink = `a[href="${mrsPath}/?author_username=${userName}"]`; expect(list.find(mrsAssignedToMeLink).length).toBe(1); expect(list.find(mrsAssignedToMeLink).text()).toBe('Merge requests assigned to me'); @@ -186,7 +186,7 @@ describe('Search autocomplete dropdown', () => { widget.searchInput.val('help'); widget.searchInput.triggerHandler('focus'); list = widget.wrap.find('.dropdown-menu').find('ul'); - link = "a[href='" + projectIssuesPath + '/?assignee_id=' + userId + "']"; + link = `a[href='${projectIssuesPath}/?assignee_username=${userName}']`; expect(list.find(link).length).toBe(0); }); diff --git a/spec/support/helpers/filter_item_select_helper.rb b/spec/support/helpers/filter_item_select_helper.rb deleted file mode 100644 index 519e84d359e..00000000000 --- a/spec/support/helpers/filter_item_select_helper.rb +++ /dev/null @@ -1,19 +0,0 @@ -# Helper allows you to select value from filter-items -# -# Params -# value - value for select -# selector - css selector of item -# -# Usage: -# -# filter_item_select('Any Author', '.js-author-search') -# -module FilterItemSelectHelper - def filter_item_select(value, selector) - find(selector).click - wait_for_requests - page.within('.dropdown-content') do - click_link value - end - end -end |