Commit c5048400 authored by Nick Thomas's avatar Nick Thomas

Merge branch 'feat/update-contribution-calendar' into 'master'

Include private contributions in user contribution graph

Closes #14078

See merge request gitlab-org/gitlab-ce!17296
parents 272281e4 124cece3
...@@ -101,6 +101,7 @@ class ProfilesController < Profiles::ApplicationController ...@@ -101,6 +101,7 @@ class ProfilesController < Profiles::ApplicationController
:organization, :organization,
:preferred_language, :preferred_language,
:private_profile, :private_profile,
:include_private_contributions,
status: [:emoji, :message] status: [:emoji, :message]
) )
end end
......
...@@ -48,20 +48,6 @@ class UserRecentEventsFinder ...@@ -48,20 +48,6 @@ class UserRecentEventsFinder
end end
def projects def projects
# Compile a list of projects `current_user` interacted with target_user.project_interactions.to_sql
# and `target_user` is allowed to see.
authorized = target_user
.project_interactions
.joins(:project_authorizations)
.where(project_authorizations: { user: current_user })
.select(:id)
visible = target_user
.project_interactions
.where(visibility_level: Gitlab::VisibilityLevel.levels_for_user(current_user))
.select(:id)
Gitlab::SQL::Union.new([authorized, visible]).to_sql
end end
end end
...@@ -19,7 +19,7 @@ module EventsHelper ...@@ -19,7 +19,7 @@ module EventsHelper
name = self_added ? 'You' : author.name name = self_added ? 'You' : author.name
link_to name, user_path(author.username), title: name link_to name, user_path(author.username), title: name
else else
event.author_name escape_once(event.author_name)
end end
end end
......
...@@ -151,15 +151,17 @@ class Event < ActiveRecord::Base ...@@ -151,15 +151,17 @@ class Event < ActiveRecord::Base
if push? || commit_note? if push? || commit_note?
Ability.allowed?(user, :download_code, project) Ability.allowed?(user, :download_code, project)
elsif membership_changed? elsif membership_changed?
true Ability.allowed?(user, :read_project, project)
elsif created_project? elsif created_project?
true Ability.allowed?(user, :read_project, project)
elsif issue? || issue_note? elsif issue? || issue_note?
Ability.allowed?(user, :read_issue, note? ? note_target : target) Ability.allowed?(user, :read_issue, note? ? note_target : target)
elsif merge_request? || merge_request_note? elsif merge_request? || merge_request_note?
Ability.allowed?(user, :read_merge_request, note? ? note_target : target) Ability.allowed?(user, :read_merge_request, note? ? note_target : target)
elsif milestone?
Ability.allowed?(user, :read_project, project)
else else
milestone? false # No other event types are visible
end end
end end
......
...@@ -11,3 +11,5 @@ ...@@ -11,3 +11,5 @@
= render "events/event/note", event: event = render "events/event/note", event: event
- else - else
= render "events/event/common", event: event = render "events/event/common", event: event
- elsif @user.include_private_contributions?
= render "events/event/private", event: event
%span.event-scope %span.event-scope
= event_preposition(event) = event_preposition(event)
- if event.project - if event.project
= link_to_project event.project = link_to_project(event.project)
- else - else
= event.project_name = event.project_name
= icon_for_profile_event(event) = icon_for_profile_event(event)
.event-title .event-title
%span.author_name= link_to_author event %span.author_name= link_to_author(event)
%span{ class: event.action_name } %span{ class: event.action_name }
- if event.target - if event.target
= event.action_name = event.action_name
......
= icon_for_profile_event(event) = icon_for_profile_event(event)
.event-title .event-title
%span.author_name= link_to_author event %span.author_name= link_to_author(event)
%span{ class: event.action_name } %span{ class: event.action_name }
= event_action_name(event) = event_action_name(event)
- if event.project - if event.project
= link_to_project event.project = link_to_project(event.project)
- else - else
= event.project_name = event.project_name
= icon_for_profile_event(event) = icon_for_profile_event(event)
.event-title .event-title
%span.author_name= link_to_author event %span.author_name= link_to_author(event)
= event.action_name = event.action_name
= event_note_title_html(event) = event_note_title_html(event)
......
.event-inline.event-item
.event-item-timestamp
= time_ago_with_tooltip(event.created_at)
.system-note-image= sprite_icon('eye-slash', size: 16, css_class: 'icon')
.event-title
- author_name = capture do
%span.author_name= link_to_author(event)
= s_('Profiles|%{author_name} made a private contribution').html_safe % { author_name: author_name }
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
= icon_for_profile_event(event) = icon_for_profile_event(event)
.event-title .event-title
%span.author_name= link_to_author event %span.author_name= link_to_author(event)
%span.pushed #{event.action_name} #{event.ref_type} %span.pushed #{event.action_name} #{event.ref_type}
%strong %strong
- commits_link = project_commits_path(project, event.ref_name) - commits_link = project_commits_path(project, event.ref_name)
......
- breadcrumb_title "Edit Profile" - breadcrumb_title s_("Profiles|Edit Profile")
- @content_class = "limit-container-width" unless fluid_layout - @content_class = "limit-container-width" unless fluid_layout
- gravatar_link = link_to Gitlab.config.gravatar.host, 'https://' + Gitlab.config.gravatar.host
= bootstrap_form_for @user, url: profile_path, method: :put, html: { multipart: true, class: 'edit-user prepend-top-default js-quick-submit' }, authenticity_token: true do |f| = bootstrap_form_for @user, url: profile_path, method: :put, html: { multipart: true, class: 'edit-user prepend-top-default js-quick-submit' }, authenticity_token: true do |f|
= form_errors(@user) = form_errors(@user)
...@@ -7,34 +8,36 @@ ...@@ -7,34 +8,36 @@
.row .row
.col-lg-4.profile-settings-sidebar .col-lg-4.profile-settings-sidebar
%h4.prepend-top-0 %h4.prepend-top-0
Public Avatar = s_("Profiles|Public Avatar")
%p %p
- if @user.avatar? - if @user.avatar?
You can change your avatar here
- if gravatar_enabled? - if gravatar_enabled?
or remove the current avatar to revert to #{link_to Gitlab.config.gravatar.host, 'https://' + Gitlab.config.gravatar.host} = s_("Profiles|You can change your avatar here or remove the current avatar to revert to %{gravatar_link}").html_safe % { gravatar_link: gravatar_link }
- else
= s_("Profiles|You can change your avatar here")
- else - else
You can upload an avatar here
- if gravatar_enabled? - if gravatar_enabled?
or change it at #{link_to Gitlab.config.gravatar.host, 'https://' + Gitlab.config.gravatar.host} = s_("Profiles|You can upload your avatar here or change it at %{gravatar_link}").html_safe % { gravatar_link: gravatar_link }
- else
= s_("Profiles|You can upload your avatar here")
.col-lg-8 .col-lg-8
.clearfix.avatar-image.append-bottom-default .clearfix.avatar-image.append-bottom-default
= link_to avatar_icon_for_user(@user, 400), target: '_blank', rel: 'noopener noreferrer' do = link_to avatar_icon_for_user(@user, 400), target: '_blank', rel: 'noopener noreferrer' do
= image_tag avatar_icon_for_user(@user, 160), alt: '', class: 'avatar s160' = image_tag avatar_icon_for_user(@user, 160), alt: '', class: 'avatar s160'
%h5.prepend-top-0= _("Upload new avatar") %h5.prepend-top-0= s_("Profiles|Upload new avatar")
.prepend-top-5.append-bottom-10 .prepend-top-5.append-bottom-10
%button.btn.js-choose-user-avatar-button{ type: 'button' }= _("Choose file...") %button.btn.js-choose-user-avatar-button{ type: 'button' }= s_("Profiles|Choose file...")
%span.avatar-file-name.prepend-left-default.js-avatar-filename= _("No file chosen") %span.avatar-file-name.prepend-left-default.js-avatar-filename= s_("Profiles|No file chosen")
= f.file_field_without_bootstrap :avatar, class: 'js-user-avatar-input hidden', accept: 'image/*' = f.file_field_without_bootstrap :avatar, class: 'js-user-avatar-input hidden', accept: 'image/*'
.form-text.text-muted= _("The maximum file size allowed is 200KB.") .form-text.text-muted= s_("Profiles|The maximum file size allowed is 200KB.")
- if @user.avatar? - if @user.avatar?
%hr %hr
= link_to _('Remove avatar'), profile_avatar_path, data: { confirm: _('Avatar will be removed. Are you sure?') }, method: :delete, class: 'btn btn-danger btn-inverted' = link_to s_("Profiles|Remove avatar"), profile_avatar_path, data: { confirm: s_("Profiles|Avatar will be removed. Are you sure?") }, method: :delete, class: 'btn btn-danger btn-inverted'
%hr %hr
.row .row
.col-lg-4.profile-settings-sidebar .col-lg-4.profile-settings-sidebar
%h4.prepend-top-0= s_("User|Current status") %h4.prepend-top-0= s_("Profiles|Current status")
%p= s_("Profiles|This emoji and message will appear on your profile and throughout the interface.") %p= s_("Profiles|This emoji and message will appear on your profile and throughout the interface.")
.col-lg-8 .col-lg-8
= f.fields_for :status, @user.status do |status_form| = f.fields_for :status, @user.status do |status_form|
...@@ -66,62 +69,66 @@ ...@@ -66,62 +69,66 @@
.row .row
.col-lg-4.profile-settings-sidebar .col-lg-4.profile-settings-sidebar
%h4.prepend-top-0 %h4.prepend-top-0
Main settings = s_("Profiles|Main settings")
%p %p
This information will appear on your profile. = s_("Profiles|This information will appear on your profile.")
- if current_user.ldap_user? - if current_user.ldap_user?
Some options are unavailable for LDAP accounts = s_("Profiles|Some options are unavailable for LDAP accounts")
.col-lg-8 .col-lg-8
.row .row
- if @user.read_only_attribute?(:name) - if @user.read_only_attribute?(:name)
= f.text_field :name, required: true, readonly: true, wrapper: { class: 'col-md-9' }, = f.text_field :name, required: true, readonly: true, wrapper: { class: 'col-md-9' },
help: "Your name was automatically set based on your #{ attribute_provider_label(:name) } account, so people you know can recognize you." help: s_("Profiles|Your name was automatically set based on your %{provider_label} account, so people you know can recognize you.") % { provider_label: attribute_provider_label(:name) }
- else - else
= f.text_field :name, label: 'Full name', required: true, wrapper: { class: 'col-md-9' }, help: "Enter your name, so people you know can recognize you." = f.text_field :name, label: 'Full name', required: true, wrapper: { class: 'col-md-9' }, help: "Enter your name, so people you know can recognize you."
= f.text_field :id, readonly: true, label: 'User ID', wrapper: { class: 'col-md-3' } = f.text_field :id, readonly: true, label: 'User ID', wrapper: { class: 'col-md-3' }
- if @user.read_only_attribute?(:email) - if @user.read_only_attribute?(:email)
= f.text_field :email, required: true, readonly: true, help: "Your email address was automatically set based on your #{ attribute_provider_label(:email) } account." = f.text_field :email, required: true, readonly: true, help: s_("Profiles|Your email address was automatically set based on your %{provider_label} account.") % { provider_label: attribute_provider_label(:email) }
- else - else
= f.text_field :email, required: true, value: (@user.email unless @user.temp_oauth_email?), = f.text_field :email, required: true, value: (@user.email unless @user.temp_oauth_email?),
help: user_email_help_text(@user) help: user_email_help_text(@user)
= f.select :public_email, options_for_select(@user.all_emails, selected: @user.public_email), = f.select :public_email, options_for_select(@user.all_emails, selected: @user.public_email),
{ help: 'This email will be displayed on your public profile.', include_blank: 'Do not show on profile' }, { help: s_("Profiles|This email will be displayed on your public profile."), include_blank: s_("Profiles|Do not show on profile") },
control_class: 'select2' control_class: 'select2'
= f.select :preferred_language, Gitlab::I18n::AVAILABLE_LANGUAGES.map { |value, label| [label, value] }, = f.select :preferred_language, Gitlab::I18n::AVAILABLE_LANGUAGES.map { |value, label| [label, value] },
{ help: 'This feature is experimental and translations are not complete yet.' }, { help: s_("Profiles|This feature is experimental and translations are not complete yet.") },
control_class: 'select2' control_class: 'select2'
= f.text_field :skype = f.text_field :skype
= f.text_field :linkedin = f.text_field :linkedin
= f.text_field :twitter = f.text_field :twitter
= f.text_field :website_url, label: 'Website' = f.text_field :website_url, label: s_("Profiles|Website")
- if @user.read_only_attribute?(:location) - if @user.read_only_attribute?(:location)
= f.text_field :location, readonly: true, help: "Your location was automatically set based on your #{ attribute_provider_label(:location) } account." = f.text_field :location, readonly: true, help: s_("Profiles|Your location was automatically set based on your %{provider_label} account.") % { provider_label: attribute_provider_label(:location) }
- else - else
= f.text_field :location = f.text_field :location
= f.text_field :organization = f.text_field :organization
= f.text_area :bio, rows: 4, maxlength: 250, help: 'Tell us about yourself in fewer than 250 characters.' = f.text_area :bio, rows: 4, maxlength: 250, help: s_("Profiles|Tell us about yourself in fewer than 250 characters.")
%hr %hr
%h5 Private profile %h5= ("Private profile")
- private_profile_label = capture do - private_profile_label = capture do
Don't display activity-related personal information on your profile = s_("Profiles|Don't display activity-related personal information on your profiles")
= link_to icon('question-circle'), help_page_path('user/profile/index.md', anchor: 'private-profile') = link_to icon('question-circle'), help_page_path('user/profile/index.md', anchor: 'private-profile')
= f.check_box :private_profile, label: private_profile_label = f.check_box :private_profile, label: private_profile_label
%h5= s_("Profiles|Private contributions")
= f.check_box :include_private_contributions, label: 'Include private contributions on my profile'
.help-block
= s_("Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information.")
.prepend-top-default.append-bottom-default .prepend-top-default.append-bottom-default
= f.submit 'Update profile settings', class: 'btn btn-success' = f.submit s_("Profiles|Update profile settings"), class: 'btn btn-success'
= link_to 'Cancel', user_path(current_user), class: 'btn btn-cancel' = link_to _("Cancel"), user_path(current_user), class: 'btn btn-cancel'
.modal.modal-profile-crop .modal.modal-profile-crop
.modal-dialog .modal-dialog
.modal-content .modal-content
.modal-header .modal-header
%h4.modal-title %h4.modal-title
Position and size your new avatar = s_("Profiles|Position and size your new avatar")
%button.close{ type: "button", "data-dismiss": "modal", "aria-label" => _('Close') } %button.close{ type: "button", "data-dismiss": "modal", "aria-label" => _("Close") }
%span{ "aria-hidden": true } &times; %span{ "aria-hidden": true } &times;
.modal-body .modal-body
.profile-crop-image-container .profile-crop-image-container
%img.modal-profile-crop-image{ alt: 'Avatar cropper' } %img.modal-profile-crop-image{ alt: s_("Profiles|Avatar cropper") }
.crop-controls .crop-controls
.btn-group .btn-group
%button.btn.btn-primary{ data: { method: 'zoom', option: '0.1' } } %button.btn.btn-primary{ data: { method: 'zoom', option: '0.1' } }
...@@ -130,4 +137,4 @@ ...@@ -130,4 +137,4 @@
%span.fa.fa-search-minus %span.fa.fa-search-minus
.modal-footer .modal-footer
%button.btn.btn-primary.js-upload-user-avatar{ type: 'button' } %button.btn.btn-primary.js-upload-user-avatar{ type: 'button' }
Set new profile picture = s_("Profiles|Set new profile picture")
%h4.prepend-top-20 %h4.prepend-top-20
Contributions for = _("Contributions for <strong>%{calendar_date}</strong>").html_safe % { calendar_date: @calendar_date.to_s(:medium) }
%strong= @calendar_date.to_s(:medium)
- if @events.any? - if @events.any?
%ul.bordered-list %ul.bordered-list
...@@ -9,25 +8,28 @@ ...@@ -9,25 +8,28 @@
%span.light %span.light
%i.fa.fa-clock-o %i.fa.fa-clock-o
= event.created_at.strftime('%-I:%M%P') = event.created_at.strftime('%-I:%M%P')
- if event.push? - if event.visible_to_user?(current_user)
#{event.action_name} #{event.ref_type} - if event.push?
#{event.action_name} #{event.ref_type}
%strong
- commits_path = project_commits_path(event.project, event.ref_name)
= link_to_if event.project.repository.branch_exists?(event.ref_name), event.ref_name, commits_path
- else
= event_action_name(event)
%strong
- if event.note?
= link_to event.note_target.to_reference, event_note_target_url(event), class: 'has-tooltip', title: event.target_title
- elsif event.target
= link_to event.target.to_reference, [event.project.namespace.becomes(Namespace), event.project, event.target], class: 'has-tooltip', title: event.target_title
at
%strong %strong
- commits_path = project_commits_path(event.project, event.ref_name) - if event.project
= link_to_if event.project.repository.branch_exists?(event.ref_name), event.ref_name, commits_path = link_to_project(event.project)
- else
= event.project_name
- else - else
= event_action_name(event) made a private contribution
%strong
- if event.note?
= link_to event.note_target.to_reference, event_note_target_url(event), class: 'has-tooltip', title: event.target_title
- elsif event.target
= link_to event.target.to_reference, [event.project.namespace.becomes(Namespace), event.project, event.target], class: 'has-tooltip', title: event.target_title
at
%strong
- if event.project
= link_to_project event.project
- else
= event.project_name
- else - else
%p %p
No contributions found for #{@calendar_date.to_s(:medium)} = _('No contributions were found')
---
title: Include private contributions to contributions calendar
merge_request: 17296
author: George Tsiolis
type: added
class AddIncludePrivateContributionsToUsers < ActiveRecord::Migration
DOWNTIME = false
def change
add_column :users, :include_private_contributions, :boolean
end
end
...@@ -2205,6 +2205,7 @@ ActiveRecord::Schema.define(version: 20180906101639) do ...@@ -2205,6 +2205,7 @@ ActiveRecord::Schema.define(version: 20180906101639) do
t.integer "accepted_term_id" t.integer "accepted_term_id"
t.string "feed_token" t.string "feed_token"
t.boolean "private_profile" t.boolean "private_profile"
t.boolean "include_private_contributions"
end end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
......
...@@ -91,6 +91,18 @@ To enable private profile: ...@@ -91,6 +91,18 @@ To enable private profile:
NOTE: **Note:** NOTE: **Note:**
You and GitLab admins can see your the abovementioned information on your profile even if it is private. You and GitLab admins can see your the abovementioned information on your profile even if it is private.
## Private contributions
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/14078) in GitLab 11.3.
Enabling private contributions will include contributions to private projects, in the user contribution calendar graph and user recent activity.
To enable private contributions:
1. Navigate to your personal [profile settings](#profile-settings).
2. Check the "Private contributions" option.
3. Hit **Update profile settings**.
## Current status ## Current status
> Introduced in GitLab 11.2. > Introduced in GitLab 11.2.
......
...@@ -7,7 +7,11 @@ module Gitlab ...@@ -7,7 +7,11 @@ module Gitlab
def initialize(contributor, current_user = nil) def initialize(contributor, current_user = nil)
@contributor = contributor @contributor = contributor
@current_user = current_user @current_user = current_user
@projects = ContributedProjectsFinder.new(contributor).execute(current_user) @projects = if @contributor.include_private_contributions?
ContributedProjectsFinder.new(@contributor).execute(@contributor)
else
ContributedProjectsFinder.new(contributor).execute(current_user)
end
end end
def activity_dates def activity_dates
...@@ -36,13 +40,9 @@ module Gitlab ...@@ -36,13 +40,9 @@ module Gitlab
def events_by_date(date) def events_by_date(date)
return Event.none unless can_read_cross_project? return Event.none unless can_read_cross_project?
events = Event.contributions.where(author_id: contributor.id) Event.contributions.where(author_id: contributor.id)
.where(created_at: date.beginning_of_day..date.end_of_day) .where(created_at: date.beginning_of_day..date.end_of_day)
.where(project_id: projects) .where(project_id: projects)
# Use visible_to_user? instead of the complicated logic in activity_dates
# because we're only viewing the events for a single day.
events.select { |event| event.visible_to_user?(current_user) }
end end
def starting_year def starting_year
......
...@@ -1894,6 +1894,9 @@ msgstr "" ...@@ -1894,6 +1894,9 @@ msgstr ""
msgid "Contribution guide" msgid "Contribution guide"
msgstr "" msgstr ""
msgid "Contributions for <strong>%{calendar_date}</strong>"
msgstr ""
msgid "Contributors" msgid "Contributors"
msgstr "" msgstr ""
...@@ -3932,6 +3935,9 @@ msgstr "" ...@@ -3932,6 +3935,9 @@ msgstr ""
msgid "No container images stored for this project. Add one by following the instructions above." msgid "No container images stored for this project. Add one by following the instructions above."
msgstr "" msgstr ""
msgid "No contributions were found"
msgstr ""
msgid "No due date" msgid "No due date"
msgstr "" msgstr ""
...@@ -4471,6 +4477,9 @@ msgstr "" ...@@ -4471,6 +4477,9 @@ msgstr ""
msgid "Profiles| You are going to change the username %{currentUsernameBold} to %{newUsernameBold}. Profile and projects will be redirected to the %{newUsername} namespace but this redirect will expire once the %{currentUsername} namespace is registered by another user or group. Please update your Git repository remotes as soon as possible." msgid "Profiles| You are going to change the username %{currentUsernameBold} to %{newUsernameBold}. Profile and projects will be redirected to the %{newUsername} namespace but this redirect will expire once the %{currentUsername} namespace is registered by another user or group. Please update your Git repository remotes as soon as possible."
msgstr "" msgstr ""
msgid "Profiles|%{author_name} made a private contribution"
msgstr ""
msgid "Profiles|Account scheduled for removal." msgid "Profiles|Account scheduled for removal."
msgstr "" msgstr ""
...@@ -4480,15 +4489,30 @@ msgstr "" ...@@ -4480,15 +4489,30 @@ msgstr ""
msgid "Profiles|Add status emoji" msgid "Profiles|Add status emoji"
msgstr "" msgstr ""
msgid "Profiles|Avatar cropper"
msgstr ""
msgid "Profiles|Avatar will be removed. Are you sure?"
msgstr ""
msgid "Profiles|Change username" msgid "Profiles|Change username"
msgstr "" msgstr ""
msgid "Profiles|Choose file..."
msgstr ""
msgid "Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information."
msgstr ""
msgid "Profiles|Clear status" msgid "Profiles|Clear status"
msgstr "" msgstr ""
msgid "Profiles|Current path: %{path}" msgid "Profiles|Current path: %{path}"
msgstr "" msgstr ""
msgid "Profiles|Current status"
msgstr ""
msgid "Profiles|Delete Account" msgid "Profiles|Delete Account"
msgstr "" msgstr ""
...@@ -4501,39 +4525,108 @@ msgstr "" ...@@ -4501,39 +4525,108 @@ msgstr ""
msgid "Profiles|Deleting an account has the following effects:" msgid "Profiles|Deleting an account has the following effects:"
msgstr "" msgstr ""
msgid "Profiles|Do not show on profile"
msgstr ""
msgid "Profiles|Don't display activity-related personal information on your profiles"
msgstr ""
msgid "Profiles|Edit Profile"
msgstr ""
msgid "Profiles|Invalid password" msgid "Profiles|Invalid password"
msgstr "" msgstr ""
msgid "Profiles|Invalid username" msgid "Profiles|Invalid username"
msgstr "" msgstr ""
msgid "Profiles|Main settings"
msgstr ""
msgid "Profiles|No file chosen"
msgstr ""
msgid "Profiles|Path" msgid "Profiles|Path"
msgstr "" msgstr ""
msgid "Profiles|Position and size your new avatar"
msgstr ""
msgid "Profiles|Private contributions"
msgstr ""
msgid "Profiles|Public Avatar"
msgstr ""
msgid "Profiles|Remove avatar"
msgstr ""
msgid "Profiles|Set new profile picture"
msgstr ""
msgid "Profiles|Some options are unavailable for LDAP accounts"
msgstr ""
msgid "Profiles|Tell us about yourself in fewer than 250 characters."
msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr ""
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?" msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr "" msgstr ""
msgid "Profiles|This email will be displayed on your public profile."
msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface." msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr "" msgstr ""
msgid "Profiles|This feature is experimental and translations are not complete yet."
msgstr ""
msgid "Profiles|This information will appear on your profile."
msgstr ""
msgid "Profiles|Type your %{confirmationValue} to confirm:" msgid "Profiles|Type your %{confirmationValue} to confirm:"
msgstr "" msgstr ""
msgid "Profiles|Typically starts with \"ssh-rsa …\"" msgid "Profiles|Typically starts with \"ssh-rsa …\""
msgstr "" msgstr ""
msgid "Profiles|Update profile settings"
msgstr ""
msgid "Profiles|Update username" msgid "Profiles|Update username"
msgstr "" msgstr ""
msgid "Profiles|Upload new avatar"
msgstr ""
msgid "Profiles|Username change failed - %{message}" msgid "Profiles|Username change failed - %{message}"
msgstr "" msgstr ""
msgid "Profiles|Username successfully changed" msgid "Profiles|Username successfully changed"
msgstr "" msgstr ""
msgid "Profiles|Website"
msgstr ""
msgid "Profiles|What's your status?" msgid "Profiles|What's your status?"
msgstr "" msgstr ""
msgid "Profiles|You can change your avatar here"
msgstr ""
msgid "Profiles|You can change your avatar here or remove the current avatar to revert to %{gravatar_link}"
msgstr ""
msgid "Profiles|You can upload your avatar here"
msgstr ""
msgid "Profiles|You can upload your avatar here or change it at %{gravatar_link}"
msgstr ""
msgid "Profiles|You don't have access to delete this user." msgid "Profiles|You don't have access to delete this user."
msgstr "" msgstr ""
...@@ -4543,6 +4636,15 @@ msgstr "" ...@@ -4543,6 +4636,15 @@ msgstr ""
msgid "Profiles|Your account is currently an owner in these groups:" msgid "Profiles|Your account is currently an owner in these groups:"
msgstr "" msgstr ""
msgid "Profiles|Your email address was automatically set based on your %{provider_label} account."
msgstr ""
msgid "Profiles|Your location was automatically set based on your %{provider_label} account."
msgstr ""
msgid "Profiles|Your name was automatically set based on your %{provider_label} account, so people you know can recognize you."
msgstr ""
msgid "Profiles|Your status" msgid "Profiles|Your status"
msgstr "" msgstr ""
...@@ -6310,9 +6412,6 @@ msgstr "" ...@@ -6310,9 +6412,6 @@ msgstr ""
msgid "Upload file" msgid "Upload file"
msgstr "" msgstr ""
msgid "Upload new avatar"
msgstr ""
msgid "UploadLink|click to upload" msgid "UploadLink|click to upload"
msgstr "" msgstr ""
...@@ -6358,9 +6457,6 @@ msgstr "" ...@@ -6358,9 +6457,6 @@ msgstr ""
msgid "Users" msgid "Users"
msgstr "" msgstr ""
msgid "User|Current status"
msgstr ""
msgid "Variables" msgid "Variables"
msgstr "" msgstr ""
......
...@@ -95,6 +95,7 @@ describe 'bin/changelog' do ...@@ -95,6 +95,7 @@ describe 'bin/changelog' do
it 'shows error message and exits the program' do it 'shows error message and exits the program' do
allow($stdin).to receive(:getc).and_return(type) allow($stdin).to receive(:getc).and_return(type)
expect do expect do
expect { described_class.read_type }.to raise_error( expect { described_class.read_type }.to raise_error(
ChangelogHelpers::Abort, ChangelogHelpers::Abort,
......
...@@ -8,6 +8,7 @@ describe ContributedProjectsFinder do ...@@ -8,6 +8,7 @@ describe ContributedProjectsFinder do
let!(:public_project) { create(:project, :public) } let!(:public_project) { create(:project, :public) }
let!(:private_project) { create(:project, :private) } let!(:private_project) { create(:project, :private) }
let!(:internal_project) { create(:project, :internal) }
before do before do
private_project.add_maintainer(source_user) private_project.add_maintainer(source_user)
...@@ -16,17 +17,18 @@ describe ContributedProjectsFinder do ...@@ -16,17 +17,18 @@ describe ContributedProjectsFinder do
create(:push_event, project: public_project, author: source_user) create(:push_event, project: public_project, author: source_user)
create(:push_event, project: private_project, author: source_user) create(:push_event, project: private_project, author: source_user)
create(:push_event, project: internal_project, author: source_user)
end end
describe 'without a current user' do describe 'activity without a current user' do
subject { finder.execute } subject { finder.execute }
it { is_expected.to eq([public_project]) } it { is_expected.to match_array([public_project]) }
end end
describe 'with a current user' do describe 'activity with a current user' do
subject { finder.execute(current_user) } subject { finder.execute(current_user) }
it { is_expected.to eq([private_project, public_project]) } it { is_expected.to match_array([private_project, internal_project, public_project]) }
end end
end end
...@@ -13,49 +13,25 @@ describe UserRecentEventsFinder do ...@@ -13,49 +13,25 @@ describe UserRecentEventsFinder do
subject(:finder) { described_class.new(current_user, project_owner) } subject(:finder) { described_class.new(current_user, project_owner) }
describe '#execute' do describe '#execute' do
context 'current user does not have access to projects' do context 'when profile is public' do
it 'returns public and internal events' do it 'returns all the events' do
records = finder.execute expect(finder.execute).to include(private_event, internal_event, public_event)
expect(records).to include(public_event, internal_event)
expect(records).not_to include(private_event)
end end
end end
context 'when current user has access to the projects' do context 'when profile is private' do
before do it 'returns no event' do
private_project.add_developer(current_user) allow(Ability).to receive(:allowed?).and_call_original
internal_project.add_developer(current_user) allow(Ability).to receive(:allowed?).with(current_user, :read_user_profile, project_owner).and_return(false)
public_project.add_developer(current_user)
end
context 'when profile is public' do
it 'returns all the events' do
expect(finder.execute).to include(private_event, internal_event, public_event)
end
end
context 'when profile is private' do
it 'returns no event' do
allow(Ability).to receive(:allowed?).and_call_original
allow(Ability).to receive(:allowed?).with(current_user, :read_user_profile, project_owner).and_return(false)
expect(finder.execute).to be_empty
end
end
it 'does not include the events if the user cannot read cross project' do
expect(Ability).to receive(:allowed?).and_call_original
expect(Ability).to receive(:allowed?).with(current_user, :read_cross_project) { false }
expect(finder.execute).to be_empty expect(finder.execute).to be_empty
end end
end end
context 'when current user is anonymous' do it 'does not include the events if the user cannot read cross project' do
let(:current_user) { nil } expect(Ability).to receive(:allowed?).and_call_original
expect(Ability).to receive(:allowed?).with(current_user, :read_cross_project) { false }
it 'returns public events only' do expect(finder.execute).to be_empty
expect(finder.execute).to eq([public_event])
end
end end
end end
end end
...@@ -189,9 +189,9 @@ describe API::Helpers::Pagination do ...@@ -189,9 +189,9 @@ describe API::Helpers::Pagination do
it 'it returns the right link to the next page' do it 'it returns the right link to the next page' do
allow(subject).to receive(:params) allow(subject).to receive(:params)
.and_return({ pagination: 'keyset', ks_prev_id: projects[3].id, ks_prev_name: projects[3].name, per_page: 2 }) .and_return({ pagination: 'keyset', ks_prev_id: projects[3].id, ks_prev_name: projects[3].name, per_page: 2 })
expect_header('X-Per-Page', '2') expect_header('X-Per-Page', '2')
expect_header('X-Next-Page', "#{value}?ks_prev_id=#{projects[6].id}&ks_prev_name=#{projects[6].name}&pagination=keyset&per_page=2") expect_header('X-Next-Page', "#{value}?ks_prev_id=#{projects[6].id}&ks_prev_name=#{projects[6].name}&pagination=keyset&per_page=2")
expect_header('Link', anything) do |_key, val| expect_header('Link', anything) do |_key, val|
expect(val).to include('rel="next"') expect(val).to include('rel="next"')
end end
......
...@@ -7,6 +7,7 @@ describe Forever do ...@@ -7,6 +7,7 @@ describe Forever do
context 'when using PostgreSQL' do context 'when using PostgreSQL' do
it 'should return Postgresql future date' do it 'should return Postgresql future date' do
allow(Gitlab::Database).to receive(:postgresql?).and_return(true) allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
expect(subject).to eq(described_class::POSTGRESQL_DATE) expect(subject).to eq(described_class::POSTGRESQL_DATE)
end end
end end
...@@ -14,6 +15,7 @@ describe Forever do ...@@ -14,6 +15,7 @@ describe Forever do
context 'when using MySQL' do context 'when using MySQL' do
it 'should return MySQL future date' do it 'should return MySQL future date' do
allow(Gitlab::Database).to receive(:postgresql?).and_return(false) allow(Gitlab::Database).to receive(:postgresql?).and_return(false)
expect(subject).to eq(described_class::MYSQL_DATE) expect(subject).to eq(described_class::MYSQL_DATE)
end end
end end
......
...@@ -62,13 +62,16 @@ describe Gitlab::ContributionsCalendar do ...@@ -62,13 +62,16 @@ describe Gitlab::ContributionsCalendar do
expect(calendar.activity_dates).to eq(last_week => 2, today => 1) expect(calendar.activity_dates).to eq(last_week => 2, today => 1)
end end
it "only shows private events to authorized users" do context "when the user has opted-in for private contributions" do
create_event(private_project, today) it "shows private and public events to all users" do
create_event(feature_project, today) user.update_column(:include_private_contributions, true)
create_event(private_project, today)
create_event(public_project, today)
expect(calendar.activity_dates[today]).to eq(0) expect(calendar.activity_dates[today]).to eq(1)
expect(calendar(user).activity_dates[today]).to eq(0) expect(calendar(user).activity_dates[today]).to eq(1)
expect(calendar(contributor).activity_dates[today]).to eq(2) expect(calendar(contributor).activity_dates[today]).to eq(2)
end
end end
it "counts the diff notes on merge request" do it "counts the diff notes on merge request" do
...@@ -128,7 +131,7 @@ describe Gitlab::ContributionsCalendar do ...@@ -128,7 +131,7 @@ describe Gitlab::ContributionsCalendar do
e3 = create_event(feature_project, today) e3 = create_event(feature_project, today)
create_event(public_project, last_week) create_event(public_project, last_week)
expect(calendar.events_by_date(today)).to contain_exactly(e1) expect(calendar.events_by_date(today)).to contain_exactly(e1, e3)
expect(calendar(contributor).events_by_date(today)).to contain_exactly(e1, e2, e3) expect(calendar(contributor).events_by_date(today)).to contain_exactly(e1, e2, e3)
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