Merge branch 'bvl-user-status-message-35463' into 'master'
Allow users to set a status Closes #35463 See merge request gitlab-org/gitlab-ce!20614
This commit is contained in:
		
						commit
						83a0db0c55
					
				| 
						 | 
				
			
			@ -74,6 +74,9 @@ export default {
 | 
			
		|||
    </div>
 | 
			
		||||
    <a :href="author.path">
 | 
			
		||||
      <span class="note-header-author-name">{{ author.name }}</span>
 | 
			
		||||
      <span
 | 
			
		||||
        v-if="author.status_tooltip_html"
 | 
			
		||||
        v-html="author.status_tooltip_html"></span>
 | 
			
		||||
      <span class="note-headline-light">
 | 
			
		||||
        @{{ author.username }}
 | 
			
		||||
      </span>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -113,6 +113,9 @@ export default {
 | 
			
		|||
 | 
			
		||||
          {{ user.name }}
 | 
			
		||||
        </a>
 | 
			
		||||
        <span
 | 
			
		||||
          v-if="user.status_tooltip_html"
 | 
			
		||||
          v-html="user.status_tooltip_html"></span>
 | 
			
		||||
      </template>
 | 
			
		||||
    </section>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,10 +2,18 @@ module MembersPresentation
 | 
			
		|||
  extend ActiveSupport::Concern
 | 
			
		||||
 | 
			
		||||
  def present_members(members)
 | 
			
		||||
    preload_associations(members)
 | 
			
		||||
    Gitlab::View::Presenter::Factory.new(
 | 
			
		||||
      members,
 | 
			
		||||
      current_user: current_user,
 | 
			
		||||
      presenter_class: MembersPresenter
 | 
			
		||||
    ).fabricate!
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def preload_associations(members)
 | 
			
		||||
    ActiveRecord::Associations::Preloader.new.preload(members, :user)
 | 
			
		||||
    ActiveRecord::Associations::Preloader.new.preload(members, :source)
 | 
			
		||||
    ActiveRecord::Associations::Preloader.new.preload(members.map(&:user), :status)
 | 
			
		||||
    ActiveRecord::Associations::Preloader.new.preload(members.map(&:user), :u2f_registrations)
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,5 @@
 | 
			
		|||
module MembershipActions
 | 
			
		||||
  include MembersPresentation
 | 
			
		||||
  extend ActiveSupport::Concern
 | 
			
		||||
 | 
			
		||||
  def create
 | 
			
		||||
| 
						 | 
				
			
			@ -20,6 +21,7 @@ module MembershipActions
 | 
			
		|||
      .execute(member)
 | 
			
		||||
      .present(current_user: current_user)
 | 
			
		||||
 | 
			
		||||
    present_members([member])
 | 
			
		||||
    respond_to do |format|
 | 
			
		||||
      format.js { render 'shared/members/update', locals: { member: member } }
 | 
			
		||||
    end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -41,7 +41,7 @@ module NotesActions
 | 
			
		|||
    @note = Notes::CreateService.new(note_project, current_user, create_params).execute
 | 
			
		||||
 | 
			
		||||
    if @note.is_a?(Note)
 | 
			
		||||
      Notes::RenderService.new(current_user).execute([@note])
 | 
			
		||||
      prepare_notes_for_rendering([@note], noteable)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    respond_to do |format|
 | 
			
		||||
| 
						 | 
				
			
			@ -56,7 +56,7 @@ module NotesActions
 | 
			
		|||
    @note = Notes::UpdateService.new(project, current_user, note_params).execute(note)
 | 
			
		||||
 | 
			
		||||
    if @note.is_a?(Note)
 | 
			
		||||
      Notes::RenderService.new(current_user).execute([@note])
 | 
			
		||||
      prepare_notes_for_rendering([@note])
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    respond_to do |format|
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,6 +4,7 @@ module RendersNotes
 | 
			
		|||
    preload_noteable_for_regular_notes(notes)
 | 
			
		||||
    preload_max_access_for_authors(notes, @project)
 | 
			
		||||
    preload_first_time_contribution_for_authors(noteable, notes)
 | 
			
		||||
    preload_author_status(notes)
 | 
			
		||||
    Notes::RenderService.new(current_user).execute(notes)
 | 
			
		||||
 | 
			
		||||
    notes
 | 
			
		||||
| 
						 | 
				
			
			@ -28,4 +29,8 @@ module RendersNotes
 | 
			
		|||
 | 
			
		||||
    notes.each {|n| n.specialize_for_first_contribution!(noteable)}
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def preload_author_status(notes)
 | 
			
		||||
    ActiveRecord::Associations::Preloader.new.preload(notes, { author: :status })
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,7 +30,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
 | 
			
		|||
    end
 | 
			
		||||
 | 
			
		||||
    @members = @members.page(params[:page]).per(50)
 | 
			
		||||
    @members = present_members(@members.includes(:user))
 | 
			
		||||
    @members = present_members(@members)
 | 
			
		||||
 | 
			
		||||
    @requesters = present_members(
 | 
			
		||||
      AccessRequestsFinder.new(@group).execute(current_user))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -100,7 +100,8 @@ class ProfilesController < Profiles::ApplicationController
 | 
			
		|||
      :website_url,
 | 
			
		||||
      :organization,
 | 
			
		||||
      :preferred_language,
 | 
			
		||||
      :private_profile
 | 
			
		||||
      :private_profile,
 | 
			
		||||
      status: [:emoji, :message]
 | 
			
		||||
    )
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,7 +22,9 @@ class Projects::CommitController < Projects::ApplicationController
 | 
			
		|||
    apply_diff_view_cookie!
 | 
			
		||||
 | 
			
		||||
    respond_to do |format|
 | 
			
		||||
      format.html  { render }
 | 
			
		||||
      format.html  do
 | 
			
		||||
        render
 | 
			
		||||
      end
 | 
			
		||||
      format.diff  do
 | 
			
		||||
        send_git_diff(@project.repository, @commit.diff_refs)
 | 
			
		||||
      end
 | 
			
		||||
| 
						 | 
				
			
			@ -124,7 +126,10 @@ class Projects::CommitController < Projects::ApplicationController
 | 
			
		|||
  end
 | 
			
		||||
 | 
			
		||||
  def commit
 | 
			
		||||
    @noteable = @commit ||= @project.commit_by(oid: params[:id])
 | 
			
		||||
    @noteable = @commit ||= @project.commit_by(oid: params[:id]).tap do |commit|
 | 
			
		||||
      # preload author and their status for rendering
 | 
			
		||||
      commit&.author&.status
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def define_commit_vars
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -165,7 +165,7 @@ class Projects::IssuesController < Projects::ApplicationController
 | 
			
		|||
    return @issue if defined?(@issue)
 | 
			
		||||
 | 
			
		||||
    # The Sortable default scope causes performance issues when used with find_by
 | 
			
		||||
    @issuable = @noteable = @issue ||= @project.issues.where(iid: params[:id]).reorder(nil).take!
 | 
			
		||||
    @issuable = @noteable = @issue ||= @project.issues.includes(author: :status).where(iid: params[:id]).reorder(nil).take!
 | 
			
		||||
    @note = @project.notes.new(noteable: @issuable)
 | 
			
		||||
 | 
			
		||||
    return render_404 unless can?(current_user, :read_issue, @issue)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,7 +6,7 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
 | 
			
		|||
  private
 | 
			
		||||
 | 
			
		||||
  def merge_request
 | 
			
		||||
    @issuable = @merge_request ||= @project.merge_requests.find_by!(iid: params[:id])
 | 
			
		||||
    @issuable = @merge_request ||= @project.merge_requests.includes(author: :status).find_by!(iid: params[:id])
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def merge_request_params
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,5 @@
 | 
			
		|||
class Projects::NotesController < Projects::ApplicationController
 | 
			
		||||
  include RendersNotes
 | 
			
		||||
  include NotesActions
 | 
			
		||||
  include NotesHelper
 | 
			
		||||
  include ToggleAwardEmoji
 | 
			
		||||
| 
						 | 
				
			
			@ -53,7 +54,7 @@ class Projects::NotesController < Projects::ApplicationController
 | 
			
		|||
  private
 | 
			
		||||
 | 
			
		||||
  def render_json_with_notes_serializer
 | 
			
		||||
    Notes::RenderService.new(current_user).execute([note])
 | 
			
		||||
    prepare_notes_for_rendering([note])
 | 
			
		||||
 | 
			
		||||
    render json: note_serializer.represent(note)
 | 
			
		||||
  end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -161,7 +161,11 @@ class Projects::PipelinesController < Projects::ApplicationController
 | 
			
		|||
  end
 | 
			
		||||
 | 
			
		||||
  def pipeline
 | 
			
		||||
    @pipeline ||= project.pipelines.find_by!(id: params[:id]).present(current_user: current_user)
 | 
			
		||||
    @pipeline ||= project
 | 
			
		||||
                    .pipelines
 | 
			
		||||
                    .includes(user: :status)
 | 
			
		||||
                    .find_by!(id: params[:id])
 | 
			
		||||
                    .present(current_user: current_user)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def commit
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -88,7 +88,7 @@ class Projects::SnippetsController < Projects::ApplicationController
 | 
			
		|||
  protected
 | 
			
		||||
 | 
			
		||||
  def snippet
 | 
			
		||||
    @snippet ||= @project.snippets.find(params[:id])
 | 
			
		||||
    @snippet ||= @project.snippets.inc_relations_for_view.find(params[:id])
 | 
			
		||||
  end
 | 
			
		||||
  alias_method :awardable, :snippet
 | 
			
		||||
  alias_method :spammable, :snippet
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,7 +9,7 @@ class Snippets::NotesController < ApplicationController
 | 
			
		|||
  private
 | 
			
		||||
 | 
			
		||||
  def note
 | 
			
		||||
    @note ||= snippet.notes.find(params[:id])
 | 
			
		||||
    @note ||= snippet.notes.inc_relations_for_view.find(params[:id])
 | 
			
		||||
  end
 | 
			
		||||
  alias_method :awardable, :note
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -95,7 +95,7 @@ class SnippetsController < ApplicationController
 | 
			
		|||
  protected
 | 
			
		||||
 | 
			
		||||
  def snippet
 | 
			
		||||
    @snippet ||= PersonalSnippet.find_by(id: params[:id])
 | 
			
		||||
    @snippet ||= PersonalSnippet.inc_relations_for_view.find_by(id: params[:id])
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  alias_method :awardable, :snippet
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -159,6 +159,12 @@ module IssuablesHelper
 | 
			
		|||
    output << content_tag(:strong) do
 | 
			
		||||
      author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "d-none d-sm-inline", tooltip: true)
 | 
			
		||||
      author_output << link_to_member(project, issuable.author, size: 24, by_username: true, avatar: false, mobile_classes: "d-block d-sm-none")
 | 
			
		||||
 | 
			
		||||
      if status = user_status(issuable.author)
 | 
			
		||||
        author_output << "  #{status}".html_safe
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      author_output
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    output << " ".html_safe
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,4 +9,8 @@ module ProfilesHelper
 | 
			
		|||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def show_user_status_field?
 | 
			
		||||
    Feature.enabled?(:user_status_form) || cookies[:feature_user_status_form] == 'true'
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -39,6 +39,24 @@ module UsersHelper
 | 
			
		|||
    "access:#{max_project_member_access(project)}"
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def user_status(user)
 | 
			
		||||
    return unless user
 | 
			
		||||
 | 
			
		||||
    unless user.association(:status).loaded?
 | 
			
		||||
      exception = RuntimeError.new("Status was not preloaded")
 | 
			
		||||
      Gitlab::Sentry.track_exception(exception, extra: { user: user.inspect })
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    return unless user.status
 | 
			
		||||
 | 
			
		||||
    content_tag :span,
 | 
			
		||||
                class: 'user-status-emoji has-tooltip',
 | 
			
		||||
                title: user.status.message_html,
 | 
			
		||||
                data: { html: true, placement: 'top' } do
 | 
			
		||||
      emoji_icon user.status.emoji
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  private
 | 
			
		||||
 | 
			
		||||
  def get_profile_tabs
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -101,7 +101,7 @@ class Note < ActiveRecord::Base
 | 
			
		|||
  scope :inc_author_project, -> { includes(:project, :author) }
 | 
			
		||||
  scope :inc_author, -> { includes(:author) }
 | 
			
		||||
  scope :inc_relations_for_view, -> do
 | 
			
		||||
    includes(:project, :author, :updated_by, :resolved_by, :award_emoji,
 | 
			
		||||
    includes(:project, { author: :status }, :updated_by, :resolved_by, :award_emoji,
 | 
			
		||||
             :system_note_metadata, :note_diff_file)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -49,6 +49,7 @@ class Snippet < ActiveRecord::Base
 | 
			
		|||
  scope :are_public, -> { where(visibility_level: Snippet::PUBLIC) }
 | 
			
		||||
  scope :public_and_internal, -> { where(visibility_level: [Snippet::PUBLIC, Snippet::INTERNAL]) }
 | 
			
		||||
  scope :fresh,   -> { order("created_at DESC") }
 | 
			
		||||
  scope :inc_relations_for_view, -> { includes(author: :status) }
 | 
			
		||||
 | 
			
		||||
  participant :author
 | 
			
		||||
  participant :notes_with_associations
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -141,6 +141,8 @@ class User < ActiveRecord::Base
 | 
			
		|||
  has_many :term_agreements
 | 
			
		||||
  belongs_to :accepted_term, class_name: 'ApplicationSetting::Term'
 | 
			
		||||
 | 
			
		||||
  has_one :status, class_name: 'UserStatus'
 | 
			
		||||
 | 
			
		||||
  #
 | 
			
		||||
  # Validations
 | 
			
		||||
  #
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,17 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
class UserStatus < ActiveRecord::Base
 | 
			
		||||
  include CacheMarkdownField
 | 
			
		||||
 | 
			
		||||
  self.primary_key = :user_id
 | 
			
		||||
 | 
			
		||||
  DEFAULT_EMOJI = 'speech_balloon'.freeze
 | 
			
		||||
 | 
			
		||||
  belongs_to :user
 | 
			
		||||
 | 
			
		||||
  validates :user, presence: true
 | 
			
		||||
  validates :emoji, inclusion: { in: Gitlab::Emoji.emojis_names }
 | 
			
		||||
  validates :message, length: { maximum: 100 }, allow_blank: true
 | 
			
		||||
 | 
			
		||||
  cache_markdown_field :message, pipeline: :emoji
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -16,6 +16,7 @@ class UserPolicy < BasePolicy
 | 
			
		|||
  rule { ~subject_ghost & (user_is_self | admin) }.policy do
 | 
			
		||||
    enable :destroy_user
 | 
			
		||||
    enable :update_user
 | 
			
		||||
    enable :update_user_status
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  rule { default }.enable :read_user_profile
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,19 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
module UserStatusTooltip
 | 
			
		||||
  extend ActiveSupport::Concern
 | 
			
		||||
  include ActionView::Helpers::TagHelper
 | 
			
		||||
  include ActionView::Context
 | 
			
		||||
  include EmojiHelper
 | 
			
		||||
  include UsersHelper
 | 
			
		||||
 | 
			
		||||
  included do
 | 
			
		||||
    expose :user_status_if_loaded, as: :status_tooltip_html
 | 
			
		||||
 | 
			
		||||
    def user_status_if_loaded
 | 
			
		||||
      return nil unless object.association(:status).loaded?
 | 
			
		||||
 | 
			
		||||
      user_status(object)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -2,6 +2,7 @@
 | 
			
		|||
 | 
			
		||||
class UserEntity < API::Entities::UserBasic
 | 
			
		||||
  include RequestAwareEntity
 | 
			
		||||
  include UserStatusTooltip
 | 
			
		||||
 | 
			
		||||
  expose :path do |user|
 | 
			
		||||
    user_path(user)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,39 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
module Users
 | 
			
		||||
  class SetStatusService
 | 
			
		||||
    include Gitlab::Allowable
 | 
			
		||||
 | 
			
		||||
    attr_reader :current_user, :target_user, :params
 | 
			
		||||
 | 
			
		||||
    def initialize(current_user, params)
 | 
			
		||||
      @current_user, @params = current_user, params.dup
 | 
			
		||||
      @target_user = params.delete(:user) || current_user
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def execute
 | 
			
		||||
      return false unless can?(current_user, :update_user_status, target_user)
 | 
			
		||||
 | 
			
		||||
      if params[:emoji].present? || params[:message].present?
 | 
			
		||||
        set_status
 | 
			
		||||
      else
 | 
			
		||||
        remove_status
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    private
 | 
			
		||||
 | 
			
		||||
    def set_status
 | 
			
		||||
      params[:emoji] = UserStatus::DEFAULT_EMOJI if params[:emoji].blank?
 | 
			
		||||
      user_status.update(params)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def remove_status
 | 
			
		||||
      UserStatus.delete(target_user.id)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def user_status
 | 
			
		||||
      target_user.status || target_user.build_status
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -7,6 +7,7 @@ module Users
 | 
			
		|||
    def initialize(current_user, params = {})
 | 
			
		||||
      @current_user = current_user
 | 
			
		||||
      @user = params.delete(:user)
 | 
			
		||||
      @status_params = params.delete(:status)
 | 
			
		||||
      @params = params.dup
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -17,10 +18,11 @@ module Users
 | 
			
		|||
 | 
			
		||||
      assign_attributes(&block)
 | 
			
		||||
 | 
			
		||||
      if @user.save(validate: validate)
 | 
			
		||||
      if @user.save(validate: validate) && update_status
 | 
			
		||||
        notify_success(user_exists)
 | 
			
		||||
      else
 | 
			
		||||
        error(@user.errors.full_messages.uniq.join('. '))
 | 
			
		||||
        messages = @user.errors.full_messages + Array(@user.status&.errors&.full_messages)
 | 
			
		||||
        error(messages.uniq.join('. '))
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -34,6 +36,12 @@ module Users
 | 
			
		|||
 | 
			
		||||
    private
 | 
			
		||||
 | 
			
		||||
    def update_status
 | 
			
		||||
      return true unless @status_params
 | 
			
		||||
 | 
			
		||||
      Users::SetStatusService.new(current_user, @status_params.merge(user: @user)).execute
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def notify_success(user_exists)
 | 
			
		||||
      notify_new_user(@user, nil) unless user_exists
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,6 +30,18 @@
 | 
			
		|||
      - if @user.avatar?
 | 
			
		||||
        %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'
 | 
			
		||||
 | 
			
		||||
  - if show_user_status_field?
 | 
			
		||||
    %hr
 | 
			
		||||
    .row
 | 
			
		||||
      .col-lg-4.profile-settings-sidebar
 | 
			
		||||
        %h4.prepend-top-0= s_("User|Current Status")
 | 
			
		||||
        %p= s_("Profiles|This emoji and message will appear on your profile and throughout the interface. The message can contain emoji codes, too.")
 | 
			
		||||
      .col-lg-8
 | 
			
		||||
        .row
 | 
			
		||||
          = f.fields_for :status, @user.status do |status_form|
 | 
			
		||||
            = status_form.text_field :emoji
 | 
			
		||||
            = status_form.text_field :message, maxlength: 100
 | 
			
		||||
  %hr
 | 
			
		||||
  .row
 | 
			
		||||
    .col-lg-4.profile-settings-sidebar
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,6 +13,7 @@
 | 
			
		|||
    = author_avatar(@commit, size: 24, has_tooltip: false)
 | 
			
		||||
    %strong
 | 
			
		||||
      = commit_author_link(@commit, avatar: true, size: 24)
 | 
			
		||||
    = user_status(@commit.author)
 | 
			
		||||
    - if @commit.different_committer?
 | 
			
		||||
      %span.light= _('Committed by')
 | 
			
		||||
      %strong
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,6 +11,7 @@
 | 
			
		|||
      = image_tag avatar_icon_for_user(user, 40), class: "avatar s40", alt: ''
 | 
			
		||||
      .user-info
 | 
			
		||||
        = link_to user.name, user_path(user), class: 'member'
 | 
			
		||||
        = user_status(user)
 | 
			
		||||
        %span.cgray= user.to_reference
 | 
			
		||||
 | 
			
		||||
        - if user == current_user
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -31,7 +31,9 @@
 | 
			
		|||
      .note-header
 | 
			
		||||
        .note-header-info
 | 
			
		||||
          %a{ href: user_path(note.author) }
 | 
			
		||||
            %span.note-header-author-name= sanitize(note.author.name)
 | 
			
		||||
            %span.note-header-author-name
 | 
			
		||||
              = sanitize(note.author.name)
 | 
			
		||||
            = user_status(note.author)
 | 
			
		||||
            %span.note-headline-light
 | 
			
		||||
              = note.author.to_reference
 | 
			
		||||
          %span.note-headline-light
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,6 +8,7 @@
 | 
			
		|||
      Authored
 | 
			
		||||
      = time_ago_with_tooltip(@snippet.created_at, placement: 'bottom', html_class: 'snippet_updated_ago')
 | 
			
		||||
      by #{link_to_member(@project, @snippet.author, size: 24, author_class: "author item-title", avatar_class: "d-none d-sm-inline")}
 | 
			
		||||
      = user_status(@snippet.author)
 | 
			
		||||
 | 
			
		||||
  .detail-page-header-actions
 | 
			
		||||
    - if @snippet.project_id?
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -40,6 +40,11 @@
 | 
			
		|||
        .cover-title
 | 
			
		||||
          = @user.name
 | 
			
		||||
 | 
			
		||||
        - if @user.status
 | 
			
		||||
          .cover-status
 | 
			
		||||
            = emoji_icon(@user.status.emoji)
 | 
			
		||||
            = markdown_field(@user.status, :message)
 | 
			
		||||
 | 
			
		||||
        .cover-desc.member-date
 | 
			
		||||
          %p
 | 
			
		||||
            %span.middle-dot-divider
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,5 @@
 | 
			
		|||
---
 | 
			
		||||
title: Users can set a status message and emoji
 | 
			
		||||
merge_request: 20614
 | 
			
		||||
author: niedermyer & davamr
 | 
			
		||||
type: added
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,20 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
class CreateUserStatuses < ActiveRecord::Migration
 | 
			
		||||
  include Gitlab::Database::MigrationHelpers
 | 
			
		||||
 | 
			
		||||
  DOWNTIME = false
 | 
			
		||||
 | 
			
		||||
  def change
 | 
			
		||||
    create_table :user_statuses, id: false, primary_key: :user_id do |t|
 | 
			
		||||
      t.references :user,
 | 
			
		||||
                   foreign_key: { on_delete: :cascade },
 | 
			
		||||
                   null: false,
 | 
			
		||||
                   primary_key: true
 | 
			
		||||
      t.integer :cached_markdown_version, limit: 4
 | 
			
		||||
      t.string :emoji, null: false, default: 'speech_balloon'
 | 
			
		||||
      t.string :message, limit: 100
 | 
			
		||||
      t.string :message_html
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -2069,6 +2069,13 @@ ActiveRecord::Schema.define(version: 20180726172057) do
 | 
			
		|||
  add_index "user_interacted_projects", ["project_id", "user_id"], name: "index_user_interacted_projects_on_project_id_and_user_id", unique: true, using: :btree
 | 
			
		||||
  add_index "user_interacted_projects", ["user_id"], name: "index_user_interacted_projects_on_user_id", using: :btree
 | 
			
		||||
 | 
			
		||||
  create_table "user_statuses", primary_key: "user_id", force: :cascade do |t|
 | 
			
		||||
    t.integer "cached_markdown_version"
 | 
			
		||||
    t.string "emoji", default: "speech_balloon", null: false
 | 
			
		||||
    t.string "message", limit: 100
 | 
			
		||||
    t.string "message_html"
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  create_table "user_synced_attributes_metadata", force: :cascade do |t|
 | 
			
		||||
    t.boolean "name_synced", default: false
 | 
			
		||||
    t.boolean "email_synced", default: false
 | 
			
		||||
| 
						 | 
				
			
			@ -2374,6 +2381,7 @@ ActiveRecord::Schema.define(version: 20180726172057) do
 | 
			
		|||
  add_foreign_key "user_custom_attributes", "users", on_delete: :cascade
 | 
			
		||||
  add_foreign_key "user_interacted_projects", "projects", name: "fk_722ceba4f7", on_delete: :cascade
 | 
			
		||||
  add_foreign_key "user_interacted_projects", "users", name: "fk_0894651f08", on_delete: :cascade
 | 
			
		||||
  add_foreign_key "user_statuses", "users", on_delete: :cascade
 | 
			
		||||
  add_foreign_key "user_synced_attributes_metadata", "users", on_delete: :cascade
 | 
			
		||||
  add_foreign_key "users", "application_setting_terms", column: "accepted_term_id", name: "fk_789cd90b35", on_delete: :cascade
 | 
			
		||||
  add_foreign_key "users_star_projects", "projects", name: "fk_22cd27ddfc", on_delete: :cascade
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -440,6 +440,83 @@ GET /user
 | 
			
		|||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## User status
 | 
			
		||||
 | 
			
		||||
Get the status of the currently signed in user.
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
GET /user/status
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/user/status"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Example response:
 | 
			
		||||
 | 
			
		||||
```json
 | 
			
		||||
{
 | 
			
		||||
  "emoji":"coffee",
 | 
			
		||||
  "message":"I crave coffee :coffee:",
 | 
			
		||||
  "message_html": "I crave coffee <gl-emoji title=\"hot beverage\" data-name=\"coffee\" data-unicode-version=\"4.0\">☕</gl-emoji>"
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Get the status of a user
 | 
			
		||||
 | 
			
		||||
Get the status of a user.
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
GET /users/:id_or_username/status
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
| Attribute | Type | Required | Description |
 | 
			
		||||
| --------- | ---- | -------- | ----------- |
 | 
			
		||||
| `id_or_username` | string | yes    | The id or username of the user to get a status of |
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
curl "https://gitlab.example.com/users/janedoe/status"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Example response:
 | 
			
		||||
 | 
			
		||||
```json
 | 
			
		||||
{
 | 
			
		||||
  "emoji":"coffee",
 | 
			
		||||
  "message":"I crave coffee :coffee:",
 | 
			
		||||
  "message_html": "I crave coffee <gl-emoji title=\"hot beverage\" data-name=\"coffee\" data-unicode-version=\"4.0\">☕</gl-emoji>"
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Set user status
 | 
			
		||||
 | 
			
		||||
Set the status of the current user.
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
PUT /user/status
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
| Attribute | Type | Required | Description |
 | 
			
		||||
| --------- | ---- | -------- | ----------- |
 | 
			
		||||
| `emoji`   | string | no     | The name of the emoji to use as status, if omitted `speech_balloon` is used. Emoji name can be one of the specified names in the [Gemojione index][gemojione-index]. |
 | 
			
		||||
| `message` | string | no     | The message to set as a status. It can also contain emoji codes. |
 | 
			
		||||
 | 
			
		||||
When both parameters `emoji` and `message` are empty, the status will be cleared.
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "emoji=coffee" --data "emoji=I crave coffee" https://gitlab.example.com/api/v4/user/status
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Example responses
 | 
			
		||||
 | 
			
		||||
```json
 | 
			
		||||
{
 | 
			
		||||
  "emoji":"coffee",
 | 
			
		||||
  "message":"I crave coffee",
 | 
			
		||||
  "message_html": "I crave coffee"
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## List user projects
 | 
			
		||||
 | 
			
		||||
Please refer to the [List of user projects ](projects.md#list-user-projects).
 | 
			
		||||
| 
						 | 
				
			
			@ -1167,3 +1244,5 @@ Example response:
 | 
			
		|||
```
 | 
			
		||||
 | 
			
		||||
Please note that `last_activity_at` is deprecated, please use `last_activity_on`.
 | 
			
		||||
 | 
			
		||||
[gemojione-index]: https://github.com/jonathanwiesel/gemojione/blob/master/config/index.json
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -62,6 +62,14 @@ module API
 | 
			
		|||
      expose :admin?, as: :is_admin
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    class UserStatus < Grape::Entity
 | 
			
		||||
      expose :emoji
 | 
			
		||||
      expose :message
 | 
			
		||||
      expose :message_html do |entity|
 | 
			
		||||
        MarkupHelper.markdown_field(entity, :message)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    class Email < Grape::Entity
 | 
			
		||||
      expose :id, :email
 | 
			
		||||
    end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -121,6 +121,17 @@ module API
 | 
			
		|||
        present user, opts
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      desc "Get the status of a user"
 | 
			
		||||
      params do
 | 
			
		||||
        requires :id_or_username, type: String, desc: 'The ID or username of the user'
 | 
			
		||||
      end
 | 
			
		||||
      get ":id_or_username/status" do
 | 
			
		||||
        user = find_user(params[:id_or_username])
 | 
			
		||||
        not_found!('User') unless user && can?(current_user, :read_user, user)
 | 
			
		||||
 | 
			
		||||
        present user.status || {}, with: Entities::UserStatus
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      desc 'Create a user. Available only for admins.' do
 | 
			
		||||
        success Entities::UserPublic
 | 
			
		||||
      end
 | 
			
		||||
| 
						 | 
				
			
			@ -740,6 +751,30 @@ module API
 | 
			
		|||
 | 
			
		||||
        present paginate(activities), with: Entities::UserActivity
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      desc 'Set the status of the current user' do
 | 
			
		||||
        success Entities::UserStatus
 | 
			
		||||
      end
 | 
			
		||||
      params do
 | 
			
		||||
        optional :emoji, type: String, desc: "The emoji to set on the status"
 | 
			
		||||
        optional :message, type: String, desc: "The status message to set"
 | 
			
		||||
      end
 | 
			
		||||
      put "status" do
 | 
			
		||||
        forbidden! unless can?(current_user, :update_user_status, current_user)
 | 
			
		||||
 | 
			
		||||
        if ::Users::SetStatusService.new(current_user, declared_params).execute
 | 
			
		||||
          present current_user.status, with: Entities::UserStatus
 | 
			
		||||
        else
 | 
			
		||||
          render_validation_error!(current_user.status)
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      desc 'get the status of the current user' do
 | 
			
		||||
        success Entities::UserStatus
 | 
			
		||||
      end
 | 
			
		||||
      get 'status' do
 | 
			
		||||
        present current_user.status || {}, with: Entities::UserStatus
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,17 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
module Banzai
 | 
			
		||||
  module Pipeline
 | 
			
		||||
    class EmojiPipeline < BasePipeline
 | 
			
		||||
      # These filters will only perform sanitization of the content, preventing
 | 
			
		||||
      # XSS, and replace emoji.
 | 
			
		||||
      def self.filters
 | 
			
		||||
        @filters ||= FilterArray[
 | 
			
		||||
          Filter::HtmlEntityFilter,
 | 
			
		||||
          Filter::SanitizationFilter,
 | 
			
		||||
          Filter::EmojiFilter
 | 
			
		||||
        ]
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -4081,6 +4081,9 @@ msgstr ""
 | 
			
		|||
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface. The message can contain emoji codes, too."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
msgid "Profiles|Type your %{confirmationValue} to confirm:"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -5624,6 +5627,9 @@ msgstr ""
 | 
			
		|||
msgid "Users"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
msgid "User|Current Status"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
msgid "Variables"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -78,6 +78,15 @@ describe ProfilesController, :request_store do
 | 
			
		|||
      expect(ldap_user.name).not_to eq('John')
 | 
			
		||||
      expect(ldap_user.location).to eq('City, Country')
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'allows setting a user status' do
 | 
			
		||||
      sign_in(user)
 | 
			
		||||
 | 
			
		||||
      put :update, user: { status: { message: 'Working hard!' } }
 | 
			
		||||
 | 
			
		||||
      expect(user.reload.status.message).to eq('Working hard!')
 | 
			
		||||
      expect(response).to have_gitlab_http_status(302)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe 'PUT update_username' do
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -993,6 +993,29 @@ describe Projects::IssuesController do
 | 
			
		|||
        expect(json_response.first.keys).to match_array(%w[id reply_id expanded notes diff_discussion discussion_path individual_note resolvable resolved resolved_at resolved_by resolved_by_push commit_id for_commit project_id])
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it 'renders the author status html if there is a status' do
 | 
			
		||||
        create(:user_status, user: discussion.author)
 | 
			
		||||
 | 
			
		||||
        get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid
 | 
			
		||||
 | 
			
		||||
        note_json = json_response.first['notes'].first
 | 
			
		||||
 | 
			
		||||
        expect(note_json['author']['status_tooltip_html']).to be_present
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it 'does not cause an extra query for the status' do
 | 
			
		||||
        control = ActiveRecord::QueryRecorder.new do
 | 
			
		||||
          get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        create(:user_status, user: discussion.author)
 | 
			
		||||
        second_discussion = create(:discussion_note_on_issue, noteable: issue, project: issue.project, author: create(:user))
 | 
			
		||||
        create(:user_status, user: second_discussion.author)
 | 
			
		||||
 | 
			
		||||
        expect { get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid }
 | 
			
		||||
          .not_to exceed_query_limit(control)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      context 'with cross-reference system note', :request_store do
 | 
			
		||||
        let(:new_issue) { create(:issue) }
 | 
			
		||||
        let(:cross_reference) { "mentioned in #{new_issue.to_reference(issue.project)}" }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
FactoryBot.define do
 | 
			
		||||
  factory :user_status do
 | 
			
		||||
    user
 | 
			
		||||
    emoji 'coffee'
 | 
			
		||||
    message 'I crave coffee'
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -9,7 +9,7 @@ describe 'Groups > Members > List members' do
 | 
			
		|||
  let(:nested_group) { create(:group, parent: group) }
 | 
			
		||||
 | 
			
		||||
  before do
 | 
			
		||||
    gitlab_sign_in(user1)
 | 
			
		||||
    sign_in(user1)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  it 'show members from current group and parent', :nested_groups do
 | 
			
		||||
| 
						 | 
				
			
			@ -32,6 +32,18 @@ describe 'Groups > Members > List members' do
 | 
			
		|||
    expect(second_row).to be_blank
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe 'showing status of members' do
 | 
			
		||||
    before do
 | 
			
		||||
      group.add_developer(user2)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    subject { visit group_group_members_path(group) }
 | 
			
		||||
 | 
			
		||||
    it_behaves_like 'showing user status' do
 | 
			
		||||
      let(:user_with_status) { user2 }
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def first_row
 | 
			
		||||
    page.all('ul.content-list > li')[0]
 | 
			
		||||
  end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -55,4 +55,31 @@ describe 'User edit profile' do
 | 
			
		|||
      expect(page).to have_link('gravatar.com')
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  context 'user status' do
 | 
			
		||||
    it 'hides user status when the feature is disabled' do
 | 
			
		||||
      stub_feature_flags(user_status_form: false)
 | 
			
		||||
 | 
			
		||||
      visit(profile_path)
 | 
			
		||||
 | 
			
		||||
      expect(page).not_to have_content('Current Status')
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'shows the status form when the feature is enabled' do
 | 
			
		||||
      stub_feature_flags(user_status_form: true)
 | 
			
		||||
 | 
			
		||||
      visit(profile_path)
 | 
			
		||||
 | 
			
		||||
      expect(page).to have_content('Current Status')
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'shows the status form when the feature is enabled by setting a cookie', :js do
 | 
			
		||||
      stub_feature_flags(user_status_form: false)
 | 
			
		||||
      set_cookie('feature_user_status_form', 'true')
 | 
			
		||||
 | 
			
		||||
      visit(profile_path)
 | 
			
		||||
 | 
			
		||||
      expect(page).to have_content('Current Status')
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,40 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'spec_helper'
 | 
			
		||||
 | 
			
		||||
describe 'Project > Commit > View user status' do
 | 
			
		||||
  include RepoHelpers
 | 
			
		||||
 | 
			
		||||
  set(:project) { create(:project, :repository) }
 | 
			
		||||
  set(:user) { create(:user) }
 | 
			
		||||
  let(:commit_author) { create(:user, email: sample_commit.author_email) }
 | 
			
		||||
 | 
			
		||||
  before do
 | 
			
		||||
    sign_in(user)
 | 
			
		||||
    project.add_developer(user)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  subject { visit(project_commit_path(project, sample_commit.id)) }
 | 
			
		||||
 | 
			
		||||
  describe 'status for the commit author' do
 | 
			
		||||
    it_behaves_like 'showing user status' do
 | 
			
		||||
      let(:user_with_status) { commit_author }
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe 'status for a comment on the commit' do
 | 
			
		||||
    let(:note) { create(:note, :on_commit, project: project) }
 | 
			
		||||
 | 
			
		||||
    it_behaves_like 'showing user status' do
 | 
			
		||||
      let(:user_with_status) { note.author }
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe 'status for a diff note on the commit' do
 | 
			
		||||
    let(:note) { create(:diff_note_on_commit, project: project) }
 | 
			
		||||
 | 
			
		||||
    it_behaves_like 'showing user status' do
 | 
			
		||||
      let(:user_with_status) { note.author }
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -29,4 +29,22 @@ describe "User views issue" do
 | 
			
		|||
      expect(page).not_to have_link('Close issue')
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe 'user status' do
 | 
			
		||||
    subject { visit(project_issue_path(project, issue)) }
 | 
			
		||||
 | 
			
		||||
    describe 'showing status of the author of the issue' do
 | 
			
		||||
      it_behaves_like 'showing user status' do
 | 
			
		||||
        let(:user_with_status) { issue.author }
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    describe 'showing status of a user who commented on an issue', :js do
 | 
			
		||||
      let!(:note) { create(:note, noteable: issue, project: project, author: user_with_status) }
 | 
			
		||||
 | 
			
		||||
      it_behaves_like 'showing user status' do
 | 
			
		||||
        let(:user_with_status) { create(:user) }
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -87,4 +87,12 @@ describe 'Projects members' do
 | 
			
		|||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe 'showing status of members' do
 | 
			
		||||
    it_behaves_like 'showing user status' do
 | 
			
		||||
      let(:user_with_status) { developer }
 | 
			
		||||
 | 
			
		||||
      subject { visit project_settings_members_path(project) }
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,36 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'spec_helper'
 | 
			
		||||
 | 
			
		||||
describe 'Project > Merge request > View user status' do
 | 
			
		||||
  let(:project) { create(:project, :public, :repository) }
 | 
			
		||||
  let(:merge_request) do
 | 
			
		||||
    create(:merge_request, source_project: project, target_project: project, author: create(:user))
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  subject { visit merge_request_path(merge_request) }
 | 
			
		||||
 | 
			
		||||
  describe 'the status of the merge request author' do
 | 
			
		||||
    it_behaves_like 'showing user status' do
 | 
			
		||||
      let(:user_with_status) { merge_request.author }
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  context 'for notes', :js do
 | 
			
		||||
    describe 'the status of the author of a note on a merge request' do
 | 
			
		||||
      let(:note) { create(:note, noteable: merge_request, project: project, author: create(:user)) }
 | 
			
		||||
 | 
			
		||||
      it_behaves_like 'showing user status' do
 | 
			
		||||
        let(:user_with_status) { note.author }
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    describe 'the status of the author of a diff note on a merge request' do
 | 
			
		||||
      let(:note) { create(:diff_note_on_merge_request, noteable: merge_request, project: project, author: create(:user)) }
 | 
			
		||||
 | 
			
		||||
      it_behaves_like 'showing user status' do
 | 
			
		||||
        let(:user_with_status) { note.author }
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -63,6 +63,12 @@ describe 'Pipeline', :js do
 | 
			
		|||
      expect(page).to have_css('#js-tab-pipeline.active')
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it_behaves_like 'showing user status' do
 | 
			
		||||
      let(:user_with_status) { pipeline.user }
 | 
			
		||||
 | 
			
		||||
      subject { visit project_pipeline_path(project, pipeline) }
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    describe 'pipeline graph' do
 | 
			
		||||
      context 'when pipeline has running builds' do
 | 
			
		||||
        it 'shows a running icon and a cancel action for the running build' do
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -141,4 +141,16 @@ describe 'Projects > Snippets > Project snippet', :js do
 | 
			
		|||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  it_behaves_like 'showing user status' do
 | 
			
		||||
    let(:file_name) { 'ruby-style-guide.md' }
 | 
			
		||||
    let(:content) { project.repository.blob_at('master', 'files/markdown/ruby-style-guide.md').data }
 | 
			
		||||
 | 
			
		||||
    let(:user_with_status) { snippet.author }
 | 
			
		||||
 | 
			
		||||
    subject do
 | 
			
		||||
      visit project_snippet_path(project, snippet)
 | 
			
		||||
      wait_for_requests
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,6 +16,8 @@ describe 'Comments on personal snippets', :js do
 | 
			
		|||
  before do
 | 
			
		||||
    sign_in user
 | 
			
		||||
    visit snippet_path(snippet)
 | 
			
		||||
 | 
			
		||||
    wait_for_requests
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  subject { page }
 | 
			
		||||
| 
						 | 
				
			
			@ -42,6 +44,15 @@ describe 'Comments on personal snippets', :js do
 | 
			
		|||
        expect(page).to have_selector('.note-emoji-button')
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'shows the status of a note author' do
 | 
			
		||||
      status = create(:user_status, user: user)
 | 
			
		||||
      visit snippet_path(snippet)
 | 
			
		||||
 | 
			
		||||
      within("#note_#{snippet_notes[0].id}") do
 | 
			
		||||
        expect(page).to show_user_status(status)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  context 'when submitting a note' do
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -155,4 +155,12 @@ describe 'Snippet', :js do
 | 
			
		|||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  it_behaves_like 'showing user status' do
 | 
			
		||||
    let(:file_name) { 'popen.rb' }
 | 
			
		||||
    let(:content) { project.repository.blob_at('master', 'files/ruby/popen.rb').data }
 | 
			
		||||
    let(:user_with_status) { snippet.author }
 | 
			
		||||
 | 
			
		||||
    subject { visit snippet_path(snippet) }
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -53,6 +53,14 @@ describe 'User page' do
 | 
			
		|||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  it 'shows the status if there was one' do
 | 
			
		||||
    create(:user_status, user: user, message: "Working hard!")
 | 
			
		||||
 | 
			
		||||
    visit(user_path(user))
 | 
			
		||||
 | 
			
		||||
    expect(page).to have_content("Working hard!")
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  context 'signup disabled' do
 | 
			
		||||
    it 'shows the sign in link' do
 | 
			
		||||
      stub_application_setting(signup_enabled: false)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,21 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'spec_helper'
 | 
			
		||||
 | 
			
		||||
describe Banzai::Pipeline::EmojiPipeline do
 | 
			
		||||
  def parse(text)
 | 
			
		||||
    described_class.to_html(text, {})
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  it 'replaces emoji' do
 | 
			
		||||
    expected_result = "Hello world #{Gitlab::Emoji.gl_emoji_tag('100')}"
 | 
			
		||||
 | 
			
		||||
    expect(parse('Hello world :100:')).to eq(expected_result)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  it 'filters out HTML tags' do
 | 
			
		||||
    expected_result = "Hello <b>world</b> #{Gitlab::Emoji.gl_emoji_tag('100')}"
 | 
			
		||||
 | 
			
		||||
    expect(parse('Hello <b>world</b> :100:')).to eq(expected_result)
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -20,6 +20,7 @@ describe User do
 | 
			
		|||
 | 
			
		||||
  describe 'associations' do
 | 
			
		||||
    it { is_expected.to have_one(:namespace) }
 | 
			
		||||
    it { is_expected.to have_one(:status) }
 | 
			
		||||
    it { is_expected.to have_many(:snippets).dependent(:destroy) }
 | 
			
		||||
    it { is_expected.to have_many(:members) }
 | 
			
		||||
    it { is_expected.to have_many(:project_members) }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,20 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'spec_helper'
 | 
			
		||||
 | 
			
		||||
describe UserStatus do
 | 
			
		||||
  it { is_expected.to validate_presence_of(:user) }
 | 
			
		||||
 | 
			
		||||
  it { is_expected.to allow_value('smirk').for(:emoji) }
 | 
			
		||||
  it { is_expected.not_to allow_value('hello world').for(:emoji) }
 | 
			
		||||
  it { is_expected.not_to allow_value('').for(:emoji) }
 | 
			
		||||
 | 
			
		||||
  it { is_expected.to validate_length_of(:message).is_at_most(100) }
 | 
			
		||||
  it { is_expected.to allow_value('').for(:message) }
 | 
			
		||||
 | 
			
		||||
  it 'is expected to be deleted when the user is deleted' do
 | 
			
		||||
    status = create(:user_status)
 | 
			
		||||
 | 
			
		||||
    expect { status.user.destroy }.to change { described_class.count }.from(1).to(0)
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -35,6 +35,10 @@ describe UserPolicy do
 | 
			
		|||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe "updating a user's status" do
 | 
			
		||||
    it_behaves_like 'changing a user', :update_user_status
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe "destroying a user" do
 | 
			
		||||
    it_behaves_like 'changing a user', :destroy_user
 | 
			
		||||
  end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,6 +13,27 @@ describe API::Users do
 | 
			
		|||
  let(:not_existing_pat_id) { (PersonalAccessToken.maximum('id') || 0 ) + 10 }
 | 
			
		||||
  let(:private_user) { create(:user, private_profile: true) }
 | 
			
		||||
 | 
			
		||||
  shared_examples 'rendering user status' do
 | 
			
		||||
    it 'returns the status if there was one' do
 | 
			
		||||
      create(:user_status, user: user)
 | 
			
		||||
 | 
			
		||||
      get api(path, user)
 | 
			
		||||
 | 
			
		||||
      expect(response).to have_gitlab_http_status(:success)
 | 
			
		||||
      expect(json_response['message']).to be_present
 | 
			
		||||
      expect(json_response['message_html']).to be_present
 | 
			
		||||
      expect(json_response['emoji']).to be_present
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'returns an empty response if there was no status' do
 | 
			
		||||
      get api(path, user)
 | 
			
		||||
 | 
			
		||||
      expect(response).to have_gitlab_http_status(:success)
 | 
			
		||||
      expect(json_response['message']).to be_nil
 | 
			
		||||
      expect(json_response['emoji']).to be_nil
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe 'GET /users' do
 | 
			
		||||
    context "when unauthenticated" do
 | 
			
		||||
      it "returns authorization error when the `username` parameter is not passed" do
 | 
			
		||||
| 
						 | 
				
			
			@ -310,6 +331,20 @@ describe API::Users do
 | 
			
		|||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe 'GET /users/:id_or_username/status' do
 | 
			
		||||
    context 'when finding the user by id' do
 | 
			
		||||
      it_behaves_like 'rendering user status' do
 | 
			
		||||
        let(:path) { "/users/#{user.id}/status" }
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context 'when finding the user by username' do
 | 
			
		||||
      it_behaves_like 'rendering user status' do
 | 
			
		||||
        let(:path) { "/users/#{user.username}/status" }
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe "POST /users" do
 | 
			
		||||
    before do
 | 
			
		||||
      admin
 | 
			
		||||
| 
						 | 
				
			
			@ -1774,6 +1809,34 @@ describe API::Users do
 | 
			
		|||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe 'GET /user/status' do
 | 
			
		||||
    let(:path) { '/user/status' }
 | 
			
		||||
    it_behaves_like 'rendering user status'
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe 'PUT /user/status' do
 | 
			
		||||
    it 'saves the status' do
 | 
			
		||||
      put api('/user/status', user), { emoji: 'smirk', message: 'hello world' }
 | 
			
		||||
 | 
			
		||||
      expect(response).to have_gitlab_http_status(:success)
 | 
			
		||||
      expect(json_response['emoji']).to eq('smirk')
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'renders errors when the status was invalid' do
 | 
			
		||||
      put api('/user/status', user), { emoji: 'does not exist', message: 'hello world' }
 | 
			
		||||
 | 
			
		||||
      expect(response).to have_gitlab_http_status(400)
 | 
			
		||||
      expect(json_response['message']['emoji']).to be_present
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'deletes the status when passing empty values' do
 | 
			
		||||
      put api('/user/status', user)
 | 
			
		||||
 | 
			
		||||
      expect(response).to have_gitlab_http_status(:success)
 | 
			
		||||
      expect(user.reload.status).to be_nil
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe 'GET /users/:user_id/impersonation_tokens' do
 | 
			
		||||
    let!(:active_personal_access_token) { create(:personal_access_token, user: user) }
 | 
			
		||||
    let!(:revoked_personal_access_token) { create(:personal_access_token, :revoked, user: user) }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,58 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'spec_helper'
 | 
			
		||||
 | 
			
		||||
describe Users::SetStatusService do
 | 
			
		||||
  let(:current_user) { create(:user) }
 | 
			
		||||
  subject(:service) { described_class.new(current_user, params) }
 | 
			
		||||
 | 
			
		||||
  describe '#execute' do
 | 
			
		||||
    context 'when when params are set' do
 | 
			
		||||
      let(:params) { { emoji: 'taurus', message: 'a random status' } }
 | 
			
		||||
 | 
			
		||||
      it 'creates a status' do
 | 
			
		||||
        service.execute
 | 
			
		||||
 | 
			
		||||
        expect(current_user.status.emoji).to eq('taurus')
 | 
			
		||||
        expect(current_user.status.message).to eq('a random status')
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it 'updates a status if it already existed' do
 | 
			
		||||
        create(:user_status, user: current_user)
 | 
			
		||||
 | 
			
		||||
        expect { service.execute }.not_to change { UserStatus.count }
 | 
			
		||||
        expect(current_user.status.message).to eq('a random status')
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      context 'for another user' do
 | 
			
		||||
        let(:target_user) { create(:user) }
 | 
			
		||||
        let(:params) do
 | 
			
		||||
          { emoji: 'taurus', message: 'a random status', user: target_user }
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        context 'the current user is admin' do
 | 
			
		||||
          let(:current_user) { create(:admin) }
 | 
			
		||||
 | 
			
		||||
          it 'changes the status when the current user is allowed to do that' do
 | 
			
		||||
            expect { service.execute }.to change { target_user.status }
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        it 'does not update the status if the current user is not allowed' do
 | 
			
		||||
          expect { service.execute }.not_to change { target_user.status }
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context 'without params' do
 | 
			
		||||
      let(:params) { {} }
 | 
			
		||||
 | 
			
		||||
      it 'deletes the status' do
 | 
			
		||||
        status = create(:user_status, user: current_user)
 | 
			
		||||
 | 
			
		||||
        expect { service.execute }
 | 
			
		||||
          .to change { current_user.reload.status }.from(status).to(nil)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -30,6 +30,27 @@ describe Users::UpdateService do
 | 
			
		|||
      expect(result[:message]).to eq('Username has already been taken')
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'updates the status if status params were given' do
 | 
			
		||||
      update_user(user, status: { message: "On a call" })
 | 
			
		||||
 | 
			
		||||
      expect(user.status.message).to eq("On a call")
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'does not delete the status if no status param was passed' do
 | 
			
		||||
      create(:user_status, user: user, message: 'Busy!')
 | 
			
		||||
 | 
			
		||||
      update_user(user, name: 'New name')
 | 
			
		||||
 | 
			
		||||
      expect(user.status.message).to eq('Busy!')
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'includes status error messages' do
 | 
			
		||||
      result = update_user(user, status: { emoji: "Moo!" })
 | 
			
		||||
 | 
			
		||||
      expect(result[:status]).to eq(:error)
 | 
			
		||||
      expect(result[:message]).to eq("Emoji is not included in the list")
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def update_user(user, opts)
 | 
			
		||||
      described_class.new(user, opts.merge(user: user)).execute
 | 
			
		||||
    end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,13 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
RSpec::Matchers.define :show_user_status do |status|
 | 
			
		||||
  match do |page|
 | 
			
		||||
    expect(page).to have_selector(".user-status-emoji[title='#{status.message}']")
 | 
			
		||||
 | 
			
		||||
    # The same user status might be displayed multiple times on the page
 | 
			
		||||
    emoji_span = page.first(".user-status-emoji[title='#{status.message}']")
 | 
			
		||||
    page.within(emoji_span) do
 | 
			
		||||
      expect(page).to have_emoji(status.emoji)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
shared_examples 'showing user status' do
 | 
			
		||||
  let!(:status) { create(:user_status, user: user_with_status, emoji: 'smirk', message: 'Authoring this object') }
 | 
			
		||||
 | 
			
		||||
  it 'shows the status' do
 | 
			
		||||
    subject
 | 
			
		||||
 | 
			
		||||
    expect(page).to show_user_status(status)
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -17,6 +17,13 @@ describe 'projects/merge_requests/show.html.haml' do
 | 
			
		|||
      author: user)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def preload_view_requirements
 | 
			
		||||
    # This will load the status fields of the author of the note and merge request
 | 
			
		||||
    # to avoid queries in when rendering the view being tested.
 | 
			
		||||
    closed_merge_request.author.status
 | 
			
		||||
    note.author.status
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  before do
 | 
			
		||||
    assign(:project, project)
 | 
			
		||||
    assign(:merge_request, closed_merge_request)
 | 
			
		||||
| 
						 | 
				
			
			@ -26,6 +33,8 @@ describe 'projects/merge_requests/show.html.haml' do
 | 
			
		|||
    assign(:notes, [])
 | 
			
		||||
    assign(:pipelines, Ci::Pipeline.none)
 | 
			
		||||
 | 
			
		||||
    preload_view_requirements
 | 
			
		||||
 | 
			
		||||
    allow(view).to receive_messages(current_user: user,
 | 
			
		||||
                                    can?: true,
 | 
			
		||||
                                    current_application_settings: Gitlab::CurrentSettings.current_application_settings)
 | 
			
		||||
| 
						 | 
				
			
			@ -42,6 +51,7 @@ describe 'projects/merge_requests/show.html.haml' do
 | 
			
		|||
    it 'does not show the "Reopen" button when the source project does not exist' do
 | 
			
		||||
      unlink_project.execute
 | 
			
		||||
      closed_merge_request.reload
 | 
			
		||||
      preload_view_requirements
 | 
			
		||||
 | 
			
		||||
      render
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -56,6 +66,7 @@ describe 'projects/merge_requests/show.html.haml' do
 | 
			
		|||
      forked_project.destroy
 | 
			
		||||
      # Reload merge request so MergeRequest#source_project turns to `nil`
 | 
			
		||||
      closed_merge_request.reload
 | 
			
		||||
      preload_view_requirements
 | 
			
		||||
 | 
			
		||||
      render
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue