Commit e33a8baf authored by Kushal Pandya's avatar Kushal Pandya

Merge branch '56992-add-filtering-to-project-dashboard-fe' into 'master'

Resolve "Add filtering to project dashboard [FE]"

Closes #56992

See merge request gitlab-org/gitlab-ce!25231
parents 552e9f74 ffa9266a
...@@ -1446,3 +1446,86 @@ pre.light-well { ...@@ -1446,3 +1446,86 @@ pre.light-well {
} }
} }
} }
.project-filters {
.btn svg {
color: $gl-gray-700;
}
.button-filter-group {
.btn {
width: 96px;
}
a {
color: $black;
}
.active {
background: $btn-active-gray;
}
}
.filtered-search-dropdown-label {
min-width: 68px;
@include media-breakpoint-down(xs) {
min-width: 60px;
}
}
.filtered-search {
min-width: 30%;
flex-basis: 0;
.project-filter-form .project-filter-form-field {
padding-right: $gl-padding-8;
}
.filtered-search,
.filtered-search-nav,
.filtered-search-dropdown {
flex-basis: 0;
}
@include media-breakpoint-down(lg) {
min-width: 15%;
.project-filter-form-field {
min-width: 150px;
}
}
@include media-breakpoint-down(md) {
min-width: 30%;
}
}
.filtered-search-box {
border-radius: 3px 0 0 3px;
}
.dropdown-menu-toggle {
margin-left: $gl-padding-8;
}
@include media-breakpoint-down(md) {
.extended-filtered-search-box {
min-width: 55%;
}
.filtered-search-dropdown {
width: 50%;
.dropdown-menu-toggle {
width: 100%;
}
}
}
@include media-breakpoint-down(xs) {
.filtered-search-dropdown {
width: 100%;
}
}
}
...@@ -239,8 +239,10 @@ module ProjectsHelper ...@@ -239,8 +239,10 @@ module ProjectsHelper
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
# TODO: Remove this method when removing the feature flag
# https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/11209#note_162234863
def show_projects?(projects, params) def show_projects?(projects, params)
!!(params[:personal] || params[:name] || any_projects?(projects)) Feature.enabled?(:project_list_filter_bar) || !!(params[:personal] || params[:name] || any_projects?(projects))
end end
def push_to_create_project_command(user = current_user) def push_to_create_project_command(user = current_user)
......
...@@ -128,7 +128,7 @@ module SearchHelper ...@@ -128,7 +128,7 @@ module SearchHelper
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def projects_autocomplete(term, limit = 5) def projects_autocomplete(term, limit = 5)
current_user.authorized_projects.order_id_desc.search_by_title(term) current_user.authorized_projects.order_id_desc.search_by_title(term)
.sorted_by_stars.non_archived.limit(limit).map do |p| .sorted_by_stars_desc.non_archived.limit(limit).map do |p|
{ {
category: "Projects", category: "Projects",
id: p.id, id: p.id,
......
...@@ -30,13 +30,20 @@ module SortingHelper ...@@ -30,13 +30,20 @@ module SortingHelper
end end
def projects_sort_options_hash def projects_sort_options_hash
Feature.enabled?(:project_list_filter_bar) && !current_controller?('admin/projects') ? projects_sort_common_options_hash : old_projects_sort_options_hash
end
# TODO: Simplify these sorting options
# https://gitlab.com/gitlab-org/gitlab-ce/issues/60798
# https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/11209#note_162234858
def old_projects_sort_options_hash
options = { options = {
sort_value_latest_activity => sort_title_latest_activity, sort_value_latest_activity => sort_title_latest_activity,
sort_value_name => sort_title_name, sort_value_name => sort_title_name,
sort_value_oldest_activity => sort_title_oldest_activity, sort_value_oldest_activity => sort_title_oldest_activity,
sort_value_oldest_created => sort_title_oldest_created, sort_value_oldest_created => sort_title_oldest_created,
sort_value_recently_created => sort_title_recently_created, sort_value_recently_created => sort_title_recently_created,
sort_value_most_stars => sort_title_most_stars sort_value_stars_desc => sort_title_most_stars
} }
if current_controller?('admin/projects') if current_controller?('admin/projects')
...@@ -46,6 +53,41 @@ module SortingHelper ...@@ -46,6 +53,41 @@ module SortingHelper
options options
end end
def projects_sort_common_options_hash
{
sort_value_latest_activity => sort_title_latest_activity,
sort_value_recently_created => sort_title_created_date,
sort_value_name => sort_title_name,
sort_value_stars_desc => sort_title_stars
}
end
def projects_sort_option_titles
{
sort_value_latest_activity => sort_title_latest_activity,
sort_value_recently_created => sort_title_created_date,
sort_value_name => sort_title_name,
sort_value_stars_desc => sort_title_stars,
sort_value_oldest_activity => sort_title_latest_activity,
sort_value_oldest_created => sort_title_created_date,
sort_value_name_desc => sort_title_name,
sort_value_stars_asc => sort_title_stars
}
end
def projects_reverse_sort_options_hash
{
sort_value_latest_activity => sort_value_oldest_activity,
sort_value_recently_created => sort_value_oldest_created,
sort_value_name => sort_value_name_desc,
sort_value_stars_desc => sort_value_stars_asc,
sort_value_oldest_activity => sort_value_latest_activity,
sort_value_oldest_created => sort_value_recently_created,
sort_value_name_desc => sort_value_name,
sort_value_stars_asc => sort_value_stars_desc
}
end
def groups_sort_options_hash def groups_sort_options_hash
{ {
sort_value_name => sort_title_name, sort_value_name => sort_title_name,
...@@ -59,7 +101,7 @@ module SortingHelper ...@@ -59,7 +101,7 @@ module SortingHelper
def subgroups_sort_options_hash def subgroups_sort_options_hash
groups_sort_options_hash.merge( groups_sort_options_hash.merge(
sort_value_most_stars => sort_title_most_stars sort_value_stars_desc => sort_title_most_stars
) )
end end
...@@ -176,6 +218,8 @@ module SortingHelper ...@@ -176,6 +218,8 @@ module SortingHelper
end end
end end
# TODO: dedupicate issuable and project sort direction
# https://gitlab.com/gitlab-org/gitlab-ce/issues/60798
def issuable_sort_direction_button(sort_value) def issuable_sort_direction_button(sort_value)
link_class = 'btn btn-default has-tooltip reverse-sort-btn qa-reverse-sort' link_class = 'btn btn-default has-tooltip reverse-sort-btn qa-reverse-sort'
reverse_sort = issuable_reverse_sort_order_hash[sort_value] reverse_sort = issuable_reverse_sort_order_hash[sort_value]
...@@ -187,7 +231,23 @@ module SortingHelper ...@@ -187,7 +231,23 @@ module SortingHelper
link_class += ' disabled' link_class += ' disabled'
end end
link_to(reverse_url, type: 'button', class: link_class, title: 'Sort direction') do link_to(reverse_url, type: 'button', class: link_class, title: s_('SortOptions|Sort direction')) do
sprite_icon("sort-#{issuable_sort_icon_suffix(sort_value)}", size: 16)
end
end
def project_sort_direction_button(sort_value)
link_class = 'btn btn-default has-tooltip reverse-sort-btn qa-reverse-sort'
reverse_sort = projects_reverse_sort_options_hash[sort_value]
if reverse_sort
reverse_url = filter_projects_path(sort: reverse_sort)
else
reverse_url = '#'
link_class += ' disabled'
end
link_to(reverse_url, type: 'button', class: link_class, title: s_('SortOptions|Sort direction')) do
sprite_icon("sort-#{issuable_sort_icon_suffix(sort_value)}", size: 16) sprite_icon("sort-#{issuable_sort_icon_suffix(sort_value)}", size: 16)
end end
end end
...@@ -325,6 +385,10 @@ module SortingHelper ...@@ -325,6 +385,10 @@ module SortingHelper
s_('SortOptions|Most stars') s_('SortOptions|Most stars')
end end
def sort_title_stars
s_('SortOptions|Stars')
end
def sort_title_oldest_last_activity def sort_title_oldest_last_activity
s_('SortOptions|Oldest last activity') s_('SortOptions|Oldest last activity')
end end
...@@ -466,10 +530,14 @@ module SortingHelper ...@@ -466,10 +530,14 @@ module SortingHelper
'contacted_asc' 'contacted_asc'
end end
def sort_value_most_stars def sort_value_stars_desc
'stars_desc' 'stars_desc'
end end
def sort_value_stars_asc
'stars_asc'
end
def sort_value_oldest_last_activity def sort_value_oldest_last_activity
'last_activity_on_asc' 'last_activity_on_asc'
end end
......
...@@ -357,7 +357,8 @@ class Project < ApplicationRecord ...@@ -357,7 +357,8 @@ class Project < ApplicationRecord
# last_activity_at is throttled every minute, but last_repository_updated_at is updated with every push # last_activity_at is throttled every minute, but last_repository_updated_at is updated with every push
scope :sorted_by_activity, -> { reorder("GREATEST(COALESCE(last_activity_at, '1970-01-01'), COALESCE(last_repository_updated_at, '1970-01-01')) DESC") } scope :sorted_by_activity, -> { reorder("GREATEST(COALESCE(last_activity_at, '1970-01-01'), COALESCE(last_repository_updated_at, '1970-01-01')) DESC") }
scope :sorted_by_stars, -> { reorder(star_count: :desc) } scope :sorted_by_stars_desc, -> { reorder(star_count: :desc) }
scope :sorted_by_stars_asc, -> { reorder(star_count: :asc) }
scope :in_namespace, ->(namespace_ids) { where(namespace_id: namespace_ids) } scope :in_namespace, ->(namespace_ids) { where(namespace_id: namespace_ids) }
scope :personal, ->(user) { where(namespace_id: user.namespace_id) } scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
...@@ -544,7 +545,9 @@ class Project < ApplicationRecord ...@@ -544,7 +545,9 @@ class Project < ApplicationRecord
when 'latest_activity_asc' when 'latest_activity_asc'
reorder(last_activity_at: :asc) reorder(last_activity_at: :asc)
when 'stars_desc' when 'stars_desc'
sorted_by_stars sorted_by_stars_desc
when 'stars_asc'
sorted_by_stars_asc
else else
order_by(method) order_by(method)
end end
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
.top-area.scrolling-tabs-container.inner-page-scroll-tabs .top-area.scrolling-tabs-container.inner-page-scroll-tabs
.prepend-top-default .prepend-top-default
.search-holder .search-holder
= render 'shared/projects/search_form', autofocus: true, icon: true = render 'shared/projects/search_form', autofocus: true, icon: true, admin_view: true
.dropdown .dropdown
- toggle_text = 'Namespace' - toggle_text = 'Namespace'
- if params[:namespace_id].present? - if params[:namespace_id].present?
......
- project_tab_filter = local_assigns.fetch(:project_tab_filter, "")
- feature_project_list_filter_bar = Feature.enabled?(:project_list_filter_bar)
= content_for :flash_message do = content_for :flash_message do
= render 'shared/project_limit' = render 'shared/project_limit'
...@@ -6,24 +9,27 @@ ...@@ -6,24 +9,27 @@
- if current_user.can_create_project? - if current_user.can_create_project?
.page-title-controls .page-title-controls
= link_to "New project", new_project_path, class: "btn btn-success" = link_to _("New project"), new_project_path, class: "btn btn-success"
.top-area.scrolling-tabs-container.inner-page-scroll-tabs .top-area.scrolling-tabs-container.inner-page-scroll-tabs
.fade-left= icon('angle-left') .fade-left= icon('angle-left')
.fade-right= icon('angle-right') .fade-right= icon('angle-right')
%ul.nav-links.scrolling-tabs.mobile-separator.nav.nav-tabs %ul.nav-links.scrolling-tabs.mobile-separator.nav.nav-tabs{ class: ('border-0' if feature_project_list_filter_bar) }
= nav_link(page: [dashboard_projects_path, root_path]) do = nav_link(page: [dashboard_projects_path, root_path]) do
= link_to dashboard_projects_path, class: 'shortcuts-activity', data: {placement: 'right'} do = link_to dashboard_projects_path, class: 'shortcuts-activity', data: {placement: 'right'} do
Your projects = _("Your projects")
%span.badge.badge-pill= limited_counter_with_delimiter(@total_user_projects_count) %span.badge.badge-pill= limited_counter_with_delimiter(@total_user_projects_count)
= nav_link(page: starred_dashboard_projects_path) do = nav_link(page: starred_dashboard_projects_path) do
= link_to starred_dashboard_projects_path, data: {placement: 'right'} do = link_to starred_dashboard_projects_path, data: {placement: 'right'} do
Starred projects = _("Starred projects")
%span.badge.badge-pill= limited_counter_with_delimiter(@total_starred_projects_count) %span.badge.badge-pill= limited_counter_with_delimiter(@total_starred_projects_count)
= nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path]) do = nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path]) do
= link_to explore_root_path, data: {placement: 'right'} do = link_to explore_root_path, data: {placement: 'right'} do
Explore projects = _("Explore projects")
- unless feature_project_list_filter_bar
.nav-controls .nav-controls
= render 'shared/projects/search_form' = render 'shared/projects/search_form'
= render 'shared/projects/dropdown' = render 'shared/projects/dropdown'
- if feature_project_list_filter_bar
.project-filters
= render 'shared/projects/search_bar', project_tab_filter: project_tab_filter
.nav-block - inactive_class = 'btn p-2'
%ul.nav-links.mobile-separator.nav.nav-tabs - active_class = 'btn p-2 active'
= nav_link(html_options: { class: ("active" unless params[:personal].present?) }) do - project_tab_filter = local_assigns.fetch(:project_tab_filter, "")
= link_to s_('DashboardProjects|All'), dashboard_projects_path - is_explore_trending = project_tab_filter == :explore_trending
= nav_link(html_options: { class: ("active" if params[:personal].present?) }) do - feature_project_list_filter_bar = Feature.enabled?(:project_list_filter_bar)
= link_to s_('DashboardProjects|Personal'), filter_projects_path(personal: true)
.nav-block{ class: ("w-100" if feature_project_list_filter_bar) }
- if feature_project_list_filter_bar
.btn-group.button-filter-group.d-flex.m-0.p-0
- if project_tab_filter == :explore || is_explore_trending
= 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
- else
= link_to s_('DashboardProjects|All'), dashboard_projects_path, class: params[:personal].present? ? inactive_class : active_class
= link_to s_('DashboardProjects|Personal'), filter_projects_path(personal: true), class: params[:personal].present? ? active_class : inactive_class
- else
%ul.nav-links.mobile-separator.nav.nav-tabs
= nav_link(html_options: { class: ("active" unless params[:personal].present?) }) do
= link_to s_('DashboardProjects|All'), dashboard_projects_path
= nav_link(html_options: { class: ("active" if params[:personal].present?) }) do
= link_to s_('DashboardProjects|Personal'), filter_projects_path(personal: true)
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
= render "projects/last_push" = render "projects/last_push"
- if show_projects?(@projects, params) - if show_projects?(@projects, params)
= render 'dashboard/projects_head' = render 'dashboard/projects_head'
= render 'nav' = render 'nav' unless Feature.enabled?(:project_list_filter_bar)
= render 'projects' = render 'projects'
- else - else
= render "zero_authorized_projects" = render "zero_authorized_projects"
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
%div{ class: container_class } %div{ class: container_class }
= render "projects/last_push" = render "projects/last_push"
= render 'dashboard/projects_head' = render 'dashboard/projects_head', project_tab_filter: :starred
- if params[:filter_projects] || any_projects?(@projects) - if params[:filter_projects] || any_projects?(@projects)
= render 'projects' = render 'projects'
......
- has_label = local_assigns.fetch(:has_label, false)
- feature_project_list_filter_bar = Feature.enabled?(:project_list_filter_bar)
- if current_user - if current_user
.dropdown .dropdown.js-project-filter-dropdown-wrap{ class: ('d-flex flex-grow-1 flex-shrink-1' if feature_project_list_filter_bar) }
%button.dropdown-menu-toggle{ href: '#', "data-toggle" => "dropdown", 'data-display' => 'static' } %button.dropdown-menu-toggle{ href: '#', "data-toggle" => "dropdown", 'data-display' => 'static' }
= icon('globe', class: 'mt-1') - unless has_label
%span.light.ml-3= _("Visibility:") = icon('globe', class: 'mt-1')
%span.light.ml-3= _("Visibility:")
- if params[:visibility_level].present? - if params[:visibility_level].present?
= visibility_level_label(params[:visibility_level].to_i) = visibility_level_label(params[:visibility_level].to_i)
- else - else
......
...@@ -5,9 +5,9 @@ ...@@ -5,9 +5,9 @@
= render_dashboard_gold_trial(current_user) = render_dashboard_gold_trial(current_user)
- if current_user - if current_user
= render 'dashboard/projects_head' = render 'dashboard/projects_head', project_tab_filter: :explore
- else - else
= render 'explore/head' = render 'explore/head'
= render 'explore/projects/nav' = render 'explore/projects/nav' unless Feature.enabled?(:project_list_filter_bar) && current_user
= render 'projects', projects: @projects = render 'projects', projects: @projects
...@@ -5,9 +5,9 @@ ...@@ -5,9 +5,9 @@
= render_dashboard_gold_trial(current_user) = render_dashboard_gold_trial(current_user)
- if current_user - if current_user
= render 'dashboard/projects_head' = render 'dashboard/projects_head', project_tab_filter: :starred
- else - else
= render 'explore/head' = render 'explore/head'
= render 'explore/projects/nav' = render 'explore/projects/nav' unless Feature.enabled?(:project_list_filter_bar) && current_user
= render 'projects', projects: @projects = render 'projects', projects: @projects
...@@ -5,9 +5,9 @@ ...@@ -5,9 +5,9 @@
= render_dashboard_gold_trial(current_user) = render_dashboard_gold_trial(current_user)
- if current_user - if current_user
= render 'dashboard/projects_head' = render 'dashboard/projects_head', project_tab_filter: :explore_trending
- else - else
= render 'explore/head' = render 'explore/head'
= render 'explore/projects/nav' = render 'explore/projects/nav' unless Feature.enabled?(:project_list_filter_bar) && current_user
= render 'projects', projects: @projects = render 'projects', projects: @projects
...@@ -24,10 +24,10 @@ ...@@ -24,10 +24,10 @@
%li.divider %li.divider
%li.js-filter-archived-projects %li.js-filter-archived-projects
= link_to filter_groups_path(archived: nil), class: ("is-active" unless params[:archived].present?) do = link_to filter_groups_path(archived: nil), class: ("is-active" unless params[:archived].present?) do
Hide archived projects = _("Hide archived projects")
%li.js-filter-archived-projects %li.js-filter-archived-projects
= link_to filter_groups_path(archived: true), class: ("is-active" if Gitlab::Utils.to_boolean(params[:archived])) do = link_to filter_groups_path(archived: true), class: ("is-active" if Gitlab::Utils.to_boolean(params[:archived])) do
Show archived projects = _("Show archived projects")
%li.js-filter-archived-projects %li.js-filter-archived-projects
= link_to filter_groups_path(archived: 'only'), class: ("is-active" if params[:archived] == 'only') do = link_to filter_groups_path(archived: 'only'), class: ("is-active" if params[:archived] == 'only') do
Show archived projects only = _("Show archived projects only")
- @sort ||= sort_value_latest_activity - @sort ||= sort_value_latest_activity
.dropdown.js-project-filter-dropdown-wrap .dropdown.js-project-filter-dropdown-wrap
- toggle_text = projects_sort_options_hash[@sort] = dropdown_toggle(projects_sort_options_hash[@sort], { toggle: 'dropdown', display: 'static' }, { id: 'sort-projects-dropdown' })
= dropdown_toggle(toggle_text, { toggle: 'dropdown', display: 'static' }, { id: 'sort-projects-dropdown' })
%ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable
%li.dropdown-header %li.dropdown-header
Sort by = _("Sort by")
- projects_sort_options_hash.each do |value, title| - projects_sort_options_hash.each do |value, title|
%li %li
= link_to filter_projects_path(sort: value), class: ("is-active" if @sort == value) do = link_to filter_projects_path(sort: value), class: ("is-active" if @sort == value) do
...@@ -13,29 +12,29 @@ ...@@ -13,29 +12,29 @@
%li.divider %li.divider
%li %li
= link_to filter_projects_path(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 = _("Hide archived projects")
%li %li
= link_to filter_projects_path(archived: true), class: ("is-active" if Gitlab::Utils.to_boolean(params[:archived])) do = link_to filter_projects_path(archived: true), class: ("is-active" if Gitlab::Utils.to_boolean(params[:archived])) do
Show archived projects = _("Show archived projects")
%li %li
= link_to filter_projects_path(archived: 'only'), class: ("is-active" if params[:archived] == 'only') do = link_to filter_projects_path(archived: 'only'), class: ("is-active" if params[:archived] == 'only') do
Show archived projects only = _("Show archived projects only")
- if current_user - if current_user
%li.divider %li.divider
%li %li
= link_to filter_projects_path(personal: nil), class: ("is-active" unless params[:personal].present?) do = link_to filter_projects_path(personal: nil), class: ("is-active" unless params[:personal].present?) do
Owned by anyone = _("Owned by anyone")
%li %li
= link_to filter_projects_path(personal: true), class: ("is-active" if params[:personal].present?) do = link_to filter_projects_path(personal: true), class: ("is-active" if params[:personal].present?) do
Owned by me = _("Owned by me")
- if @group && @group.shared_projects.present? - if @group && @group.shared_projects.present?
%li.divider %li.divider
%li %li
= link_to filter_projects_path(shared: nil), class: ("is-active" unless params[:shared].present?) do = link_to filter_projects_path(shared: nil), class: ("is-active" unless params[:shared].present?) do
All projects = _("All projects")
%li %li
= link_to filter_projects_path(shared: 0), class: ("is-active" if params[:shared] == '0') do = link_to filter_projects_path(shared: 0), class: ("is-active" if params[:shared] == '0') do
Hide shared projects = _("Hide shared projects")
%li %li
= link_to filter_projects_path(shared: 1), class: ("is-active" if params[:shared] == '1') do = link_to filter_projects_path(shared: 1), class: ("is-active" if params[:shared] == '1') do
Hide group projects = _("Hide group projects")
- @sort ||= sort_value_latest_activity
- project_tab_filter = local_assigns.fetch(:project_tab_filter, "")
- flex_grow_and_shrink_xs = 'd-flex flex-xs-grow-1 flex-xs-shrink-1 flex-grow-0 flex-shrink-0'
.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 project_tab_filter == :starred
.filtered-search-nav.mb-2.mb-lg-0{ class: flex_grow_and_shrink_xs }
= render 'dashboard/projects/nav', project_tab_filter: project_tab_filter
.filtered-search.d-flex.flex-grow-1.flex-shrink-1.w-100.mb-2.mb-lg-0.ml-0{ class: project_tab_filter == :starred ? "extended-filtered-search-box mb-2 mb-lg-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...")
%button.btn.btn-secondary{ type: 'submit', form: 'project-filter-form' }
= sprite_icon('search', size: 16, css_class: 'search-icon ')
.filtered-search-dropdown.flex-row.align-items-center.mb-2.m-sm-0#filtered-search-visibility-dropdown{ class: flex_grow_and_shrink_xs }
.filtered-search-dropdown-label.p-0.pl-sm-3.font-weight-bold
%span
= _("Visibility")
= render 'explore/projects/filter', has_label: true
.filtered-search-dropdown.flex-row.align-items-center.m-sm-0#filtered-search-sorting-dropdown{ class: flex_grow_and_shrink_xs }
.filtered-search-dropdown-label.p-0.pl-sm-3.font-weight-bold
%span
= _("Sort by")
= render 'shared/projects/sort_dropdown'
- 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| = form_tag filter_projects_path, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
= search_field_tag :name, params[:name], = search_field_tag :name, params[:name],
placeholder: 'Filter by name...', placeholder: placeholder,
class: 'project-filter-form-field form-control input-short js-projects-list-filter', class: "project-filter-form-field form-control #{form_field_classes}",
spellcheck: false, spellcheck: false,
id: 'project-filter-form-field', id: 'project-filter-form-field',
tabindex: "2", tabindex: "2",
......
- @sort ||= sort_value_latest_activity
- toggle_text = projects_sort_option_titles[@sort]
.btn-group.w-100{ role: "group" }
.btn-group.w-100.dropdown.js-project-filter-dropdown-wrap{ role: "group" }
%button#sort-projects-dropdown.btn.btn-default.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown', display: 'static' } }
= toggle_text
= icon('chevron-down')
%ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable
%li.dropdown-header
= _("Sort by")
- projects_sort_options_hash.each do |value, title|
%li
= link_to title, filter_projects_path(sort: value), class: ("is-active" if toggle_text == title)
%li.divider
%li
= link_to filter_projects_path(archived: nil), class: ("is-active" unless params[:archived].present?) do
= _("Hide archived projects")
%li
= link_to filter_projects_path(archived: true), class: ("is-active" if Gitlab::Utils.to_boolean(params[:archived])) do
= _("Show archived projects")
%li
= link_to filter_projects_path(archived: 'only'), class: ("is-active" if params[:archived] == 'only') do
= _("Show archived projects only")
- if current_user && @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")
%li
= link_to filter_projects_path(shared: 0), class: ("is-active" if params[:shared] == '0') do
= _("Hide shared projects")
%li
= link_to filter_projects_path(shared: 1), class: ("is-active" if params[:shared] == '1') do
= _("Hide group projects")
= project_sort_direction_button(@sort)
...@@ -754,6 +754,9 @@ msgstr "" ...@@ -754,6 +754,9 @@ msgstr ""
msgid "All merge conflicts were resolved. The merge request can now be merged." msgid "All merge conflicts were resolved. The merge request can now be merged."
msgstr "" msgstr ""
msgid "All projects"
msgstr ""
msgid "All todos were marked as done." msgid "All todos were marked as done."
msgstr "" msgstr ""
...@@ -3072,6 +3075,9 @@ msgstr "" ...@@ -3072,6 +3075,9 @@ msgstr ""
msgid "DashboardProjects|Personal" msgid "DashboardProjects|Personal"
msgstr "" msgstr ""
msgid "DashboardProjects|Trending"
msgstr ""
msgid "Data is still calculating..." msgid "Data is still calculating..."
msgstr "" msgstr ""
...@@ -4756,9 +4762,15 @@ msgstr "" ...@@ -4756,9 +4762,15 @@ msgstr ""
msgid "Help page text and support page url." msgid "Help page text and support page url."
msgstr "" msgstr ""
msgid "Hide archived projects"
msgstr ""
msgid "Hide file browser" msgid "Hide file browser"
msgstr "" msgstr ""
msgid "Hide group projects"
msgstr ""
msgid "Hide host keys manual input" msgid "Hide host keys manual input"
msgstr "" msgstr ""
...@@ -4768,6 +4780,9 @@ msgstr "" ...@@ -4768,6 +4780,9 @@ msgstr ""
msgid "Hide payload" msgid "Hide payload"
msgstr "" msgstr ""
msgid "Hide shared projects"
msgstr ""
msgid "Hide value" msgid "Hide value"
msgid_plural "Hide values" msgid_plural "Hide values"
msgstr[0] "" msgstr[0] ""
...@@ -6527,6 +6542,12 @@ msgstr "" ...@@ -6527,6 +6542,12 @@ msgstr ""
msgid "Overview" msgid "Overview"
msgstr "" msgstr ""
msgid "Owned by anyone"
msgstr ""
msgid "Owned by me"
msgstr ""
msgid "Owner" msgid "Owner"
msgstr "" msgstr ""
...@@ -8229,6 +8250,9 @@ msgstr "" ...@@ -8229,6 +8250,9 @@ msgstr ""
msgid "Search projects" msgid "Search projects"
msgstr "" msgstr ""
msgid "Search projects..."
msgstr ""
msgid "Search users" msgid "Search users"
msgstr "" msgstr ""
...@@ -8526,6 +8550,12 @@ msgstr "" ...@@ -8526,6 +8550,12 @@ msgstr ""
msgid "Show all activity" msgid "Show all activity"
msgstr "" msgstr ""
msgid "Show archived projects"
msgstr ""
msgid "Show archived projects only"
msgstr ""
msgid "Show command" msgid "Show command"
msgstr "" msgstr ""
...@@ -8792,6 +8822,12 @@ msgstr "" ...@@ -8792,6 +8822,12 @@ msgstr ""
msgid "SortOptions|Recent sign in" msgid "SortOptions|Recent sign in"
msgstr "" msgstr ""
msgid "SortOptions|Sort direction"
msgstr ""
msgid "SortOptions|Stars"
msgstr ""
msgid "SortOptions|Start later" msgid "SortOptions|Start later"
msgstr "" msgstr ""
...@@ -10550,6 +10586,9 @@ msgstr "" ...@@ -10550,6 +10586,9 @@ msgstr ""
msgid "Viewing commit" msgid "Viewing commit"
msgstr "" msgstr ""
msgid "Visibility"
msgstr ""
msgid "Visibility and access controls" msgid "Visibility and access controls"
msgstr "" msgstr ""
......
...@@ -112,6 +112,14 @@ describe 'Dashboard Projects' do ...@@ -112,6 +112,14 @@ describe 'Dashboard Projects' do
expect(first('.project-row')).to have_content(project_with_most_stars.title) expect(first('.project-row')).to have_content(project_with_most_stars.title)
end 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 end
context 'when on Starred projects tab', :js do context 'when on Starred projects tab', :js do
...@@ -134,6 +142,12 @@ describe 'Dashboard Projects' do ...@@ -134,6 +142,12 @@ 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(1) .badge-pill')).to have_content(1)
expect(find('.nav-links li:nth-child(2) .badge-pill')).to have_content(1) expect(find('.nav-links li:nth-child(2) .badge-pill')).to have_content(1)
end 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 end
describe 'with a pipeline', :clean_gitlab_redis_shared_state do describe 'with a pipeline', :clean_gitlab_redis_shared_state do
......
...@@ -14,6 +14,7 @@ describe 'Dashboard > User filters projects' do ...@@ -14,6 +14,7 @@ describe 'Dashboard > User filters projects' do
describe 'filtering personal projects' do describe 'filtering personal projects' do
before do before do
stub_feature_flags(project_list_filter_bar: false)
project2.add_developer(user) project2.add_developer(user)
visit dashboard_projects_path visit dashboard_projects_path
...@@ -30,6 +31,7 @@ describe 'Dashboard > User filters projects' do ...@@ -30,6 +31,7 @@ describe 'Dashboard > User filters projects' do
describe 'filtering starred projects', :js do describe 'filtering starred projects', :js do
before do before do
stub_feature_flags(project_list_filter_bar: false)
user.toggle_star(project) user.toggle_star(project)
visit dashboard_projects_path visit dashboard_projects_path
...@@ -42,4 +44,219 @@ describe 'Dashboard > User filters projects' do ...@@ -42,4 +44,219 @@ describe 'Dashboard > User filters projects' do
expect(page).not_to have_content('You don\'t have starred projects yet') expect(page).not_to have_content('You don\'t have starred projects yet')
end end
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 'autocompletes searches upon typing', :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)
list = page.all('.projects-list .project-name').map(&:text)
expect(list).to match(sorted_projects)
end
describe 'Search' do
it 'executes when the search button is clicked' 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
private_project = create(:project, :private, name: 'Private project', namespace: user.namespace)
internal_project = create(:project, :internal, name: 'Internal project', namespace: user.namespace)
private_project.add_maintainer(user)
internal_project.add_maintainer(user)
end
it 'filters private projects only' 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 'filters internal projects only' 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 'filters 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|
project = create(:project, name: item[:name], namespace: user.namespace, created_at: item[:created_at])
project.add_developer(user)
end
user.toggle_star(project)
user.toggle_star(project2)
user2.toggle_star(project2)
end
it 'includes 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 'has 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 'defaults 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 'sorts 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']
click_sort_direction
expect_to_see_projects(desc)
click_sort_direction
expect_to_see_projects(asc)
end
end
context 'Sorting by Last updated' do
it 'sorts 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"]
click_sort_direction
expect_to_see_projects(desc)
click_sort_direction
expect_to_see_projects(asc)
end
end
context 'Sorting by Created date' do
it 'sorts 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"]
click_sort_direction
expect_to_see_projects(desc)
click_sort_direction
expect_to_see_projects(asc)
end
end
context 'Sorting by Stars' do
it 'sorts 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"]
click_sort_direction
expect_to_see_projects(desc)
click_sort_direction
expect_to_see_projects(asc)
end
end
end
end
end end
...@@ -445,6 +445,10 @@ describe ProjectsHelper do ...@@ -445,6 +445,10 @@ describe ProjectsHelper do
Project.all Project.all
end end
before do
stub_feature_flags(project_list_filter_bar: false)
end
it 'returns true when there are projects' do it 'returns true when there are projects' do
expect(helper.show_projects?(projects, {})).to eq(true) expect(helper.show_projects?(projects, {})).to eq(true)
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