GitLab wurde erfolgreich aktualisiert. Durch regelmäßige Updates bleibt das THM GitLab sicher. Danke für Ihre Geduld.

Commit f1d3ea63 authored by Bob Van Landuyt's avatar Bob Van Landuyt

Show the status of a user in interactions

The status is shown for
- The author of a commit when viewing a commit
- Notes on a commit (regular/diff)
- The user that triggered a pipeline when viewing a pipeline
- The author of a merge request when viewing a merge request
- The author of notes on a merge request (regular/diff)
- The author of an issue when viewing an issue
- The author of notes on an issue
- The author of a snippet when viewing a snippet
- The author of notes on a snippet
- A user's profile page
- The list of members of a group/user
parent b4c4b48a
......@@ -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
module MembershipActions
include MembersPresentation
extend ActiveSupport::Concern
def create
......@@ -20,6 +21,7 @@ def update
.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 @@ def create
@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 @@ def update
@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 @@ def prepare_notes_for_rendering(notes, noteable = nil)
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 @@ def preload_first_time_contribution_for_authors(noteable, notes)
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 @@ def index
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))
......
......@@ -22,7 +22,9 @@ def show
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 @@ def referenced_merge_request_url
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 @@ def issue
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
......
class Projects::NotesController < Projects::ApplicationController
include RendersNotes
include NotesActions
include NotesHelper
include ToggleAwardEmoji
......@@ -53,7 +54,7 @@ def unresolve
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 @@ def create_params
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 @@ def destroy
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 @@ def destroy
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 @@ def issuable_meta(issuable, project, text)
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 << "&ensp; #{status}".html_safe
end
author_output
end
output << "&ensp;".html_safe
......
......@@ -39,6 +39,24 @@ def max_project_member_access_cache_key(project)
"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 @@ def values
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 @@ def content_html_invalidated?
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
......
......@@ -3,6 +3,10 @@
class UserStatus < ActiveRecord::Base
include CacheMarkdownField
self.primary_key = :user_id
DEFAULT_EMOJI = 'speech_balloon'.freeze
belongs_to :user
validates :user, presence: true
......
# 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)
......
......@@ -24,6 +24,7 @@ def execute
private
def set_status
params[:emoji] = UserStatus::DEFAULT_EMOJI if params[:emoji].blank?
user_status.update(params)
end
......
......@@ -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
......
---
title: Users can set a status message and emoji
merge_request: 20614
author:
type: added
......@@ -993,6 +993,29 @@ def create_merge_request
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)}" }
......
......@@ -9,7 +9,7 @@
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 @@
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
......
# 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 @@
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 @@
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
# 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 @@
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 @@
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 @@
before do
sign_in user
visit snippet_path(snippet)
wait_for_requests
end
subject { page }
......@@ -42,6 +44,15 @@
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 @@
end
end
end
it_behaves_like 'showing user status' do
let(:file_name) { 'popen.rb' }