summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Knox <psimyn@gmail.com>2017-03-26 00:39:19 +1100
committerSimon Knox <psimyn@gmail.com>2017-06-27 20:46:39 +1000
commitb7c71c2ea3e0446add7631ae95dc20486198965b (patch)
treeed7639b881c6d54e5fbfdcaef642c21aa7524dc9
parent1a9317b821bc625eaad8550298d288bfc29e53d7 (diff)
downloadgitlab-ce-29321-project-search.tar.gz
update sort links when changing filter value on Projects page29321-project-search
change all the links to form inputs
-rw-r--r--app/assets/javascripts/dispatcher.js3
-rw-r--r--app/assets/javascripts/filterable_list.js36
-rw-r--r--app/assets/javascripts/groups/groups_filterable_list.js2
-rw-r--r--app/assets/javascripts/groups_list.js2
-rw-r--r--app/assets/javascripts/projects_list.js3
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss38
-rw-r--r--app/assets/stylesheets/framework/nav.scss11
-rw-r--r--app/assets/stylesheets/pages/search.scss2
-rw-r--r--app/views/admin/projects/index.html.haml15
-rw-r--r--app/views/dashboard/_projects_head.html.haml1
-rw-r--r--app/views/explore/projects/_nav.html.haml1
-rw-r--r--app/views/groups/show.html.haml1
-rw-r--r--app/views/shared/projects/_dropdown.html.haml51
-rw-r--r--app/views/shared/projects/_search_form.html.haml24
-rw-r--r--changelogs/unreleased/29321-project-search.yml4
-rw-r--r--spec/features/admin/admin_projects_spec.rb9
-rw-r--r--spec/features/dashboard/archived_projects_spec.rb43
-rw-r--r--spec/features/dashboard/projects_spec.rb81
-rw-r--r--spec/features/dashboard/user_filters_projects_spec.rb45
19 files changed, 215 insertions, 157 deletions
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 88b4b567fa9..9fcf33ff586 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -412,6 +412,9 @@ import initSettingsPanels from './settings_panels';
case 'import:fogbugz:new_user_map':
new UsersSelect();
break;
+ case 'root:index':
+ new ProjectsList();
+ break;
}
switch (path.first()) {
case 'sessions':
diff --git a/app/assets/javascripts/filterable_list.js b/app/assets/javascripts/filterable_list.js
index 139206cc185..6fbba0361f2 100644
--- a/app/assets/javascripts/filterable_list.js
+++ b/app/assets/javascripts/filterable_list.js
@@ -3,16 +3,34 @@
* Updates the html content of the page with the received one.
*/
+import './lib/utils/url_utility';
+
export default class FilterableList {
- constructor(form, filter, holder) {
+ constructor({ form, filter, holder, dropdownMenu }) {
this.filterForm = form;
this.listFilterElement = filter;
this.listHolderElement = holder;
+ this.dropdownMenu = dropdownMenu;
+ if (dropdownMenu) {
+ this.dropdownLabel = dropdownMenu.parentElement.querySelector('.dropdown-toggle-text');
+ }
this.isBusy = false;
}
getFilterEndpoint() {
- return `${this.filterForm.getAttribute('action')}?${$(this.filterForm).serialize()}`;
+ const url = this.filterForm.getAttribute('action');
+ const params = this.getFormParams();
+ return gl.utils.mergeUrlParams(params, url);
+ }
+
+ getFormParams() {
+ return $(this.filterForm)
+ .serializeArray()
+ .filter(field => field.value !== '')
+ .reduce((acc, field) => ({
+ ...acc,
+ [field.name]: field.value,
+ }), {});
}
getPagePath() {
@@ -22,6 +40,7 @@ export default class FilterableList {
initSearch() {
// Wrap to prevent passing event arguments to .filterResults;
this.debounceFilter = _.debounce(this.onFilterInput.bind(this), 500);
+ this.updateButtonText = this.updateButtonText.bind(this);
this.unbindEvents();
this.bindEvents();
@@ -45,10 +64,23 @@ export default class FilterableList {
bindEvents() {
this.listFilterElement.addEventListener('input', this.debounceFilter);
+
+ if (this.dropdownMenu) {
+ $(this.dropdownMenu).closest('form').on('change', this.debounceFilter);
+ $(this.dropdownMenu).find('input[name="sort"]').on('change', this.updateButtonText);
+ }
+ }
+
+ updateButtonText(evt) {
+ this.dropdownLabel.innerText = evt.target.title;
}
unbindEvents() {
this.listFilterElement.removeEventListener('input', this.debounceFilter);
+ if (this.dropdownMenu) {
+ $(this.dropdownMenu).closest('form').off('change', this.debounceFilter);
+ $(this.dropdownMenu).find('input[name="sort"]').off('change', this.updateButtonText);
+ }
}
filterResults(queryData) {
diff --git a/app/assets/javascripts/groups/groups_filterable_list.js b/app/assets/javascripts/groups/groups_filterable_list.js
index 439a931ddad..67720bb4c60 100644
--- a/app/assets/javascripts/groups/groups_filterable_list.js
+++ b/app/assets/javascripts/groups/groups_filterable_list.js
@@ -3,7 +3,7 @@ import eventHub from './event_hub';
export default class GroupFilterableList extends FilterableList {
constructor({ form, filter, holder, filterEndpoint, pagePath }) {
- super(form, filter, holder);
+ super({ form, filter, holder });
this.form = form;
this.filterEndpoint = filterEndpoint;
this.pagePath = pagePath;
diff --git a/app/assets/javascripts/groups_list.js b/app/assets/javascripts/groups_list.js
index 56a8cbf6d03..480a7ff9040 100644
--- a/app/assets/javascripts/groups_list.js
+++ b/app/assets/javascripts/groups_list.js
@@ -11,7 +11,7 @@ export default class GroupsList {
const holder = document.querySelector('.js-groups-list-holder');
if (form && filter && holder) {
- const list = new FilterableList(form, filter, holder);
+ const list = new FilterableList({ form, filter, holder });
list.initSearch();
}
}
diff --git a/app/assets/javascripts/projects_list.js b/app/assets/javascripts/projects_list.js
index c67d59d2be5..0f151e58943 100644
--- a/app/assets/javascripts/projects_list.js
+++ b/app/assets/javascripts/projects_list.js
@@ -9,9 +9,10 @@ export default class ProjectsList {
const form = document.querySelector('form#project-filter-form');
const filter = document.querySelector('.js-projects-list-filter');
const holder = document.querySelector('.js-projects-list-holder');
+ const dropdownMenu = document.querySelector('.nav-controls .dropdown-menu');
if (form && filter && holder) {
- const list = new FilterableList(form, filter, holder);
+ const list = new FilterableList({ form, filter, holder, dropdownMenu });
list.initSearch();
}
}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index cba890ce831..273f35a57c0 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -433,6 +433,44 @@
}
}
+.project-filter-form .dropdown {
+ display: inline-block;
+ margin-left: 10px;
+ vertical-align: top;
+
+ .dropdown-filter-label {
+ @include dropdown-link;
+ cursor: pointer;
+ margin-bottom: 0;
+ font-weight: normal;
+ }
+
+ .dropdown-filter-radio {
+ position: absolute;
+ opacity: 0;
+ top: auto;
+ overflow: hidden;
+
+ + .dropdown-filter-label .dropdown-filter-check {
+ visibility: hidden;
+ line-height: 1.5;
+ margin-right: 4px;
+ }
+
+ &:focus + .dropdown-filter-label {
+ outline: 5px auto $focus-border-color;
+ outline-offset: -2px;
+ }
+
+ &:checked + .dropdown-filter-label {
+ font-weight: bold;
+
+ .dropdown-filter-check {
+ visibility: visible;
+ }
+ }
+ }
+}
.dropdown-title {
position: relative;
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index 28b2a7cfacd..d31ec52b0fd 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -161,12 +161,21 @@
}
.nav-controls {
- display: inline-block;
+ display: flex;
+ flex-wrap: wrap;
float: right;
text-align: right;
padding: 11px 0;
margin-bottom: 0;
+ .project-filter-form {
+ display: flex;
+
+ @media (max-width: $screen-xs-max) {
+ flex-wrap: wrap;
+ }
+ }
+
> .btn,
> .btn-container,
> .dropdown,
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index b9818ffcf42..c4a59f56d41 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -197,7 +197,7 @@ input[type="checkbox"]:hover {
.search-field-holder,
.project-filter-form {
- -webkit-flex: 1 0 auto;
+ display: flex;
flex: 1 0 auto;
position: relative;
margin-right: 0;
diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml
index 3301f55b8a8..d4ae7b5d8aa 100644
--- a/app/views/admin/projects/index.html.haml
+++ b/app/views/admin/projects/index.html.haml
@@ -8,20 +8,7 @@
.top-area
.prepend-top-default
.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'
+ = render 'shared/projects/search_form', autofocus: true, icon: true, show_namespace_dropdown: true
= link_to new_project_path, class: 'btn btn-new' do
New Project
= button_tag "Search", class: "btn btn-primary btn-search hide"
diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml
index 64b737ee886..b62a4529138 100644
--- a/app/views/dashboard/_projects_head.html.haml
+++ b/app/views/dashboard/_projects_head.html.haml
@@ -16,7 +16,6 @@
.nav-controls
= 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
New project
diff --git a/app/views/explore/projects/_nav.html.haml b/app/views/explore/projects/_nav.html.haml
index e0a2a1e9c96..43f44369e21 100644
--- a/app/views/explore/projects/_nav.html.haml
+++ b/app/views/explore/projects/_nav.html.haml
@@ -13,5 +13,4 @@
.nav-controls
- unless current_user
= render 'shared/projects/search_form'
- = render 'shared/projects/dropdown'
= render 'filter'
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index 80a8ba4a755..6f8eb631726 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -11,7 +11,6 @@
= render 'groups/show_nav'
.nav-controls
= 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
New Project
diff --git a/app/views/shared/projects/_dropdown.html.haml b/app/views/shared/projects/_dropdown.html.haml
index 8939aeb6c3a..fc0aa566962 100644
--- a/app/views/shared/projects/_dropdown.html.haml
+++ b/app/views/shared/projects/_dropdown.html.haml
@@ -7,32 +7,51 @@
Sort by
- projects_sort_options_hash.each do |value, title|
%li
- = link_to filter_projects_path(sort: value), class: ("is-active" if @sort == value) do
- = title
+ = radio_button_tag :sort, value, @sort == value, title: title, class: 'dropdown-filter-radio'
+ = label_tag "sort_#{value}", { class: 'dropdown-filter-label' } do
+ = icon 'check', class: 'dropdown-filter-check'
+ %span= title
%li.divider
%li
- = link_to filter_projects_path(archived: nil), class: ("is-active" unless params[:archived].present?) do
- Hide archived projects
+ = radio_button_tag :archived, '', !params[:archived].present?, { class: 'dropdown-filter-radio' }
+ = label_tag :archived_, { class: 'dropdown-filter-label' } do
+ = icon 'check', class: 'dropdown-filter-check'
+ %span Hide archived projects
%li
- = link_to filter_projects_path(archived: true), class: ("is-active" if params[:archived].present?) do
- Show archived projects
+ = radio_button_tag :archived, true, params[:archived].present?, { class: 'dropdown-filter-radio' }
+ = label_tag :archived_true, { class: 'dropdown-filter-label' } do
+ = icon 'check', class: 'dropdown-filter-check'
+ %span Show archived projects
+
- if current_user
%li.divider
%li
- = link_to filter_projects_path(personal: nil), class: ("is-active" unless params[:personal].present?) do
- Owned by anyone
+ = radio_button_tag :personal, '', !params[:personal].present?, { class: 'dropdown-filter-radio' }
+ = label_tag :personal_, { class: 'dropdown-filter-label' } do
+ = icon 'check', class: 'dropdown-filter-check'
+ %span Owned by anyone
+
%li
- = link_to filter_projects_path(personal: true), class: ("is-active" if params[:personal].present?) do
- Owned by me
+ = radio_button_tag :personal, true, params[:personal].present?, { class: 'dropdown-filter-radio' }
+ = label_tag :personal_true, { class: 'dropdown-filter-label' } do
+ = icon 'check', class: 'dropdown-filter-check'
+ %span Owned by me
+
- if @group && @group.shared_projects.present?
%li.divider
%li
- = link_to filter_projects_path(shared: nil), class: ("is-active" unless params[:shared].present?) do
- All projects
+ = radio_button_tag :shared, '', !params[:shared].present?, { class: 'dropdown-filter-radio' }
+ = label_tag :shared_, { class: 'dropdown-filter-label' } do
+ = icon 'check', class: 'dropdown-filter-check'
+ %span All projects
%li
- = link_to filter_projects_path(shared: 0), class: ("is-active" if params[:shared] == '0') do
- Hide shared projects
+ = radio_button_tag :shared, '0', params[:shared] == '0', { class: 'dropdown-filter-radio' }
+ = label_tag :shared_0, { class: 'dropdown-filter-label' } do
+ = icon 'check', class: 'dropdown-filter-check'
+ %span Hide shared projects
%li
- = link_to filter_projects_path(shared: 1), class: ("is-active" if params[:shared] == '1') do
- Hide group projects
+ = radio_button_tag :shared, '1', params[:shared] == '1', { class: 'dropdown-filter-radio' }
+ = label_tag :shared_1, { class: 'dropdown-filter-label' } do
+ = icon 'check', class: 'dropdown-filter-check'
+ %span Hide group projects
diff --git a/app/views/shared/projects/_search_form.html.haml b/app/views/shared/projects/_search_form.html.haml
index b89194bcc67..b0f9634e1f0 100644
--- a/app/views/shared/projects/_search_form.html.haml
+++ b/app/views/shared/projects/_search_form.html.haml
@@ -10,14 +10,18 @@
- if local_assigns[:icon]
= icon("search", class: "search-icon")
- - if params[:sort].present?
- = hidden_field_tag :sort, params[:sort]
+ - if local_assigns[:show_namespace_dropdown]
+ .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
- - 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]
+ = render 'shared/projects/dropdown'
diff --git a/changelogs/unreleased/29321-project-search.yml b/changelogs/unreleased/29321-project-search.yml
new file mode 100644
index 00000000000..130a0b10365
--- /dev/null
+++ b/changelogs/unreleased/29321-project-search.yml
@@ -0,0 +1,4 @@
+---
+title: include filter query in project list sort dropdown links
+merge_request:
+author:
diff --git a/spec/features/admin/admin_projects_spec.rb b/spec/features/admin/admin_projects_spec.rb
index a4ce3e1d5ee..e38e838d8c0 100644
--- a/spec/features/admin/admin_projects_spec.rb
+++ b/spec/features/admin/admin_projects_spec.rb
@@ -24,15 +24,6 @@ describe "Admin::Projects", feature: true do
expect(page).to have_content(project.name)
expect(page).not_to have_content(archived_project.name)
end
-
- it 'renders all projects', js: true do
- find(:css, '#sort-projects-dropdown').click
- click_link 'Show archived projects'
-
- expect(page).to have_content(project.name)
- expect(page).to have_content(archived_project.name)
- expect(page).to have_xpath("//span[@class='label label-warning']", text: 'archived')
- end
end
describe "GET /admin/projects/:namespace_id/:id" do
diff --git a/spec/features/dashboard/archived_projects_spec.rb b/spec/features/dashboard/archived_projects_spec.rb
deleted file mode 100644
index a5ba3e7e3cf..00000000000
--- a/spec/features/dashboard/archived_projects_spec.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-require 'spec_helper'
-
-RSpec.describe 'Dashboard Archived Project', feature: true do
- let(:user) { create :user }
- let(:project) { create :project}
- let(:archived_project) { create(:project, :archived) }
-
- before do
- project.team << [user, :master]
- archived_project.team << [user, :master]
-
- gitlab_sign_in(user)
-
- visit dashboard_projects_path
- end
-
- it 'renders non archived projects' do
- expect(page).to have_link(project.name)
- expect(page).not_to have_link(archived_project.name)
- end
-
- it 'renders all projects' do
- click_link 'Show archived projects'
-
- 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
diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb
index 2a8185ca669..d907b7bac13 100644
--- a/spec/features/dashboard/projects_spec.rb
+++ b/spec/features/dashboard/projects_spec.rb
@@ -1,27 +1,88 @@
require 'spec_helper'
-RSpec.describe 'Dashboard Projects', feature: true do
+RSpec.describe 'Dashboard Projects', feature: true, js: true do
let(:user) { create(:user) }
+ let(:user2) { create(:user) }
let(:project) { create(:project, name: "awesome stuff") }
- let(:project2) { create(:project, :public, name: 'Community project') }
+ let(:project2) { create(:project, :public, name: 'Community project', namespace: user2.namespace) }
+ let(:archived_project) { create(:project, :archived) }
before do
project.team << [user, :developer]
+ project2.team << [user, :developer]
+ archived_project.team << [user, :developer]
gitlab_sign_in(user)
end
- it 'shows the project the user in a member of in the list' do
- visit dashboard_projects_path
- expect(page).to have_content('awesome stuff')
+ describe 'displays projects in list' do
+ it 'shows the projects the user is a member of' do
+ visit dashboard_projects_path
+ expect(page).to have_content(project.name)
+ end
+
+ it 'shows the last_activity_at attribute as the update date' do
+ now = Time.now
+ project.update_column(:last_activity_at, now)
+
+ visit dashboard_projects_path
+
+ expect(page).to have_xpath("//time[@datetime='#{now.getutc.iso8601}']")
+ end
end
- it 'shows the last_activity_at attribute as the update date' do
- now = Time.now
- project.update_column(:last_activity_at, now)
+ describe 'search and sort' do
+ before do
+ visit dashboard_projects_path
+ end
+
+ it 'filters by name' do
+ expect(page).to have_content(project.name)
+ expect(page).to have_content(project2.name)
+
+ fill_in 'name', with: project.name
- visit dashboard_projects_path
+ expect(page).to have_content(project.name)
+ expect(page).not_to have_content(project2.name)
+ end
- expect(page).to have_xpath("//time[@datetime='#{now.getutc.iso8601}']")
+ it 'shows projects owned by anyone' do
+ expect(page).to have_content(project.name)
+ expect(page).to have_content(project2.name)
+ end
+
+ it 'shows projects owned by me' do
+ select_from_sort_dropdown('Owned by me')
+
+ expect(page).to have_content(project.name)
+ expect(page).not_to have_content(project2.name)
+ end
+
+ it 'hides archived projects' do
+ expect(page).not_to have_content(archived_project.name)
+ end
+
+ it 'shows archived projects' do
+ select_from_sort_dropdown('Show archived projects')
+
+ expect(page).to have_content(archived_project.name)
+ expect(page).to have_css(".label-warning", text: 'archived')
+ end
+
+ it 'retains filter values when sorting' do
+ fill_in 'name', with: project.name
+
+ select_from_sort_dropdown('Show archived projects')
+
+ expect(page).to have_content(project.name)
+ expect(page).not_to have_content(project2.name)
+ end
+
+ def select_from_sort_dropdown(label_text)
+ find('#sort-projects-dropdown').click()
+ filter = "//li[contains(., '#{label_text}')]"
+
+ find(:xpath, filter).click()
+ end
end
context 'when on Starred projects tab' do
diff --git a/spec/features/dashboard/user_filters_projects_spec.rb b/spec/features/dashboard/user_filters_projects_spec.rb
deleted file mode 100644
index e9f34760143..00000000000
--- a/spec/features/dashboard/user_filters_projects_spec.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-require 'spec_helper'
-
-describe 'Dashboard > User filters projects', :feature do
- let(:user) { create(:user) }
- let(:project) { create(:project, name: 'Victorialand', namespace: user.namespace) }
- let(:user2) { create(:user) }
- let(:project2) { create(:project, name: 'Treasure', namespace: user2.namespace) }
-
- before do
- project.team << [user, :master]
-
- gitlab_sign_in(user)
- end
-
- describe 'filtering personal projects' do
- before do
- project2.team << [user, :developer]
-
- visit dashboard_projects_path
- end
-
- it 'filters by projects "Owned by me"' do
- click_link 'Owned by me'
-
- expect(page).to have_css('.is-active', text: 'Owned by me')
- expect(page).to have_content('Victorialand')
- expect(page).not_to have_content('Treasure')
- end
- end
-
- describe 'filtering starred projects', :js do
- before do
- user.toggle_star(project)
-
- visit dashboard_projects_path
- end
-
- it 'returns message when starred projects fitler returns no results' do
- fill_in 'project-filter-form-field', with: 'Beta\n'
-
- expect(page).to have_content('No projects found')
- expect(page).not_to have_content('You don\'t have starred projects yet')
- end
- end
-end