Skip to content
Snippets Groups Projects
Commit 83a0db0c authored by Rémy Coutable's avatar Rémy Coutable
Browse files

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
parents ea6fc714 12095251
No related branches found
No related tags found
1 merge request!10495Merge Requests - Assignee
Showing
with 268 additions and 3 deletions
Loading
Loading
@@ -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
Loading
Loading
Loading
Loading
@@ -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
#
Loading
Loading
# 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
Loading
Loading
@@ -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
Loading
Loading
# 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
Loading
Loading
@@ -2,6 +2,7 @@
 
class UserEntity < API::Entities::UserBasic
include RequestAwareEntity
include UserStatusTooltip
 
expose :path do |user|
user_path(user)
Loading
Loading
# 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
Loading
Loading
@@ -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
 
Loading
Loading
@@ -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
 
Loading
Loading
@@ -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
 
Loading
Loading
Loading
Loading
@@ -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
Loading
Loading
Loading
Loading
@@ -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
Loading
Loading
Loading
Loading
@@ -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
Loading
Loading
Loading
Loading
@@ -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
Loading
Loading
Loading
Loading
@@ -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?
Loading
Loading
Loading
Loading
@@ -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
Loading
Loading
---
title: Users can set a status message and emoji
merge_request: 20614
author: niedermyer & davamr
type: added
# 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
Loading
Loading
@@ -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
Loading
Loading
@@ -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
Loading
Loading
Loading
Loading
@@ -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).
Loading
Loading
@@ -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
Loading
Loading
@@ -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
Loading
Loading
Loading
Loading
@@ -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
Loading
Loading
@@ -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% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment