Merge branch '52385-search-bar-for-dashboard-list' into 'master'

Resolve "Search filter bar for issues dashboard list"

Closes #53584, #52430, and #52385

See merge request gitlab-org/gitlab-ce!22641
This commit is contained in:
Sean McGivern 2018-11-19 09:54:03 +00:00
commit c70c403829
36 changed files with 187 additions and 311 deletions

View File

@ -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();
};

View File

@ -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();
});

View File

@ -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();
});

View File

@ -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}`,
},
];

View File

@ -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

View File

@ -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

View File

@ -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 }

View File

@ -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

View File

@ -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

View File

@ -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|

View File

@ -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

View File

@ -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)

View File

@ -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'

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,5 @@
---
title: Use search bar for filtering in dashboard issues / MRs
merge_request: 22641
author: Heinrich Lee Yu
type: changed

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

View File

@ -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)

View File

@ -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',

View File

@ -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

View File

@ -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}')]")

View File

@ -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)

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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')

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);
});

View File

@ -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