Commit 59976090 authored by Camil Staps's avatar Camil Staps

Add /starrers view for projects

parent 5b20df0a
...@@ -9,10 +9,6 @@ ...@@ -9,10 +9,6 @@
} }
} }
.member-sort-dropdown {
margin-left: $gl-padding-8;
}
.member { .member {
&.is-overridden { &.is-overridden {
.btn-ldap-override { .btn-ldap-override {
...@@ -62,36 +58,9 @@ ...@@ -62,36 +58,9 @@
} }
} }
.member-search-form { .member-access-text {
position: relative; margin-left: auto;
line-height: 43px;
@include media-breakpoint-up(sm) {
float: right;
}
.dropdown {
width: 100%;
margin-top: 5px;
.dropdown-menu-toggle {
vertical-align: middle;
width: 100%;
}
@include media-breakpoint-up(sm) {
margin-top: 0;
width: 155px;
}
}
.form-control {
width: 100%;
padding-right: 35px;
@include media-breakpoint-up(sm) {
width: 250px;
}
}
} }
.member-search-btn { .member-search-btn {
...@@ -177,7 +146,7 @@ ...@@ -177,7 +146,7 @@
padding-bottom: 1px; padding-bottom: 1px;
} }
.flex-project-members-form { .flex-users-form {
flex-wrap: nowrap; flex-wrap: nowrap;
white-space: nowrap; white-space: nowrap;
margin-left: auto; margin-left: auto;
......
.user-sort-dropdown {
margin-left: $gl-padding-8;
}
.user-search-form {
position: relative;
@include media-breakpoint-up(sm) {
float: right;
}
.dropdown {
width: 100%;
margin-top: 5px;
.dropdown-menu-toggle {
vertical-align: middle;
width: 100%;
}
@include media-breakpoint-up(sm) {
margin-top: 0;
width: 155px;
}
}
.form-control {
width: 100%;
padding-right: 35px;
@include media-breakpoint-up(sm) {
width: 250px;
}
}
}
.user-search-btn {
position: absolute;
right: 4px;
top: 0;
height: 35px;
padding-left: 10px;
padding-right: 10px;
color: $gray-darkest;
background: transparent;
border: 0;
outline: 0;
}
.flex-users-panel {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
@include media-breakpoint-down(sm) {
display: block;
.flex-project-title {
vertical-align: top;
display: inline-block;
max-width: 90%;
}
}
.flex-project-title {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.badge.badge-pill {
height: 17px;
line-height: 16px;
margin-right: 5px;
padding-top: 1px;
padding-bottom: 1px;
}
.flex-users-form {
flex-wrap: nowrap;
white-space: nowrap;
margin-left: auto;
}
}
.content-list.members-list li {
display: flex;
justify-content: space-between;
.list-item-name {
float: none;
display: flex;
flex: 1;
}
}
# frozen_string_literal: true
class Projects::StarrersController < Projects::ApplicationController
include SortingHelper
#
# Authorize
before_action :require_non_empty_project
def index
@sort = params[:sort].presence || sort_value_name
params[:has_starred] = @project
@starrers = UsersFinder.new(current_user, params).execute
@starrers = @starrers.sort_by_attribute(@sort)
end
end
...@@ -37,6 +37,7 @@ class UsersFinder ...@@ -37,6 +37,7 @@ class UsersFinder
users = by_2fa(users) users = by_2fa(users)
users = by_created_at(users) users = by_created_at(users)
users = by_custom_attributes(users) users = by_custom_attributes(users)
users = by_has_starred(users)
users users
end end
...@@ -94,4 +95,8 @@ class UsersFinder ...@@ -94,4 +95,8 @@ class UsersFinder
users users
end end
end end
def by_has_starred(items)
params[:has_starred].present? ? items.has_starred(params[:has_starred]) : items
end
end end
...@@ -108,4 +108,9 @@ module UsersHelper ...@@ -108,4 +108,9 @@ module UsersHelper
items items
end end
def filter_user_path(options = {})
options = params.slice(:sort).merge(options).permit!
"#{request.path}?#{options.to_param}"
end
end end
...@@ -282,6 +282,7 @@ class User < ApplicationRecord ...@@ -282,6 +282,7 @@ class User < ApplicationRecord
scope :for_todos, -> (todos) { where(id: todos.select(:user_id)) } scope :for_todos, -> (todos) { where(id: todos.select(:user_id)) }
scope :with_emails, -> { preload(:emails) } scope :with_emails, -> { preload(:emails) }
scope :with_dashboard, -> (dashboard) { where(dashboard: dashboard) } scope :with_dashboard, -> (dashboard) { where(dashboard: dashboard) }
scope :has_starred, ->(project) { joins(:users_star_projects).where('users_star_projects.project_id': project.id) }
# Limits the users to those that have TODOs, optionally in the given state. # Limits the users to those that have TODOs, optionally in the given state.
# #
......
...@@ -25,11 +25,11 @@ ...@@ -25,11 +25,11 @@
Members with access to Members with access to
%strong= @group.name %strong= @group.name
%span.badge.badge-pill= @members.total_count %span.badge.badge-pill= @members.total_count
= form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form flex-project-members-form' do = form_tag group_group_members_path(@group), method: :get, class: 'form-inline user-search-form flex-users-form' do
.form-group .form-group
.position-relative.append-right-8 .position-relative.append-right-8
= search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false } = search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false }
%button.member-search-btn{ type: "submit", "aria-label" => "Submit search" } %button.user-search-btn{ type: "submit", "aria-label" => "Submit search" }
= icon("search") = icon("search")
- if can_manage_members - if can_manage_members
= render 'shared/members/filter_2fa_dropdown' = render 'shared/members/filter_2fa_dropdown'
......
...@@ -8,7 +8,8 @@ ...@@ -8,7 +8,8 @@
= sprite_icon('star-o', { css_class: 'icon' }) = sprite_icon('star-o', { css_class: 'icon' })
%span= s_('ProjectOverview|Star') %span= s_('ProjectOverview|Star')
%span.star-count.count-badge-count.d-flex.align-items-center %span.star-count.count-badge-count.d-flex.align-items-center
= @project.star_count = link_to project_starrers_path(@project), title: n_(s_('ProjectOverview|Starrer'), s_('ProjectOverview|Starrers'), @project.star_count), class: 'count' do
= @project.star_count
- else - else
.count-badge.d-inline-flex.align-item-stretch.append-right-8 .count-badge.d-inline-flex.align-item-stretch.append-right-8
...@@ -16,4 +17,5 @@ ...@@ -16,4 +17,5 @@
= sprite_icon('star-o', { css_class: 'icon' }) = sprite_icon('star-o', { css_class: 'icon' })
%span= s_('ProjectOverview|Star') %span= s_('ProjectOverview|Star')
%span.star-count.count-badge-count.d-flex.align-items-center %span.star-count.count-badge-count.d-flex.align-items-center
= @project.star_count = link_to project_starrers_path(@project), title: n_(s_('ProjectOverview|Starrer'), s_('ProjectOverview|Starrers'), @project.star_count), class: 'count' do
= @project.star_count
...@@ -6,11 +6,11 @@ ...@@ -6,11 +6,11 @@
%span.flex-project-title %span.flex-project-title
= _("Members of <strong>%{project_name}</strong>").html_safe % { project_name: sanitize(project.name, tags: []) } = _("Members of <strong>%{project_name}</strong>").html_safe % { project_name: sanitize(project.name, tags: []) }
%span.badge.badge-pill= members.total_count %span.badge.badge-pill= members.total_count
= form_tag project_project_members_path(project), method: :get, class: 'form-inline member-search-form flex-project-members-form' do = form_tag project_project_members_path(project), method: :get, class: 'form-inline user-search-form flex-users-form' do
.form-group .form-group
.position-relative .position-relative
= search_field_tag :search, params[:search], { placeholder: _('Find existing members by name'), class: 'form-control', spellcheck: false } = search_field_tag :search, params[:search], { placeholder: _('Find existing members by name'), class: 'form-control', spellcheck: false }
%button.member-search-btn{ type: "submit", "aria-label" => _("Submit search") } %button.user-search-btn{ type: "submit", "aria-label" => _("Submit search") }
= icon("search") = icon("search")
= render 'shared/members/sort_dropdown' = render 'shared/members/sort_dropdown'
%ul.content-list.members-list.qa-members-list %ul.content-list.members-list.qa-members-list
......
- page_title _("Starrers")
.top-area.adjust
.nav-text
%span.flex-project-title
= _("Starrers of <strong>%{project_name}</strong>").html_safe % { project_name: sanitize_project_name(@project.name) }
%span.badge.badge-pill= @starrers.size
.nav-controls
= form_tag request.original_url, method: :get, class: 'form-inline user-search-form flex-users-form' do
.form-group
.position-relative
= search_field_tag :search, params[:search], { placeholder: _('Find starrers by name'), class: 'form-control', spellcheck: false }
%button.user-search-btn{ type: "submit", "aria-label" => _("Submit search") }
= icon("search")
.dropdown.inline.user-sort-dropdown
= dropdown_toggle(users_sort_options_hash[@sort], { toggle: 'dropdown' })
%ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable
%li.dropdown-header
= _("Sort by")
- users_sort_options_hash.each do |value, title|
%li
= link_to filter_user_path(sort: value), class: ("is-active" if @sort == value) do
= title
%ul.content-list.users-list.qa-users-list
= render partial: 'shared/users/user', collection: @starrers, as: :user
.dropdown.inline.member-sort-dropdown .dropdown.inline.user-sort-dropdown
= dropdown_toggle(member_sort_options_hash[@sort], { toggle: 'dropdown' }) = dropdown_toggle(member_sort_options_hash[@sort], { toggle: '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
......
- user = local_assigns.fetch(:user)
%li.member{ class: dom_class(user), id: dom_id(user) }
%span.list-item-name
= image_tag avatar_icon_for_user(user, 40), class: "avatar s40", alt: ''
.user-info
= link_to user.name, user_path(user), class: 'member js-user-link', data: { user_id: user.id }
%br
%span.cgray= user.to_reference
- if user == current_user
%span.badge.badge-success.prepend-left-5= _("It's you")
...@@ -170,7 +170,9 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do ...@@ -170,7 +170,9 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get :recent get :recent
end end
end end
resources :releases, only: [:index] resources :releases, only: [:index]
resources :starrers, only: [:index]
resources :forks, only: [:index, :new, :create] resources :forks, only: [:index, :new, :create]
resources :group_links, only: [:index, :create, :update, :destroy], constraints: { id: /\d+/ } resources :group_links, only: [:index, :create, :update, :destroy], constraints: { id: /\d+/ }
......
...@@ -19,9 +19,9 @@ describe 'Search group member' do ...@@ -19,9 +19,9 @@ describe 'Search group member' do
end end
it 'renders member users' do it 'renders member users' do
page.within '.member-search-form' do page.within '.user-search-form' do
fill_in 'search', with: member.name fill_in 'search', with: member.name
find('.member-search-btn').click find('.user-search-btn').click
end end
group_members_list = find(".card .content-list") group_members_list = find(".card .content-list")
......
...@@ -19,7 +19,7 @@ describe 'Groups > Members > Sort members' do ...@@ -19,7 +19,7 @@ describe 'Groups > Members > Sort members' do
expect(first_member).to include(owner.name) expect(first_member).to include(owner.name)
expect(second_member).to include(developer.name) expect(second_member).to include(developer.name)
expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending') expect(page).to have_css('.user-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending')
end end
it 'sorts by access level ascending' do it 'sorts by access level ascending' do
...@@ -27,7 +27,7 @@ describe 'Groups > Members > Sort members' do ...@@ -27,7 +27,7 @@ describe 'Groups > Members > Sort members' do
expect(first_member).to include(developer.name) expect(first_member).to include(developer.name)
expect(second_member).to include(owner.name) expect(second_member).to include(owner.name)
expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Access level, ascending') expect(page).to have_css('.user-sort-dropdown .dropdown-toggle-text', text: 'Access level, ascending')
end end
it 'sorts by access level descending' do it 'sorts by access level descending' do
...@@ -35,7 +35,7 @@ describe 'Groups > Members > Sort members' do ...@@ -35,7 +35,7 @@ describe 'Groups > Members > Sort members' do
expect(first_member).to include(owner.name) expect(first_member).to include(owner.name)
expect(second_member).to include(developer.name) expect(second_member).to include(developer.name)
expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Access level, descending') expect(page).to have_css('.user-sort-dropdown .dropdown-toggle-text', text: 'Access level, descending')
end end
it 'sorts by last joined' do it 'sorts by last joined' do
...@@ -43,7 +43,7 @@ describe 'Groups > Members > Sort members' do ...@@ -43,7 +43,7 @@ describe 'Groups > Members > Sort members' do
expect(first_member).to include(developer.name) expect(first_member).to include(developer.name)
expect(second_member).to include(owner.name) expect(second_member).to include(owner.name)
expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Last joined') expect(page).to have_css('.user-sort-dropdown .dropdown-toggle-text', text: 'Last joined')
end end
it 'sorts by oldest joined' do it 'sorts by oldest joined' do
...@@ -51,7 +51,7 @@ describe 'Groups > Members > Sort members' do ...@@ -51,7 +51,7 @@ describe 'Groups > Members > Sort members' do
expect(first_member).to include(owner.name) expect(first_member).to include(owner.name)
expect(second_member).to include(developer.name) expect(second_member).to include(developer.name)
expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Oldest joined') expect(page).to have_css('.user-sort-dropdown .dropdown-toggle-text', text: 'Oldest joined')
end end
it 'sorts by name ascending' do it 'sorts by name ascending' do
...@@ -59,7 +59,7 @@ describe 'Groups > Members > Sort members' do ...@@ -59,7 +59,7 @@ describe 'Groups > Members > Sort members' do
expect(first_member).to include(owner.name) expect(first_member).to include(owner.name)
expect(second_member).to include(developer.name) expect(second_member).to include(developer.name)
expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending') expect(page).to have_css('.user-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending')
end end
it 'sorts by name descending' do it 'sorts by name descending' do
...@@ -67,7 +67,7 @@ describe 'Groups > Members > Sort members' do ...@@ -67,7 +67,7 @@ describe 'Groups > Members > Sort members' do
expect(first_member).to include(developer.name) expect(first_member).to include(developer.name)
expect(second_member).to include(owner.name) expect(second_member).to include(owner.name)
expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, descending') expect(page).to have_css('.user-sort-dropdown .dropdown-toggle-text', text: 'Name, descending')
end end
it 'sorts by recent sign in', :clean_gitlab_redis_shared_state do it 'sorts by recent sign in', :clean_gitlab_redis_shared_state do
...@@ -75,7 +75,7 @@ describe 'Groups > Members > Sort members' do ...@@ -75,7 +75,7 @@ describe 'Groups > Members > Sort members' do
expect(first_member).to include(owner.name) expect(first_member).to include(owner.name)
expect(second_member).to include(developer.name) expect(second_member).to include(developer.name)
expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Recent sign in') expect(page).to have_css('.user-sort-dropdown .dropdown-toggle-text', text: 'Recent sign in')
end end
it 'sorts by oldest sign in', :clean_gitlab_redis_shared_state do it 'sorts by oldest sign in', :clean_gitlab_redis_shared_state do
...@@ -83,7 +83,7 @@ describe 'Groups > Members > Sort members' do ...@@ -83,7 +83,7 @@ describe 'Groups > Members > Sort members' do
expect(first_member).to include(developer.name) expect(first_member).to include(developer.name)
expect(second_member).to include(owner.name) expect(second_member).to include(owner.name)
expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Oldest sign in') expect(page).to have_css('.user-sort-dropdown .dropdown-toggle-text', text: 'Oldest sign in')
end end
def visit_members_list(sort:) def visit_members_list(sort:)
......
...@@ -52,18 +52,18 @@ describe 'Projects > Members > Groups with access list', :js do ...@@ -52,18 +52,18 @@ describe 'Projects > Members > Groups with access list', :js do
context 'search in existing members (yes, this filters the groups list as well)' do context 'search in existing members (yes, this filters the groups list as well)' do
it 'finds no results' do it 'finds no results' do
page.within '.member-search-form' do page.within '.user-search-form' do
fill_in 'search', with: 'testing 123' fill_in 'search', with: 'testing 123'
find('.member-search-btn').click find('.user-search-btn').click
end end
expect(page).not_to have_selector('.group_member') expect(page).not_to have_selector('.group_member')
end end
it 'finds results' do it 'finds results' do
page.within '.member-search-form' do page.within '.user-search-form' do
fill_in 'search', with: group.name fill_in 'search', with: group.name
find('.member-search-btn').click find('.user-search-btn').click
end end
expect(page).to have_selector('.group_member', count: 1) expect(page).to have_selector('.group_member', count: 1)
......
...@@ -18,7 +18,7 @@ describe 'Projects > Members > Sorting' do ...@@ -18,7 +18,7 @@ describe 'Projects > Members > Sorting' do
expect(first_member).to include(maintainer.name) expect(first_member).to include(maintainer.name)
expect(second_member).to include(developer.name) expect(second_member).to include(developer.name)
expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending') expect(page).to have_css('.user-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending')
end end
it 'sorts by access level ascending' do it 'sorts by access level ascending' do
...@@ -26,7 +26,7 @@ describe 'Projects > Members > Sorting' do ...@@ -26,7 +26,7 @@ describe 'Projects > Members > Sorting' do
expect(first_member).to include(developer.name) expect(first_member).to include(developer.name)
expect(second_member).to include(maintainer.name) expect(second_member).to include(maintainer.name)
expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Access level, ascending') expect(page).to have_css('.user-sort-dropdown .dropdown-toggle-text', text: 'Access level, ascending')
end end
it 'sorts by access level descending' do it 'sorts by access level descending' do
...@@ -34,7 +34,7 @@ describe 'Projects > Members > Sorting' do ...@@ -34,7 +34,7 @@ describe 'Projects > Members > Sorting' do
expect(first_member).to include(maintainer.name) expect(first_member).to include(maintainer.name)
expect(second_member).to include(developer.name) expect(second_member).to include(developer.name)
expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Access level, descending') expect(page).to have_css('.user-sort-dropdown .dropdown-toggle-text', text: 'Access level, descending')
end end
it 'sorts by last joined' do it 'sorts by last joined' do
...@@ -42,7 +42,7 @@ describe 'Projects > Members > Sorting' do ...@@ -42,7 +42,7 @@ describe 'Projects > Members > Sorting' do
expect(first_member).to include(maintainer.name) expect(first_member).to include(maintainer.name)
expect(second_member).to include(developer.name) expect(second_member).to include(developer.name)
expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Last joined') expect(page).to have_css('.user-sort-dropdown .dropdown-toggle-text', text: 'Last joined')
end end
it 'sorts by oldest joined' do it 'sorts by oldest joined' do
...@@ -50,7 +50,7 @@ describe 'Projects > Members > Sorting' do ...@@ -50,7 +50,7 @@ describe 'Projects > Members > Sorting' do
expect(first_member).to include(developer.name) expect(first_member).to include(developer.name)
expect(second_member).to include(maintainer.name) expect(second_member).to include(maintainer.name)
expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Oldest joined') expect(page).to have_css('.user-sort-dropdown .dropdown-toggle-text', text: 'Oldest joined')
end end
it 'sorts by name ascending' do it 'sorts by name ascending' do
...@@ -58,7 +58,7 @@ describe 'Projects > Members > Sorting' do ...@@ -58,7 +58,7 @@ describe 'Projects > Members > Sorting' do
expect(first_member).to include(maintainer.name) expect(first_member).to include(maintainer.name)
expect(second_member).to include(developer.name) expect(second_member).to include(developer.name)
expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending') expect(page).to have_css('.user-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending')
end end
it 'sorts by name descending' do it 'sorts by name descending' do
...@@ -66,7 +66,7 @@ describe 'Projects > Members > Sorting' do ...@@ -66,7 +66,7 @@ describe 'Projects > Members > Sorting' do
expect(first_member).to include(developer.name) expect(first_member).to include(developer.name)
expect(second_member).to include(maintainer.name) expect(second_member).to include(maintainer.name)
expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, descending') expect(page).to have_css('.user-sort-dropdown .dropdown-toggle-text', text: 'Name, descending')
end end
it 'sorts by recent sign in', :clean_gitlab_redis_shared_state do it 'sorts by recent sign in', :clean_gitlab_redis_shared_state do
...@@ -74,7 +74,7 @@ describe 'Projects > Members > Sorting' do ...@@ -74,7 +74,7 @@ describe 'Projects > Members > Sorting' do
expect(first_member).to include(maintainer.name) expect(first_member).to include(maintainer.name)
expect(second_member).to include(developer.name) expect(second_member).to include(developer.name)
expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Recent sign in') expect(page).to have_css('.user-sort-dropdown .dropdown-toggle-text', text: 'Recent sign in')
end end
it 'sorts by oldest sign in', :clean_gitlab_redis_shared_state do it 'sorts by oldest sign in', :clean_gitlab_redis_shared_state do
...@@ -82,7 +82,7 @@ describe 'Projects > Members > Sorting' do ...@@ -82,7 +82,7 @@ describe 'Projects > Members > Sorting' do
expect(first_member).to include(developer.name) expect(first_member).to include(developer.name)
expect(second_member).to include(maintainer.name) expect(second_member).to include(maintainer.name)
expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Oldest sign in') expect(page).to have_css('.user-sort-dropdown .dropdown-toggle-text', text: 'Oldest sign in')
end end
def visit_members_list(sort:) def visit_members_list(sort:)
......
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