Commit 2d006b46 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'dashboard-filter-search-keep-params' into 'master'

Fixes filtering by name reseting archive filter

Closes #28007

See merge request !9625
parents d6733662 c13c1e1b
......@@ -36,6 +36,7 @@
/* global Shortcuts */
import GroupsList from './groups_list';
import ProjectsList from './projects_list';
const ShortcutsBlob = require('./shortcuts_blob');
const UserCallout = require('./user_callout');
......@@ -98,6 +99,14 @@ const UserCallout = require('./user_callout');
case 'dashboard:todos:index':
new gl.Todos();
break;
case 'dashboard:projects:index':
case 'dashboard:projects:starred':
case 'explore:projects:index':
case 'explore:projects:trending':
case 'explore:projects:starred':
case 'admin:projects:index':
new ProjectsList();
break;
case 'dashboard:groups:index':
case 'explore:groups:index':
new GroupsList();
......@@ -163,9 +172,6 @@ const UserCallout = require('./user_callout');
case 'dashboard:activity':
new gl.Activities();
break;
case 'dashboard:projects:starred':
new gl.Activities();
break;
case 'projects:commit:show':
new Commit();
new gl.Diff();
......@@ -208,6 +214,7 @@ const UserCallout = require('./user_callout');
shortcut_handler = new ShortcutsNavigation();
new NotificationsForm();
new NotificationsDropdown();
new ProjectsList();
break;
case 'groups:group_members:index':
new gl.MemberExpirationDate();
......
/**
* Makes search request for content when user types a value in the search input.
* Updates the html content of the page with the received one.
*/
export default class FilterableList {
constructor(form, filter, holder) {
this.filterForm = form;
this.listFilterElement = filter;
this.listHolderElement = holder;
}
initSearch() {
this.debounceFilter = _.debounce(this.filterResults.bind(this), 500);
this.listFilterElement.removeEventListener('input', this.debounceFilter);
this.listFilterElement.addEventListener('input', this.debounceFilter);
}
filterResults() {
const form = this.filterForm;
const filterUrl = `${form.getAttribute('action')}?${$(form).serialize()}`;
$(this.listHolderElement).fadeTo(250, 0.5);
return $.ajax({
url: form.getAttribute('action'),
data: $(form).serialize(),
type: 'GET',
dataType: 'json',
context: this,
complete() {
$(this.listHolderElement).fadeTo(250, 1);
},
success(data) {
this.listHolderElement.innerHTML = data.html;
// Change url so if user reload a page - search results are saved
return window.history.replaceState({
page: filterUrl,
}, document.title, filterUrl);
},
});
}
}
import FilterableList from './filterable_list';
/**
* Based on project list search.
* Makes search request for groups when user types a value in the search input.
* Updates the html content of the page with the received one.
*/
export default class GroupsList {
constructor() {
this.groupsListFilterElement = document.querySelector('.js-groups-list-filter');
this.groupsListHolderElement = document.querySelector('.js-groups-list-holder');
this.initSearch();
}
initSearch() {
this.debounceFilter = _.debounce(this.filterResults.bind(this), 500);
this.groupsListFilterElement.removeEventListener('input', this.debounceFilter);
this.groupsListFilterElement.addEventListener('input', this.debounceFilter);
}
filterResults() {
const form = document.querySelector('form#group-filter-form');
const groupFilterUrl = `${form.getAttribute('action')}?${$(form).serialize()}`;
$(this.groupsListHolderElement).fadeTo(250, 0.5);
return $.ajax({
url: form.getAttribute('action'),
data: $(form).serialize(),
type: 'GET',
dataType: 'json',
context: this,
complete() {
$(this.groupsListHolderElement).fadeTo(250, 1);
},
success(data) {
this.groupsListHolderElement.innerHTML = data.html;
// Change url so if user reload a page - search results are saved
return window.history.replaceState({
page: groupFilterUrl,
const filter = document.querySelector('.js-groups-list-filter');
const holder = document.querySelector('.js-groups-list-holder');
}, document.title, groupFilterUrl);
},
});
if (form && filter && holder) {
const list = new FilterableList(form, filter, holder);
list.initSearch();
}
}
}
/* eslint-disable func-names, space-before-function-paren, object-shorthand, quotes, no-var, one-var, one-var-declaration-per-line, prefer-arrow-callback, consistent-return, no-unused-vars, camelcase, prefer-template, comma-dangle, max-len */
import FilterableList from './filterable_list';
(function() {
window.ProjectsList = {
init: function() {
$(".projects-list-filter").off('keyup');
this.initSearch();
return this.initPagination();
},
initSearch: function() {
var debounceFilter, projectsListFilter;
projectsListFilter = $('.projects-list-filter');
debounceFilter = _.debounce(window.ProjectsList.filterResults, 500);
return projectsListFilter.on('keyup', function(e) {
if (projectsListFilter.val() !== '') {
return debounceFilter();
}
});
},
filterResults: function() {
var form, project_filter_url, search;
$('.projects-list-holder').fadeTo(250, 0.5);
form = null;
form = $("form#project-filter-form");
search = $(".projects-list-filter").val();
project_filter_url = form.attr('action') + '?' + form.serialize();
return $.ajax({
type: "GET",
url: form.attr('action'),
data: form.serialize(),
complete: function() {
return $('.projects-list-holder').fadeTo(250, 1);
},
success: function(data) {
$('.projects-list-holder').replaceWith(data.html);
return history.replaceState({
page: project_filter_url
// Change url so if user reload a page - search results are saved
}, document.title, project_filter_url);
},
dataType: "json"
});
},
initPagination: function() {
return $('.projects-list-holder .pagination').on('ajax:success', function(e, data) {
return $('.projects-list-holder').replaceWith(data.html);
});
/**
* Makes search request for projects when user types a value in the search input.
* Updates the html content of the page with the received one.
*/
export default class ProjectsList {
constructor() {
const form = document.querySelector('form#project-filter-form');
const filter = document.querySelector('.js-projects-list-filter');
const holder = document.querySelector('.js-projects-list-holder');
if (form && filter && holder) {
const list = new FilterableList(form, filter, holder);
list.initSearch();
}
};
}).call(window);
}
}
......@@ -182,7 +182,8 @@ input[type="checkbox"]:hover {
display: flex;
}
.search-field-holder {
.search-field-holder,
.project-filter-form {
-webkit-flex: 1 0 auto;
flex: 1 0 auto;
position: relative;
......@@ -201,7 +202,8 @@ input[type="checkbox"]:hover {
pointer-events: none;
}
.search-text-input {
.search-text-input,
.project-filter-form-field {
padding-left: $gl-padding + 15px;
padding-right: $gl-padding + 15px;
}
......
......@@ -14,6 +14,15 @@ class Admin::ProjectsController < Admin::ApplicationController
@projects = @projects.search(params[:name]) if params[:name].present?
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page])
respond_to do |format|
format.html
format.json do
render json: {
html: view_to_html_string("admin/projects/_projects", locals: { projects: @projects })
}
end
end
end
def show
......
......@@ -8,7 +8,7 @@ module FilterProjects
extend ActiveSupport::Concern
def filter_projects(projects)
projects = projects.search(params[:filter_projects]) if params[:filter_projects].present?
projects = projects.search(params[:name]) if params[:name].present?
projects = projects.non_archived if params[:archived].blank?
projects = projects.personal(current_user) if params[:personal].present? && current_user
......
......@@ -108,7 +108,7 @@ class GroupsController < Groups::ApplicationController
@projects = @projects.sorted_by_activity
@projects = filter_projects(@projects)
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.page(params[:page]) if params[:filter_projects].blank?
@projects = @projects.page(params[:page]) if params[:name].blank?
end
def authorize_create_group!
......
module ExploreHelper
def filter_projects_path(options = {})
exist_opts = {
sort: params[:sort],
sort: params[:sort] || @sort,
scope: params[:scope],
group: params[:group],
tag: params[:tag],
visibility_level: params[:visibility_level],
name: params[:name],
personal: params[:personal],
archived: params[:archived],
shared: params[:shared],
namespace_id: params[:namespace_id],
}
options = exist_opts.merge(options)
options = exist_opts.merge(options).delete_if { |key, value| value.blank? }
request_path_with_options(options)
end
......
.js-projects-list-holder
- if @projects.any?
%ul.projects-list.content-list
- @projects.each_with_index do |project|
%li.project-row
.controls
- if project.archived
%span.label.label-warning archived
%span.badge
= storage_counter(project.statistics.storage_size)
= link_to 'Edit', edit_namespace_project_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn"
= link_to 'Delete', [project.namespace.becomes(Namespace), project], data: { confirm: remove_project_message(project) }, method: :delete, class: "btn btn-remove"
.title
= link_to [:admin, project.namespace.becomes(Namespace), project] do
.dash-project-avatar
.avatar-container.s40
= project_icon(project, alt: '', class: 'avatar project-avatar s40')
%span.project-full-name
%span.namespace-name
- if project.namespace
= project.namespace.human_name
\/
%span.project-name.filter-title
= project.name
- if project.description.present?
.description
= markdown_field(project, :description)
= paginate @projects, theme: 'gitlab'
- else
.nothing-here-block No projects found
......@@ -7,40 +7,24 @@
%div{ class: container_class }
.top-area
.prepend-top-default
= form_tag admin_projects_path, method: :get do |f|
.search-holder
.search-field-holder
= search_field_tag :name, params[:name], class: "form-control search-text-input js-search-input", id: "dashboard_search", autofocus: true, spellcheck: false, placeholder: 'Search by name'
- if params[:visibility_level].present?
= hidden_field_tag 'visibility_level', params[:visibility_level]
- if params[:sort].present?
= hidden_field_tag 'sort', params[:sort]
- if params[:personal].present?
= hidden_field_tag 'visibility_level', 'true'
- if params[:archived].present?
= hidden_field_tag 'archived', 'true'
= icon("search", class: "search-icon")
.dropdown
- toggle_text = 'Namespace'
- if params[:namespace_id].present?
- namespace = Namespace.find(params[:namespace_id])
- toggle_text = "#{namespace.kind}: #{namespace.full_path}"
= dropdown_toggle(toggle_text, { toggle: 'dropdown' }, { toggle_class: 'js-namespace-select large' })
.dropdown-menu.dropdown-select.dropdown-menu-align-right
= dropdown_title('Namespaces')
= dropdown_filter("Search for Namespace")
= dropdown_content
= dropdown_loading
= render 'shared/projects/dropdown'
= link_to new_project_path, class: 'btn btn-new' do
New Project
= button_tag "Search", class: "btn btn-primary btn-search hide"
.search-holder
= render 'shared/projects/search_form', autofocus: true, icon: true
.dropdown
- toggle_text = 'Namespace'
- if params[:namespace_id].present?
= hidden_field_tag :namespace_id, params[:namespace_id]
- namespace = Namespace.find(params[:namespace_id])
- toggle_text = "#{namespace.kind}: #{namespace.full_path}"
= dropdown_toggle(toggle_text, { toggle: 'dropdown' }, { toggle_class: 'js-namespace-select large' })
.dropdown-menu.dropdown-select.dropdown-menu-align-right
= dropdown_title('Namespaces')
= dropdown_filter("Search for Namespace")
= dropdown_content
= dropdown_loading
= render 'shared/projects/dropdown'
= link_to new_project_path, class: 'btn btn-new' do
New Project
= button_tag "Search", class: "btn btn-primary btn-search hide"
%ul.nav-links
- opts = params[:visibility_level].present? ? {} : { page: admin_projects_path }
......@@ -58,35 +42,4 @@
= link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PUBLIC) do
Public
.projects-list-holder
- if @projects.any?
%ul.projects-list.content-list
- @projects.each_with_index do |project|
%li.project-row
.controls
- if project.archived
%span.label.label-warning archived
%span.badge
= storage_counter(project.statistics.storage_size)
= link_to 'Edit', edit_namespace_project_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn"
= link_to 'Delete', [project.namespace.becomes(Namespace), project], data: { confirm: remove_project_message(project) }, method: :delete, class: "btn btn-remove"
.title
= link_to [:admin, project.namespace.becomes(Namespace), project] do
.dash-project-avatar
.avatar-container.s40
= project_icon(project, alt: '', class: 'avatar project-avatar s40')
%span.project-full-name
%span.namespace-name
- if project.namespace
= project.namespace.human_name
\/
%span.project-name.filter-title
= project.name
- if project.description.present?
.description
= markdown_field(project, :description)
= paginate @projects, theme: 'gitlab'
- else
.nothing-here-block No projects found
= render 'projects'
......@@ -7,8 +7,7 @@
= link_to explore_groups_path, title: 'Explore groups' do
Explore Groups
.nav-controls
= form_tag request.path, method: :get, class: 'group-filter-form', id: 'group-filter-form' do |f|
= search_field_tag :filter_groups, params[:filter_groups], placeholder: 'Filter by name...', class: 'group-filter-form-field form-control input-short js-groups-list-filter', spellcheck: false, id: 'group-filter-form-field', tabindex: "2"
= render 'shared/groups/search_form'
= render 'shared/groups/dropdown'
- if current_user.can_create_group?
= link_to new_group_path, class: "btn btn-new" do
......
......@@ -13,8 +13,7 @@
Explore projects
.nav-controls
= form_tag request.path, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
= search_field_tag :filter_projects, params[:filter_projects], placeholder: 'Filter by name...', class: 'project-filter-form-field form-control input-short projects-list-filter', spellcheck: false, id: 'project-filter-form-field', tabindex: "2"
= render 'shared/projects/search_form'
= render 'shared/projects/dropdown'
- if current_user.can_create_project?
= link_to new_project_path, class: 'btn btn-new' do
......
......@@ -5,14 +5,13 @@
- header_title "Projects", dashboard_projects_path
.user-callout{ 'callout-svg' => custom_icon('icon_customization') }
- if @projects.any? || params[:filter_projects]
- if @projects.any? || params[:name]
= render 'dashboard/projects_head'
- if @last_push
= render "events/event_last_push", event: @last_push
- if @projects.any? || params[:filter_projects]
- if @projects.any? || params[:name]
= render 'projects'
- else
= render "zero_authorized_projects"
.top-area
%ul.nav-links
= nav_link(page: explore_groups_path) do
= link_to explore_groups_path do
Explore Groups
.nav-controls
= render 'shared/groups/search_form'
= render 'shared/groups/dropdown'
......@@ -5,7 +5,7 @@
= render 'dashboard/groups_head'
- else
= render 'explore/head'
= render 'nav'
- if @groups.present?
= render 'groups'
......
%ul.nav-links
= nav_link(page: [trending_explore_projects_path, explore_root_path]) do
= link_to trending_explore_projects_path do
Trending
= nav_link(page: starred_explore_projects_path) do
= link_to starred_explore_projects_path do
Most stars
= nav_link(page: explore_projects_path) do
= link_to explore_projects_path do
All
.top-area
%ul.nav-links
= nav_link(page: [trending_explore_projects_path, explore_root_path]) do
= link_to trending_explore_projects_path do
Trending
= nav_link(page: starred_explore_projects_path) do
= link_to starred_explore_projects_path do
Most stars
= nav_link(page: explore_projects_path) do
= link_to explore_projects_path do
All
.nav-controls
- unless current_user
= render 'shared/projects/search_form'
= render 'shared/projects/dropdown'
= render 'filter'
......@@ -6,10 +6,5 @@
- else
= render 'explore/head'
.top-area
= render 'explore/projects/nav'
.nav-controls
= render 'filter'
= render 'explore/projects/nav'
= render 'projects', projects: @projects
......@@ -11,8 +11,7 @@
.top-area
= render 'groups/show_nav'
.nav-controls
= form_tag request.path, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false
= render 'shared/projects/search_form'
= render 'shared/projects/dropdown'
- if can? current_user, :create_projects, @group
= link_to new_project_path(namespace_id: @group.id), class: 'btn btn-new pull-right' do
......
= form_tag request.path, method: :get, class: 'group-filter-form', id: 'group-filter-form' do |f|
= search_field_tag :filter_groups, params[:filter_groups], placeholder: 'Filter by name...', class: 'group-filter-form-field form-control input-short js-groups-list-filter', spellcheck: false, id: 'group-filter-form-field', tabindex: "2"
- @sort ||= sort_value_recently_updated
- personal = params[:personal]
- archived = params[:archived]
- shared = params[:shared]
- namespace_id = params[:namespace_id]
.dropdown
- toggle_text = projects_sort_options_hash[@sort]
= dropdown_toggle(toggle_text, { toggle: 'dropdown' }, { id: 'sort-projects-dropdown' })
......@@ -11,32 +7,32 @@
Sort by
- projects_sort_options_hash.each do |value, title|
%li
= link_to filter_projects_path(namespace_id: namespace_id, sort: value, archived: archived, personal: personal), class: ("is-active" if @sort == value) do
= link_to filter_projects_path(sort: value), class: ("is-active" if @sort == value) do
= title
%li.divider
%li
= link_to filter_projects_path(namespace_id: namespace_id, sort: @sort, archived: nil), class: ("is-active" unless params[:archived].present?) do
= link_to filter_projects_path(archived: nil), class: ("is-active" unless params[:archived].present?) do
Hide archived projects
%li
= link_to filter_projects_path(namespace_id: namespace_id, sort: @sort, archived: true), class: ("is-active" if params[:archived].present?) do
= link_to filter_projects_path(archived: true), class: ("is-active" if params[:archived].present?) do
Show archived projects
- if current_user
%li.divider
%li
= link_to filter_projects_path(namespace_id: namespace_id, sort: @sort, personal: nil), class: ("is-active" unless personal.present?) do
= link_to filter_projects_path(personal: nil), class: ("is-active" unless params[:personal].present?) do
Owned by anyone
%li
= link_to filter_projects_path(namespace_id: namespace_id, sort: @sort, personal: true), class: ("is-active" if personal.present?) do
= link_to filter_projects_path(personal: true), class: ("is-active" if params[:personal].present?) do
Owned by me
- if @group && @group.shared_projects.present?
%li.divider
%li
= link_to filter_projects_path(namespace_id: namespace_id, sort: @sort, shared: nil), class: ("is-active" unless shared.present?) do
= link_to filter_projects_path(shared: nil), class: ("is-active" unless params[:shared].present?) do
All projects
%li
= link_to filter_projects_path(namespace_id: namespace_id, sort: @sort, shared: 0), class: ("is-active" if shared == '0') do
= link_to filter_projects_path(shared: 0), class: ("is-active" if params[:shared] == '0') do
Hide shared projects
%li
= link_to filter_projects_path(namespace_id: namespace_id, sort: @sort, shared: 1), class: ("is-active" if shared == '1') do
= link_to filter_projects_path(shared: 1), class: ("is-active" if params[:shared] == '1') do
Hide group projects
......@@ -8,7 +8,7 @@
- show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true
- remote = false unless local_assigns[:remote] == true
.projects-list-holder
.js-projects-list-holder
- if projects.any?
%ul.projects-list.content-list
- projects.each_with_index do |project, i|
......@@ -25,6 +25,3 @@
= paginate(projects, remote: remote, theme: "gitlab") if projects.respond_to? :total_pages
- else
.nothing-here-block No projects found
:javascript
ProjectsList.init();
= form_tag filter_projects_path, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
= search_field_tag :name, params[:name],
placeholder: 'Filter by name...',
class: 'project-filter-form-field form-control input-short js-projects-list-filter',
spellcheck: false,
id: 'project-filter-form-field',
tabindex: "2",
autofocus: local_assigns[:autofocus]
- if local_assigns[:icon]
= icon("search", class: "search-icon")
- if params[:sort].present?
= hidden_field_tag :sort, params[:sort]
- if params[:personal].present?
= hidden_field_tag :personal, params[:personal]
- if params[:archived].present?
= hidden_field_tag :archived, params[:archived]
- if params[:visibility_level].present?
= hidden_field_tag :visibility_level, params[:visibility_level]
---
title: Dashboard project search keeps selected sort & filters
merge_request:
author:
......@@ -56,7 +56,7 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps
end
step 'I should see my fork on the list' do
page.within('.projects-list-holder') do
page.within('.js-projects-list-holder') do
project = @user.fork_of(@project)
expect(page).to have_content("#{project.namespace.human_name} / #{project.name}")
end
......
......@@ -166,11 +166,15 @@ module SharedProject
end
step 'I should see project "Internal"' do
expect(page).to have_content "Internal"
page.within '.js-projects-list-holder' do
expect(page).to have_content "Internal"
end
end
step 'I should not see project "Internal"' do
expect(page).not_to have_content "Internal"
page.within '.js-projects-list-holder' do
expect(page).not_to have_content "Internal"
end
end
step 'public project "Community"' do
......
......@@ -25,4 +25,19 @@ RSpec.describe 'Dashboard Archived Project', feature: true do
expect(page).to have_link(project.name)
expect(page).to have_link(archived_project.name)
end
it 'searchs archived projects', :js do
click_button 'Last updated'
click_link 'Show archived projects'
expect(page).to have_link(project.name)
expect(page).to have_link(archived_project.name)
fill_in 'project-filter-form-field', with: archived_project.name
find('#project-filter-form-field').native.send_keys :return
expect(page).not_to have_link(project.name)
expect(page).to have_link(archived_project.name)
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment