Commit 59976090 authored by Camil Staps's avatar Camil Staps

Add /starrers view for projects

parent 5b20df0a
......@@ -9,10 +9,6 @@
}
}
.member-sort-dropdown {
margin-left: $gl-padding-8;
}
.member {
&.is-overridden {
.btn-ldap-override {
......@@ -62,36 +58,9 @@
}
}
.member-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;
}
}
.member-access-text {
margin-left: auto;
line-height: 43px;
}
.member-search-btn {
......@@ -177,7 +146,7 @@
padding-bottom: 1px;
}
.flex-project-members-form {
.flex-users-form {
flex-wrap: nowrap;
white-space: nowrap;
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
users = by_2fa(users)
users = by_created_at(users)
users = by_custom_attributes(users)
users = by_has_starred(users)
users
end
......@@ -94,4 +95,8 @@ class UsersFinder
users
end
end
def by_has_starred(items)
params[:has_starred].present? ? items.has_starred(params[:has_starred]) : items
end
end
......@@ -108,4 +108,9 @@ module UsersHelper
items
end
def filter_user_path(options = {})
options = params.slice(:sort).merge(options).permit!
"#{request.path}?#{options.to_param}"
end
end
......@@ -282,6 +282,7 @@ class User < ApplicationRecord
scope :for_todos, -> (todos) { where(id: todos.select(:user_id)) }
scope :with_emails, -> { preload(:emails) }
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.
#
......
......@@ -25,11 +25,11 @@
Members with access to
%strong= @group.name
%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
.position-relative.append-right-8
= 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")
- if can_manage_members
= render 'shared/members/filter_2fa_dropdown'
......
......@@ -8,7 +8,8 @@
= sprite_icon('star-o', { css_class: 'icon' })
%span= s_('ProjectOverview|Star')
%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
.count-badge.d-inline-flex.align-item-stretch.append-right-8
......@@ -16,4 +17,5 @@
= sprite_icon('star-o', { css_class: 'icon' })
%span= s_('ProjectOverview|Star')
%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 @@
%span.flex-project-title
= _("Members of <strong>%{project_name}</strong>").html_safe % { project_name: sanitize(project.name, tags: []) }
%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
.position-relative
= 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")
= render 'shared/members/sort_dropdown'
%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' })
%ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable
%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
get :recent
end
end
resources :releases, only: [:index]
resources :starrers, only: [:index]
resources :forks, only: [:index, :new, :create]
resources :group_links, only: [:index, :create, :update, :destroy], constraints: { id: /\d+/ }
......
......@@ -19,9 +19,9 @@ describe 'Search group member' do
end
it 'renders member users' do
page.within '.member-search-form' do
page.within '.user-search-form' do
fill_in 'search', with: member.name
find('.member-search-btn').click
find('.user-search-btn').click
end
group_members_list = find(".card .content-list")
......
......@@ -19,7 +19,7 @@ describe 'Groups > Members > Sort members' do
expect(first_member).to include(owner.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
it 'sorts by access level ascending' do
......@@ -27,7 +27,7 @@ describe 'Groups > Members > Sort members' do
expect(first_member).to include(developer.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
it 'sorts by access level descending' do
......@@ -35,7 +35,7 @@ describe 'Groups > Members > Sort members' do
expect(first_member).to include(owner.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
it 'sorts by last joined' do
......@@ -43,7 +43,7 @@ describe 'Groups > Members > Sort members' do
expect(first_member).to include(developer.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
it 'sorts by oldest joined' do
......@@ -51,7 +51,7 @@ describe 'Groups > Members > Sort members' do
expect(first_member).to include(owner.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
it 'sorts by name ascending' do
......@@ -59,7 +59,7 @@ describe 'Groups > Members > Sort members' do
expect(first_member).to include(owner.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
it 'sorts by name descending' do
......@@ -67,7 +67,7 @@ describe 'Groups > Members > Sort members' do
expect(first_member).to include(developer.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
it 'sorts by recent sign in', :clean_gitlab_redis_shared_state do
......@@ -75,7 +75,7 @@ describe 'Groups > Members > Sort members' do
expect(first_member).to include(owner.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
it 'sorts by oldest sign in', :clean_gitlab_redis_shared_state do
......@@ -83,7 +83,7 @@ describe 'Groups > Members > Sort members' do
expect(first_member).to include(developer.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
def visit_members_list(sort:)
......
......@@ -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
it 'finds no results' do
page.within '.member-search-form' do
page.within '.user-search-form' do
fill_in 'search', with: 'testing 123'
find('.member-search-btn').click
find('.user-search-btn').click
end
expect(page).not_to have_selector('.group_member')
end
it 'finds results' do
page.within '.member-search-form' do
page.within '.user-search-form' do
fill_in 'search', with: group.name
find('.member-search-btn').click
find('.user-search-btn').click
end
expect(page).to have_selector('.group_member', count: 1)
......
......@@ -18,7 +18,7 @@ describe 'Projects > Members > Sorting' do
expect(first_member).to include(maintainer.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
it 'sorts by access level ascending' do
......@@ -26,7 +26,7 @@ describe 'Projects > Members > Sorting' do
expect(first_member).to include(developer.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
it 'sorts by access level descending' do
......@@ -34,7 +34,7 @@ describe 'Projects > Members > Sorting' do
expect(first_member).to include(maintainer.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
it 'sorts by last joined' do
......@@ -42,7 +42,7 @@ describe 'Projects > Members > Sorting' do
expect(first_member).to include(maintainer.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
it 'sorts by oldest joined' do
......@@ -50,7 +50,7 @@ describe 'Projects > Members > Sorting' do
expect(first_member).to include(developer.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
it 'sorts by name ascending' do
......@@ -58,7 +58,7 @@ describe 'Projects > Members > Sorting' do
expect(first_member).to include(maintainer.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
it 'sorts by name descending' do
......@@ -66,7 +66,7 @@ describe 'Projects > Members > Sorting' do
expect(first_member).to include(developer.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
it 'sorts by recent sign in', :clean_gitlab_redis_shared_state do
......@@ -74,7 +74,7 @@ describe 'Projects > Members > Sorting' do
expect(first_member).to include(maintainer.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
it 'sorts by oldest sign in', :clean_gitlab_redis_shared_state do
......@@ -82,7 +82,7 @@ describe 'Projects > Members > Sorting' do
expect(first_member).to include(developer.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
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