summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEzekiel Kigbo <ekigbo@gitlab.com>2019-04-12 17:11:46 +0200
committerEzekiel Kigbo <ekigbo@gitlab.com>2019-05-06 16:41:47 +0100
commit5f22907418397861d9b07cbaeea05ef7264d5605 (patch)
treeed1485b6f47a701e6271d15ae8160814287de3db
parentaebf22f639073ab4d5c0ff4970d60bf24e8879f0 (diff)
downloadgitlab-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.scss112
-rw-r--r--app/views/dashboard/projects/_nav.html.haml5
-rw-r--r--app/views/shared/projects/_search_bar.html.haml24
-rw-r--r--app/views/shared/projects/_search_form.html.haml2
-rw-r--r--app/views/shared/projects/_sort_dropdown.html.haml4
-rw-r--r--changelogs/unreleased/56992-add-filtering-to-project-dashboard-fe.yml5
-rw-r--r--spec/features/dashboard/projects_spec.rb13
-rw-r--r--spec/features/dashboard/user_filters_projects_spec.rb226
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