diff options
author | Simon Knox <psimyn@gmail.com> | 2017-03-26 00:39:19 +1100 |
---|---|---|
committer | Simon Knox <psimyn@gmail.com> | 2017-06-27 20:46:39 +1000 |
commit | b7c71c2ea3e0446add7631ae95dc20486198965b (patch) | |
tree | ed7639b881c6d54e5fbfdcaef642c21aa7524dc9 | |
parent | 1a9317b821bc625eaad8550298d288bfc29e53d7 (diff) | |
download | gitlab-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
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 |