diff options
author | Ezekiel Kigbo <ekigbo@gitlab.com> | 2019-04-12 17:11:46 +0200 |
---|---|---|
committer | Ezekiel Kigbo <ekigbo@gitlab.com> | 2019-05-06 16:41:47 +0100 |
commit | 5f22907418397861d9b07cbaeea05ef7264d5605 (patch) | |
tree | ed1485b6f47a701e6271d15ae8160814287de3db | |
parent | aebf22f639073ab4d5c0ff4970d60bf24e8879f0 (diff) | |
download | gitlab-ce-5f22907418397861d9b07cbaeea05ef7264d5605.tar.gz |
Update project list specs
Add tests to ensure search only executes with a button click or enter,
sort by Name, Last updated, Created date and Stars and tests for
Visibility filter
-rw-r--r-- | app/assets/stylesheets/pages/projects.scss | 112 | ||||
-rw-r--r-- | app/views/dashboard/projects/_nav.html.haml | 5 | ||||
-rw-r--r-- | app/views/shared/projects/_search_bar.html.haml | 24 | ||||
-rw-r--r-- | app/views/shared/projects/_search_form.html.haml | 2 | ||||
-rw-r--r-- | app/views/shared/projects/_sort_dropdown.html.haml | 4 | ||||
-rw-r--r-- | changelogs/unreleased/56992-add-filtering-to-project-dashboard-fe.yml | 5 | ||||
-rw-r--r-- | spec/features/dashboard/projects_spec.rb | 13 | ||||
-rw-r--r-- | spec/features/dashboard/user_filters_projects_spec.rb | 226 |
8 files changed, 277 insertions, 114 deletions
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index ba57d6e12ec..6bd45ba3d4e 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -1448,10 +1448,6 @@ pre.light-well { } .project-filters { - .row-content-block { - border-top: 0; - } - .btn svg { color: $gl-gray-700; } @@ -1470,53 +1466,39 @@ pre.light-well { } } - .filtered-search-wrapper { - flex-wrap: nowrap; - flex-direction: row; - } - - .filtered-search-dropdown { - width: auto; - flex-direction: row; - align-items: center; - } - - .filtered-search, - .filtered-search-nav, - .filtered-search-dropdown { - display: flex; - min-width: auto; - margin: 0; - } - .filtered-search-dropdown-label { - padding: 0 0 0 16px; - font-weight: bold; min-width: 68px; + + @include media-breakpoint-down(xs) { + min-width: 60px; + } } .filtered-search { - margin: 0 0 0 16px; min-width: 30%; - width: 100%; flex: 1 1 0; .project-filter-form .project-filter-form-field { padding-right: 8px; } - } - .filtered-search, - .filtered-search-dropdown { - .btn-group { - width: 100%; + @include media-breakpoint-down(lg) { + min-width: 15%; + + .project-filter-form-field { + min-width: 150px; + } } - .qa-reverse-sort { - max-width: 38px; + @include media-breakpoint-down(md) { + min-width: 30%; } } + .qa-reverse-sort { + max-width: 38px; + } + .filtered-search-box { border-radius: 3px 0 0 3px; } @@ -1525,41 +1507,11 @@ pre.light-well { margin-left: 8px; } - @include media-breakpoint-down(lg) { - .filtered-search { - min-width: 15%; - - .project-filter-form-field { - min-width: 150px; - } - } - - .extended-filtered-search-box { - margin: 0; - min-width: 45%; - } - } - @include media-breakpoint-down(md) { - .filtered-search:not(.extended-filtered-search-box) { - margin: 0 0 8px 16px; - min-width: 30%; - } - .extended-filtered-search-box { - margin: 0 0 8px; min-width: 55%; } - .filtered-search-nav { - margin: 0 0 8px; - } - - - .filtered-search-wrapper { - flex-wrap: wrap; - } - .filtered-search-dropdown { width: 50%; } @@ -1579,43 +1531,11 @@ pre.light-well { width: 100%; } - .filtered-search-wrapper { - display: flex; - flex-flow: column nowrap; - } - .filtered-search, .filtered-search-nav, .filtered-search-dropdown { flex: 1 1 0; width: 100%; } - - .filtered-search:not(.extended-filtered-search-box), - .filtered-search { - margin-left: 0; - } - - .filtered-search-box { - margin: 0; - } - - .filtered-search-nav .nav-block { - width: 100%; - } - - .filtered-search-dropdown { - margin: 0 0 8px; - - &:last-of-type { - margin: 0; - } - } - - .filtered-search-dropdown-label { - padding: 0; - min-width: 60px; - } } } - diff --git a/app/views/dashboard/projects/_nav.html.haml b/app/views/dashboard/projects/_nav.html.haml index 8f4a517d918..b01e6c4293c 100644 --- a/app/views/dashboard/projects/_nav.html.haml +++ b/app/views/dashboard/projects/_nav.html.haml @@ -3,7 +3,7 @@ - inactive_class = 'btn p-2' - active_class = 'btn p-2 active' - is_explore_trending = local_assigns.fetch(:is_explore_trending, false) -.nav-block +.nav-block{ class: Feature.enabled?(:project_list_filter_bar) ? "w-100" : "" } - if !Feature.enabled?(:project_list_filter_bar) %ul.nav-links.mobile-separator.nav.nav-tabs = nav_link(html_options: { class: ("active" unless params[:personal].present?) }) do @@ -11,8 +11,7 @@ = nav_link(html_options: { class: ("active" if params[:personal].present?) }) do = link_to s_('DashboardProjects|Personal'), filter_projects_path(personal: true) - else - -# %ul.btn-group.button-filter-group.d-flex.m-0.p-0 - %div.btn-group.button-filter-group.d-flex.m-0.p-0 + .btn-group.button-filter-group.d-flex.m-0.p-0 - if is_explore = link_to s_('DashboardProjects|Trending'), trending_explore_projects_path, class: is_explore_trending ? active_class : inactive_class = link_to s_('DashboardProjects|All'), explore_projects_path, class: is_explore_trending ? inactive_class : active_class diff --git a/app/views/shared/projects/_search_bar.html.haml b/app/views/shared/projects/_search_bar.html.haml index 8c5dd25bee8..a70e60d7948 100644 --- a/app/views/shared/projects/_search_bar.html.haml +++ b/app/views/shared/projects/_search_bar.html.haml @@ -2,28 +2,28 @@ - is_explore = local_assigns.fetch(:is_explore, false) - is_explore_trending = local_assigns.fetch(:is_explore_trending, false) - without_tabs = local_assigns.fetch(:without_tabs, false) -.filtered-search-block.row-content-block - .filtered-search-wrapper.d-flex +.filtered-search-block.row-content-block.bt-0 + .filtered-search-wrapper.d-flex.flex-nowrap.flex-column.flex-sm-wrap.flex-sm-row.flex-xl-nowrap - unless without_tabs - .filtered-search-nav + .filtered-search-nav.d-flex.mb-2.mb-lg-0 = render 'dashboard/projects/nav', is_explore: is_explore, is_explore_trending: is_explore_trending - .filtered-search{ class: without_tabs ? "extended-filtered-search-box" : "" } - .btn-group{ role: "group" } - .btn-group{ role: "group" } - .filtered-search-box + .filtered-search.d-flex.w-100.mb-2.mb-lg-0{ class: without_tabs ? "extended-filtered-search-box ml-0 mb-2 mb-lg-0" : "ml-0 ml-sm-3" } + .btn-group.w-100{ role: "group" } + .btn-group.w-100{ role: "group" } + .filtered-search-box.m-0 .filtered-search-box-input-container.pl-2 = render 'shared/projects/search_form', admin_view: false, search_form_placeholder: _("Search projects...") -# TODO: since we are no longer triggering search when we type - -# we might be able to safely remove app/assets/javascripts/projects_list.js + -# we might be able to remove app/assets/javascripts/projects_list.js %button.btn.btn-secondary{ type: 'submit', form: 'project-filter-form' } = sprite_icon('search', size: 16, css_class: 'search-icon ') - .filtered-search-dropdown - .filtered-search-dropdown-label + .filtered-search-dropdown.d-flex.flex-row.align-items-center.mb-2.m-sm-0#filtered-search-visibility-dropdown + .filtered-search-dropdown-label.p-0.pl-sm-3.font-weight-bold %span = _("Visibility") = render 'explore/projects/filter', has_label: true - .filtered-search-dropdown - .filtered-search-dropdown-label + .filtered-search-dropdown.d-flex.flex-row.align-items-center.m-sm-0#filtered-search-sorting-dropdown + .filtered-search-dropdown-label.p-0.pl-sm-3.font-weight-bold %span = _("Sort by") = render 'shared/projects/sort_dropdown' diff --git a/app/views/shared/projects/_search_form.html.haml b/app/views/shared/projects/_search_form.html.haml index b85f51f37c5..7c7c0a363ac 100644 --- a/app/views/shared/projects/_search_form.html.haml +++ b/app/views/shared/projects/_search_form.html.haml @@ -1,4 +1,4 @@ -- form_field_classes = local_assigns[:admin_view] ? 'input-short' : '' +- form_field_classes = local_assigns[:admin_view] || !Feature.enabled?(:project_list_filter_bar) ? 'input-short js-projects-list-filter' : '' - placeholder = local_assigns[:search_form_placeholder] ? search_form_placeholder : 'Filter by name...' = form_tag filter_projects_path, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f| diff --git a/app/views/shared/projects/_sort_dropdown.html.haml b/app/views/shared/projects/_sort_dropdown.html.haml index 504ca772815..c806d0851db 100644 --- a/app/views/shared/projects/_sort_dropdown.html.haml +++ b/app/views/shared/projects/_sort_dropdown.html.haml @@ -1,6 +1,6 @@ - @sort ||= sort_value_latest_activity -.btn-group{ role: "group" } - .btn-group.dropdown.js-project-filter-dropdown-wrap{ role: "group" } +.btn-group.w-100{ role: "group" } + .btn-group.w-100.dropdown.js-project-filter-dropdown-wrap{ role: "group" } - toggle_text = projects_sort_option_titles[@sort] %button.dropdown-menu-toggle{ id: 'sort-projects-dropdown', type: 'button', data: { toggle: 'dropdown', display: 'static' }, class: 'btn btn-default' } = toggle_text diff --git a/changelogs/unreleased/56992-add-filtering-to-project-dashboard-fe.yml b/changelogs/unreleased/56992-add-filtering-to-project-dashboard-fe.yml new file mode 100644 index 00000000000..8519affd137 --- /dev/null +++ b/changelogs/unreleased/56992-add-filtering-to-project-dashboard-fe.yml @@ -0,0 +1,5 @@ +--- +title: Add filtering to project dashboard +merge_request: 25231 +author: +type: added diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb index 9d1c1e3acc7..8b5f645b2b4 100644 --- a/spec/features/dashboard/projects_spec.rb +++ b/spec/features/dashboard/projects_spec.rb @@ -112,6 +112,14 @@ describe 'Dashboard Projects' do expect(first('.project-row')).to have_content(project_with_most_stars.title) end + + it 'shows tabs to filter by all projects or personal' do + visit dashboard_projects_path + segmented_button = page.find('.filtered-search-nav .button-filter-group') + + expect(segmented_button).to have_content 'All' + expect(segmented_button).to have_content 'Personal' + end end context 'when on Starred projects tab', :js do @@ -134,6 +142,11 @@ describe 'Dashboard Projects' do expect(find('.nav-links li:nth-child(1) .badge-pill')).to have_content(1) expect(find('.nav-links li:nth-child(2) .badge-pill')).to have_content(1) end + + it 'does not show tabs to filter by all projects or personal' do + visit(starred_dashboard_projects_path) + expect(page).not_to have_content '.filtered-search-nav' + end end describe 'with a pipeline', :clean_gitlab_redis_shared_state do diff --git a/spec/features/dashboard/user_filters_projects_spec.rb b/spec/features/dashboard/user_filters_projects_spec.rb index cc86114e436..9945bf85997 100644 --- a/spec/features/dashboard/user_filters_projects_spec.rb +++ b/spec/features/dashboard/user_filters_projects_spec.rb @@ -14,6 +14,7 @@ describe 'Dashboard > User filters projects' do describe 'filtering personal projects' do before do + stub_feature_flags(project_list_filter_bar: false) project2.add_developer(user) visit dashboard_projects_path @@ -30,6 +31,7 @@ describe 'Dashboard > User filters projects' do describe 'filtering starred projects', :js do before do + stub_feature_flags(project_list_filter_bar: false) user.toggle_star(project) visit dashboard_projects_path @@ -42,4 +44,228 @@ describe 'Dashboard > User filters projects' do expect(page).not_to have_content('You don\'t have starred projects yet') end end + + describe 'without search bar', :js do + before do + stub_feature_flags(project_list_filter_bar: false) + + project2.add_developer(user) + visit dashboard_projects_path + end + + it 'will autocomplete searches', :js do + expect(page).to have_content 'Victorialand' + expect(page).to have_content 'Treasure' + + fill_in 'project-filter-form-field', with: 'Lord beerus\n' + + expect(page).not_to have_content 'Victorialand' + expect(page).not_to have_content 'Treasure' + end + end + + describe 'with search bar', :js do + before do + stub_feature_flags(project_list_filter_bar: true) + + project2.add_developer(user) + visit dashboard_projects_path + end + + # TODO: move these helpers somewhere more useful + def click_sort_direction + page.find('.filtered-search-block #filtered-search-sorting-dropdown .reverse-sort-btn').click + end + + def select_dropdown_option(selector, label) + dropdown = page.find(selector) + dropdown.click + + dropdown.find('.dropdown-menu a', text: label, match: :first).click + end + + def expect_to_see_projects(sorted_projects) + click_sort_direction + list = page.all('.projects-list .project-name').map(&:text) + expect(list).to match(sorted_projects) + end + + describe 'Search' do + it 'will execute when i click the search button' do + expect(page).to have_content 'Victorialand' + expect(page).to have_content 'Treasure' + + fill_in 'project-filter-form-field', with: 'Lord vegeta\n' + find('.filtered-search .btn').click + + expect(page).not_to have_content 'Victorialand' + expect(page).not_to have_content 'Treasure' + end + + it 'will execute when i press enter' do + expect(page).to have_content 'Victorialand' + expect(page).to have_content 'Treasure' + + fill_in 'project-filter-form-field', with: 'Lord frieza\n' + find('#project-filter-form-field').native.send_keys :enter + + expect(page).not_to have_content 'Victorialand' + expect(page).not_to have_content 'Treasure' + end + end + + describe 'Filter' do + before do + priv = create(:project, :private, name: 'Private project', namespace: user.namespace) + int = create(:project, :internal, name: 'Internal project', namespace: user.namespace) + + priv.add_maintainer(user) + int.add_maintainer(user) + end + + it 'can filter for only private projects' do + select_dropdown_option '#filtered-search-visibility-dropdown', 'Private' + expect(current_url).to match(/visibility_level=0/) + list = page.all('.projects-list .project-name').map(&:text) + expect(list).to match(["Private project", "Treasure", "Victorialand"]) + end + + it 'can filter for only internal projects' do + select_dropdown_option '#filtered-search-visibility-dropdown', 'Internal' + expect(current_url).to match(/visibility_level=10/) + list = page.all('.projects-list .project-name').map(&:text) + expect(list).to match(['Internal project']) + end + + it 'can filter for any project' do + select_dropdown_option '#filtered-search-visibility-dropdown', 'Any' + list = page.all('.projects-list .project-name').map(&:text) + expect(list).to match(["Internal project", "Private project", "Treasure", "Victorialand"]) + end + end + + describe 'Sorting' do + before do + [ + { name: 'Red ribbon army', created_at: 2.days.ago }, + { name: 'Cell saga', created_at: Time.now }, + { name: 'Frieza saga', created_at: 10.days.ago } + ].each do |item| + proj = create(:project, name: item[:name], namespace: user.namespace, created_at: item[:created_at]) + proj.add_developer(user) + end + + user.toggle_star(project) + user.toggle_star(project2) + user2.toggle_star(project2) + end + + it 'will include sorting direction' do + sorting_dropdown = page.find('.filtered-search-block #filtered-search-sorting-dropdown') + expect(sorting_dropdown).to have_css '.reverse-sort-btn' + end + + it 'will have all sorting options', :js do + sorting_dropdown = page.find('.filtered-search-block #filtered-search-sorting-dropdown') + sorting_option_labels = ['Last updated', 'Created date', 'Name', 'Stars'] + + sorting_dropdown.click + + sorting_option_labels.each do |label| + expect(sorting_dropdown).to have_content(label) + end + end + + it 'will default to Last updated', :js do + page.find('.filtered-search-block #filtered-search-sorting-dropdown').click + active_sorting_option = page.first('.filtered-search-block #filtered-search-sorting-dropdown .is-active') + + expect(active_sorting_option).to have_content 'Last updated' + end + + context 'Sorting by name' do + it 'will sort the project list' do + select_dropdown_option '#filtered-search-sorting-dropdown', 'Name' + + desc = ['Victorialand', 'Treasure', 'Red ribbon army', 'Frieza saga', 'Cell saga'] + asc = ['Cell saga', 'Frieza saga', 'Red ribbon army', 'Treasure', 'Victorialand'] + + expect_to_see_projects(desc) + expect_to_see_projects(asc) + end + + it 'will update the url query' do + select_dropdown_option '#filtered-search-sorting-dropdown', 'Name' + + [/sort=name_desc/, /sort=name_asc/].each do |query_param| + click_sort_direction + expect(current_url).to match(query_param) + end + end + end + + context 'Sorting by Last updated' do + it 'will sort the project list' do + select_dropdown_option '#filtered-search-sorting-dropdown', 'Last updated' + + desc = ["Frieza saga", "Red ribbon army", "Victorialand", "Treasure", "Cell saga"] + asc = ["Cell saga", "Treasure", "Victorialand", "Red ribbon army", "Frieza saga"] + + expect_to_see_projects(desc) + expect_to_see_projects(asc) + end + + it 'will update the url query' do + select_dropdown_option '#filtered-search-sorting-dropdown', 'Last updated' + + [/sort=latest_activity_asc/, /sort=latest_activity_desc/].each do |query_param| + click_sort_direction + expect(current_url).to match(query_param) + end + end + end + + context 'Sorting by Created date' do + it 'will sort the project list' do + select_dropdown_option '#filtered-search-sorting-dropdown', 'Created date' + + desc = ["Frieza saga", "Red ribbon army", "Victorialand", "Treasure", "Cell saga"] + asc = ["Cell saga", "Treasure", "Victorialand", "Red ribbon army", "Frieza saga"] + + expect_to_see_projects(desc) + expect_to_see_projects(asc) + end + + it 'will update the url query' do + select_dropdown_option '#filtered-search-sorting-dropdown', 'Created date' + + [/sort=created_asc/, /sort=created_desc/].each do |query_param| + click_sort_direction + expect(current_url).to match(query_param) + end + end + end + + context 'Sorting by Stars' do + it 'will sort the project list' do + select_dropdown_option '#filtered-search-sorting-dropdown', 'Stars' + + desc = ["Red ribbon army", "Cell saga", "Frieza saga", "Victorialand", "Treasure"] + asc = ["Treasure", "Victorialand", "Red ribbon army", "Cell saga", "Frieza saga"] + + expect_to_see_projects(desc) + expect_to_see_projects(asc) + end + + it 'will update the url query' do + select_dropdown_option '#filtered-search-sorting-dropdown', 'Stars' + + [/sort=stars_asc/, /sort=stars_desc/].each do |query_param| + click_sort_direction + expect(current_url).to match(query_param) + end + end + end + end + end end |