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:
commit
c70c403829
|
@ -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();
|
||||
};
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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}`,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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 |
|
@ -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)
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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}')]")
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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')
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue