summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorStan Hu <stanhu@gmail.com>2019-08-08 04:40:55 +0000
committerStan Hu <stanhu@gmail.com>2019-08-08 04:40:55 +0000
commit3ad34c3a243cda19e73fbda0c9682e0a1c16c10a (patch)
tree38a419d054a0c999449d7837a311e28364e0cc42 /app
parent4985f6e26d758e7d5e43c3525a4afe4a9459ce8e (diff)
parentd4078b535c9854695e770cdfb5e0f4846a8cf64a (diff)
downloadgitlab-ce-3ad34c3a243cda19e73fbda0c9682e0a1c16c10a.tar.gz
Merge branch '20137-starrers' into 'master'
Add possibilty to view starrers ("stargazers") of a repository & any user's starred repositories Closes #20137 See merge request gitlab-org/gitlab-ce!24690
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/pages/users/user_tabs.js2
-rw-r--r--app/assets/javascripts/star.js2
-rw-r--r--app/assets/stylesheets/pages/members.scss39
-rw-r--r--app/assets/stylesheets/pages/users.scss105
-rw-r--r--app/controllers/projects/starrers_controller.rb34
-rw-r--r--app/controllers/users_controller.rb41
-rw-r--r--app/finders/starred_projects_finder.rb11
-rw-r--r--app/finders/users_star_projects_finder.rb36
-rw-r--r--app/helpers/projects_helper.rb5
-rw-r--r--app/helpers/sorting_helper.rb17
-rw-r--r--app/helpers/users_helper.rb2
-rw-r--r--app/models/user.rb11
-rw-r--r--app/models/users_star_project.rb27
-rw-r--r--app/views/groups/group_members/index.html.haml4
-rw-r--r--app/views/projects/buttons/_star.html.haml6
-rw-r--r--app/views/projects/project_members/_team.html.haml4
-rw-r--r--app/views/projects/starrers/_starrer.html.haml19
-rw-r--r--app/views/projects/starrers/index.html.haml32
-rw-r--r--app/views/shared/members/_sort_dropdown.html.haml2
-rw-r--r--app/views/shared/projects/_list.html.haml31
-rw-r--r--app/views/shared/projects/_project.html.haml4
-rw-r--r--app/views/users/show.html.haml8
22 files changed, 372 insertions, 70 deletions
diff --git a/app/assets/javascripts/pages/users/user_tabs.js b/app/assets/javascripts/pages/users/user_tabs.js
index 1d8b388e935..4ac4efec45d 100644
--- a/app/assets/javascripts/pages/users/user_tabs.js
+++ b/app/assets/javascripts/pages/users/user_tabs.js
@@ -143,7 +143,7 @@ export default class UserTabs {
this.loadOverviewTab();
}
- const loadableActions = ['groups', 'contributed', 'projects', 'snippets'];
+ const loadableActions = ['groups', 'contributed', 'projects', 'starred', 'snippets'];
if (loadableActions.indexOf(action) > -1) {
this.loadTab(action, endpoint);
}
diff --git a/app/assets/javascripts/star.js b/app/assets/javascripts/star.js
index 70f89152f70..97afeecd8ac 100644
--- a/app/assets/javascripts/star.js
+++ b/app/assets/javascripts/star.js
@@ -18,7 +18,7 @@ export default class Star {
const isStarred = $starSpan.hasClass('starred');
$this
.parent()
- .find('.star-count')
+ .find('.count')
.text(data.star_count);
if (isStarred) {
diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss
index 68af01f9ccc..d54d264bc5d 100644
--- a/app/assets/stylesheets/pages/members.scss
+++ b/app/assets/stylesheets/pages/members.scss
@@ -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;
diff --git a/app/assets/stylesheets/pages/users.scss b/app/assets/stylesheets/pages/users.scss
new file mode 100644
index 00000000000..3b018c1e087
--- /dev/null
+++ b/app/assets/stylesheets/pages/users.scss
@@ -0,0 +1,105 @@
+.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;
+ }
+}
+
+.card-body .user-info {
+ float: left;
+
+ .user {
+ color: $gl-text-color;
+ font-weight: $gl-font-weight-bold;
+ }
+}
diff --git a/app/controllers/projects/starrers_controller.rb b/app/controllers/projects/starrers_controller.rb
new file mode 100644
index 00000000000..c8facea1d70
--- /dev/null
+++ b/app/controllers/projects/starrers_controller.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+class Projects::StarrersController < Projects::ApplicationController
+ include SortingHelper
+
+ def index
+ @starrers = UsersStarProjectsFinder.new(@project, params, current_user: @current_user).execute
+
+ # Normally the number of public starrers is equal to the number of visible
+ # starrers. We need to fix the counts in two cases: when the current user
+ # is an admin (and can see everything) and when the current user has a
+ # private profile and has starred the project (and can see itself).
+ @public_count =
+ if @current_user&.admin?
+ @starrers.with_public_profile.count
+ elsif @current_user&.private_profile && has_starred_project?(@starrers)
+ @starrers.size - 1
+ else
+ @starrers.size
+ end
+
+ @total_count = @project.starrers.size
+ @private_count = @total_count - @public_count
+
+ @sort = params[:sort].presence || sort_value_name
+ @starrers = @starrers.sort_by_attribute(@sort).page(params[:page])
+ end
+
+ private
+
+ def has_starred_project?(starrers)
+ starrers.first { |starrer| starrer.user_id == current_user.id }
+ end
+end
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 072d62ddf38..91e0efcf45f 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -17,7 +17,7 @@ class UsersController < ApplicationController
prepend_before_action(only: [:show]) { authenticate_sessionless_user!(:rss) }
before_action :user, except: [:exists]
before_action :authorize_read_user_profile!,
- only: [:calendar, :calendar_activities, :groups, :projects, :contributed_projects, :snippets]
+ only: [:calendar, :calendar_activities, :groups, :projects, :contributed_projects, :starred_projects, :snippets]
def show
respond_to do |format|
@@ -57,27 +57,30 @@ class UsersController < ApplicationController
def projects
load_projects
- skip_pagination = Gitlab::Utils.to_boolean(params[:skip_pagination])
- skip_namespace = Gitlab::Utils.to_boolean(params[:skip_namespace])
- compact_mode = Gitlab::Utils.to_boolean(params[:compact_mode])
-
- respond_to do |format|
- format.html { render 'show' }
- format.json do
- pager_json("shared/projects/_list", @projects.count, projects: @projects, skip_pagination: skip_pagination, skip_namespace: skip_namespace, compact_mode: compact_mode)
- end
- end
+ present_projects(@projects)
end
def contributed
load_contributed_projects
+ present_projects(@contributed_projects)
+ end
+
+ def starred
+ load_starred_projects
+
+ present_projects(@starred_projects)
+ end
+
+ def present_projects(projects)
+ skip_pagination = Gitlab::Utils.to_boolean(params[:skip_pagination])
+ skip_namespace = Gitlab::Utils.to_boolean(params[:skip_namespace])
+ compact_mode = Gitlab::Utils.to_boolean(params[:compact_mode])
+
respond_to do |format|
format.html { render 'show' }
format.json do
- render json: {
- html: view_to_html_string("shared/projects/_list", projects: @contributed_projects)
- }
+ pager_json("shared/projects/_list", projects.count, projects: projects, skip_pagination: skip_pagination, skip_namespace: skip_namespace, compact_mode: compact_mode)
end
end
end
@@ -120,6 +123,10 @@ class UsersController < ApplicationController
ContributedProjectsFinder.new(user).execute(current_user)
end
+ def starred_projects
+ StarredProjectsFinder.new(user, current_user: current_user).execute
+ end
+
def contributions_calendar
@contributions_calendar ||= Gitlab::ContributionsCalendar.new(user, current_user)
end
@@ -145,6 +152,12 @@ class UsersController < ApplicationController
prepare_projects_for_rendering(@contributed_projects)
end
+ def load_starred_projects
+ @starred_projects = starred_projects
+
+ prepare_projects_for_rendering(@starred_projects)
+ end
+
def load_groups
@groups = JoinedGroupsFinder.new(user).execute(current_user)
diff --git a/app/finders/starred_projects_finder.rb b/app/finders/starred_projects_finder.rb
new file mode 100644
index 00000000000..fcb469d1d17
--- /dev/null
+++ b/app/finders/starred_projects_finder.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class StarredProjectsFinder < ProjectsFinder
+ def initialize(user, params: {}, current_user: nil)
+ super(
+ params: params,
+ current_user: current_user,
+ project_ids_relation: user.starred_projects.select(:id)
+ )
+ end
+end
diff --git a/app/finders/users_star_projects_finder.rb b/app/finders/users_star_projects_finder.rb
new file mode 100644
index 00000000000..49c4e087b4b
--- /dev/null
+++ b/app/finders/users_star_projects_finder.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+class UsersStarProjectsFinder
+ include CustomAttributesFilter
+
+ attr_accessor :params
+
+ def initialize(project, params = {}, current_user: nil)
+ @params = params
+ @project = project
+ @current_user = current_user
+ end
+
+ def execute
+ stars = UsersStarProject.all
+ stars = by_project(stars)
+ stars = by_search(stars)
+ stars = filter_visible_profiles(stars)
+
+ stars
+ end
+
+ private
+
+ def by_search(items)
+ params[:search].present? ? items.search(params[:search]) : items
+ end
+
+ def by_project(items)
+ items.by_project(@project)
+ end
+
+ def filter_visible_profiles(items)
+ items.with_visible_profile(@current_user)
+ end
+end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 8d0079a4dd3..71c9c121e48 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -601,6 +601,11 @@ module ProjectsHelper
end
end
+ def filter_starrer_path(options = {})
+ options = params.slice(:sort).merge(options).permit!
+ "#{request.path}?#{options.to_param}"
+ end
+
def sidebar_projects_paths
%w[
projects#show
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
index 15f35645c78..a4eb76a2359 100644
--- a/app/helpers/sorting_helper.rb
+++ b/app/helpers/sorting_helper.rb
@@ -167,6 +167,15 @@ module SortingHelper
}
end
+ def starrers_sort_options_hash
+ {
+ sort_value_name => sort_title_name,
+ sort_value_name_desc => sort_title_name_desc,
+ sort_value_recently_created => sort_title_recently_starred,
+ sort_value_oldest_created => sort_title_oldest_starred
+ }
+ end
+
def sortable_item(item, path, sorted_by)
link_to item, path, class: sorted_by == item ? 'is-active' : ''
end
@@ -327,6 +336,10 @@ module SortingHelper
s_('SortOptions|Oldest sign in')
end
+ def sort_title_oldest_starred
+ s_('SortOptions|Oldest starred')
+ end
+
def sort_title_oldest_updated
s_('SortOptions|Oldest updated')
end
@@ -347,6 +360,10 @@ module SortingHelper
s_('SortOptions|Recent sign in')
end
+ def sort_title_recently_starred
+ s_('SortOptions|Recently starred')
+ end
+
def sort_title_recently_updated
s_('SortOptions|Last updated')
end
diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb
index 73ca17c6605..e38e3378e07 100644
--- a/app/helpers/users_helper.rb
+++ b/app/helpers/users_helper.rb
@@ -89,7 +89,7 @@ module UsersHelper
tabs = []
if can?(current_user, :read_user_profile, @user)
- tabs += [:overview, :activity, :groups, :contributed, :projects, :snippets]
+ tabs += [:overview, :activity, :groups, :contributed, :projects, :starred, :snippets]
end
tabs
diff --git a/app/models/user.rb b/app/models/user.rb
index 4630552e02e..ac83c8e3256 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -282,6 +282,17 @@ 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 :with_public_profile, -> { where(private_profile: false) }
+
+ def self.with_visible_profile(user)
+ return with_public_profile if user.nil?
+
+ if user.admin?
+ all
+ else
+ with_public_profile.or(where(id: user.id))
+ end
+ end
# Limits the users to those that have TODOs, optionally in the given state.
#
diff --git a/app/models/users_star_project.rb b/app/models/users_star_project.rb
index 9be6bd2e6f3..3c7a805cc5c 100644
--- a/app/models/users_star_project.rb
+++ b/app/models/users_star_project.rb
@@ -1,10 +1,37 @@
# frozen_string_literal: true
class UsersStarProject < ApplicationRecord
+ include Sortable
+
belongs_to :project, counter_cache: :star_count, touch: true
belongs_to :user
validates :user, presence: true
validates :user_id, uniqueness: { scope: [:project_id] }
validates :project, presence: true
+
+ alias_attribute :starred_since, :created_at
+
+ scope :order_user_name_asc, -> { joins(:user).merge(User.order_name_asc) }
+ scope :order_user_name_desc, -> { joins(:user).merge(User.order_name_desc) }
+ scope :by_project, -> (project) { where(project_id: project.id) }
+ scope :with_visible_profile, -> (user) { joins(:user).merge(User.with_visible_profile(user)) }
+ scope :with_public_profile, -> { joins(:user).merge(User.with_public_profile) }
+
+ class << self
+ def sort_by_attribute(method)
+ order_method = method || 'id_desc'
+
+ case order_method.to_s
+ when 'name_asc' then order_user_name_asc
+ when 'name_desc' then order_user_name_desc
+ else
+ order_by(order_method)
+ end
+ end
+
+ def search(query)
+ joins(:user).merge(User.search(query))
+ end
+ end
end
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index 021c0b6c429..2b8c9f65d43 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -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'
diff --git a/app/views/projects/buttons/_star.html.haml b/app/views/projects/buttons/_star.html.haml
index 090d1549aa7..02e5297528b 100644
--- a/app/views/projects/buttons/_star.html.haml
+++ b/app/views/projects/buttons/_star.html.haml
@@ -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
diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml
index f220299ec30..5310c1fad01 100644
--- a/app/views/projects/project_members/_team.html.haml
+++ b/app/views/projects/project_members/_team.html.haml
@@ -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
diff --git a/app/views/projects/starrers/_starrer.html.haml b/app/views/projects/starrers/_starrer.html.haml
new file mode 100644
index 00000000000..377d62f8abd
--- /dev/null
+++ b/app/views/projects/starrers/_starrer.html.haml
@@ -0,0 +1,19 @@
+- starrer = local_assigns.fetch(:starrer)
+
+.col-lg-3.col-md-4.col-sm-12
+ .card
+ .card-body
+ = image_tag avatar_icon_for_user(starrer.user, 40), class: "avatar s40", alt: ''
+
+ .user-info
+ .block-truncated
+ = link_to starrer.user.name, user_path(starrer.user), class: 'user js-user-link', data: { user_id: starrer.user.id }
+
+ .block-truncated
+ %span.cgray= starrer.user.to_reference
+
+ - if starrer.user == current_user
+ %span.badge.badge-success.prepend-left-5= _("It's you")
+
+ .block-truncated
+ = time_ago_with_tooltip(starrer.starred_since)
diff --git a/app/views/projects/starrers/index.html.haml b/app/views/projects/starrers/index.html.haml
new file mode 100644
index 00000000000..e55ed99f643
--- /dev/null
+++ b/app/views/projects/starrers/index.html.haml
@@ -0,0 +1,32 @@
+- page_title _("Starrers")
+
+.top-area.adjust
+ .nav-text
+ - full_count_title = "#{@public_count} public and #{@private_count} private"
+ #{pluralize(@total_count, 'starrer')}: #{full_count_title}
+ - if @starrers.size > 0 || params[:search].present?
+ .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: _('Search'), class: 'form-control', spellcheck: false }
+ %button.user-search-btn{ type: "submit", "aria-label" => _("Submit search") }
+ = icon("search")
+ .dropdown.inline.user-sort-dropdown
+ = dropdown_toggle(starrers_sort_options_hash[@sort], { toggle: 'dropdown' })
+ %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable
+ %li.dropdown-header
+ = _("Sort by")
+ - starrers_sort_options_hash.each do |value, title|
+ %li
+ = link_to filter_starrer_path(sort: value), class: ("is-active" if @sort == value) do
+ = title
+- if @starrers.size > 0
+ .row.prepend-top-10
+ = render partial: 'starrer', collection: @starrers, as: :starrer
+ = paginate @starrers, theme: 'gitlab'
+- else
+ - if params[:search].present?
+ .nothing-here-block= _('No starrers matched your search')
+ - else
+ .nothing-here-block= _('Nobody has starred this repository yet')
diff --git a/app/views/shared/members/_sort_dropdown.html.haml b/app/views/shared/members/_sort_dropdown.html.haml
index 59bdfb73e6e..f5ebab035db 100644
--- a/app/views/shared/members/_sort_dropdown.html.haml
+++ b/app/views/shared/members/_sort_dropdown.html.haml
@@ -1,4 +1,4 @@
-.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
diff --git a/app/views/shared/projects/_list.html.haml b/app/views/shared/projects/_list.html.haml
index 67cb1aa549c..bb05658c719 100644
--- a/app/views/shared/projects/_list.html.haml
+++ b/app/views/shared/projects/_list.html.haml
@@ -17,15 +17,20 @@
- contributed_projects_illustration_path = 'illustrations/profile-page/contributed-projects.svg'
- contributed_projects_current_user_empty_message_header = s_('UserProfile|Explore public groups to find projects to contribute to.')
- contributed_projects_visitor_empty_message = s_('UserProfile|This user hasn\'t contributed to any projects')
+- starred_projects_illustration_path = 'illustrations/starred_empty.svg'
+- starred_projects_current_user_empty_message_header = s_('UserProfile|Star projects to track their progress and show your appreciation.')
+- starred_projects_visitor_empty_message = s_('UserProfile|This user hasn\'t starred any projects')
- own_projects_illustration_path = 'illustrations/profile-page/personal-project.svg'
- own_projects_current_user_empty_message_header = s_('UserProfile|You haven\'t created any personal projects.')
- own_projects_current_user_empty_message_description = s_('UserProfile|Your projects can be available publicly, internally, or privately, at your choice.')
- own_projects_visitor_empty_message = s_('UserProfile|This user doesn\'t have any personal projects')
- explore_page_empty_message = s_('UserProfile|Explore public groups to find projects to contribute to.')
-- primary_button_label = _('New project')
-- primary_button_link = new_project_path
-- secondary_button_label = _('Explore groups')
-- secondary_button_link = explore_groups_path
+- new_project_button_label = _('New project')
+- new_project_button_link = new_project_path
+- explore_projects_button_label = _('Explore projects')
+- explore_projects_button_link = explore_projects_path
+- explore_groups_button_label = _('Explore groups')
+- explore_groups_button_link = explore_groups_path
.js-projects-list-holder
- if any_projects?(projects)
@@ -48,15 +53,21 @@
- if @contributed_projects
= render partial: 'shared/empty_states/profile_tabs', locals: { illustration_path: contributed_projects_illustration_path,
current_user_empty_message_header: contributed_projects_current_user_empty_message_header,
- primary_button_label: primary_button_label,
- primary_button_link: primary_button_link,
- secondary_button_label: secondary_button_label,
- secondary_button_link: secondary_button_link,
+ primary_button_label: new_project_button_label,
+ primary_button_link: new_project_button_link,
+ secondary_button_label: explore_groups_button_label,
+ secondary_button_link: explore_groups_button_link,
visitor_empty_message: contributed_projects_visitor_empty_message }
+ - elsif @starred_projects
+ = render partial: 'shared/empty_states/profile_tabs', locals: { illustration_path: starred_projects_illustration_path,
+ current_user_empty_message_header: starred_projects_current_user_empty_message_header,
+ primary_button_label: explore_projects_button_label,
+ primary_button_link: explore_projects_button_link,
+ visitor_empty_message: starred_projects_visitor_empty_message }
- else
= render partial: 'shared/empty_states/profile_tabs', locals: { illustration_path: own_projects_illustration_path,
current_user_empty_message_header: own_projects_current_user_empty_message_header,
current_user_empty_message_description: own_projects_current_user_empty_message_description,
- primary_button_label: primary_button_label,
- primary_button_link: primary_button_link,
+ primary_button_label: new_project_button_label,
+ primary_button_link: new_project_button_link,
visitor_empty_message: defined?(explore_page) && explore_page ? explore_page_empty_message : own_projects_visitor_empty_message }
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index f40a9cffb29..b7474d891dc 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -63,7 +63,9 @@
- if project.archived
%span.d-flex.icon-wrapper.badge.badge-warning archived
- if stars
- %span.d-flex.align-items-center.icon-wrapper.stars.has-tooltip{ data: { container: 'body', placement: 'top' }, title: _('Stars') }
+ = link_to project_starrers_path(project),
+ class: "d-flex align-items-center icon-wrapper stars has-tooltip",
+ title: _('Stars'), data: { container: 'body', placement: 'top' } do
= sprite_icon('star', size: 14, css_class: 'append-right-4')
= number_with_delimiter(project.star_count)
- if forks
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index b3a73030859..73bee7c2586 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -111,6 +111,10 @@
%li.js-projects-tab
= link_to user_projects_path, data: { target: 'div#projects', action: 'projects', toggle: 'tab', endpoint: user_projects_path(format: :json) } do
= s_('UserProfile|Personal projects')
+ - if profile_tab?(:starred)
+ %li.js-starred-tab
+ = link_to user_starred_projects_path, data: { target: 'div#starred', action: 'starred', toggle: 'tab', endpoint: user_starred_projects_path(format: :json) } do
+ = s_('UserProfile|Starred projects')
- if profile_tab?(:snippets)
%li.js-snippets-tab
= link_to user_snippets_path, data: { target: 'div#snippets', action: 'snippets', toggle: 'tab', endpoint: user_snippets_path(format: :json) } do
@@ -142,6 +146,10 @@
#projects.tab-pane
-# This tab is always loaded via AJAX
+ - if profile_tab?(:starred)
+ #starred.tab-pane
+ -# This tab is always loaded via AJAX
+
- if profile_tab?(:snippets)
#snippets.tab-pane
-# This tab is always loaded via AJAX