Skip to content
Snippets Groups Projects
Commit 3bdc57f0 authored by Zeger-Jan van de Weg's avatar Zeger-Jan van de Weg
Browse files

Create table for award emoji

parent 05920a79
No related branches found
No related tags found
No related merge requests found
Showing
with 225 additions and 116 deletions
module ToggleAwardEmoji
extend ActiveSupport::Concern
included do
before_action :authenticate_user!, only: [:toggle_award_emoji]
end
def toggle_award_emoji
name = params.require(:name)
awardable.toggle_award_emoji(name, current_user)
render json: { ok: true }
end
private
def awardable
raise NotImplementedError
end
end
class Projects::IssuesController < Projects::ApplicationController
include ToggleSubscriptionAction
include IssuableActions
include ToggleAwardEmoji
 
before_action :module_enabled
before_action :issue,
Loading
Loading
@@ -61,7 +62,7 @@ class Projects::IssuesController < Projects::ApplicationController
 
def show
@note = @project.notes.new(noteable: @issue)
@notes = @issue.notes.nonawards.with_associations.fresh
@notes = @issue.notes.with_associations.fresh
@noteable = @issue
 
respond_to do |format|
Loading
Loading
@@ -158,6 +159,7 @@ class Projects::IssuesController < Projects::ApplicationController
end
alias_method :subscribable_resource, :issue
alias_method :issuable, :issue
alias_method :awardable, :issue
 
def authorize_read_issue!
return render_404 unless can?(current_user, :read_issue, @issue)
Loading
Loading
Loading
Loading
@@ -2,6 +2,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
include ToggleSubscriptionAction
include DiffHelper
include IssuableActions
include ToggleAwardEmoji
 
before_action :module_enabled
before_action :merge_request, only: [
Loading
Loading
@@ -195,7 +196,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
 
if params[:merge_when_build_succeeds].present? && @merge_request.ci_commit && @merge_request.ci_commit.active?
MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user, merge_params)
.execute(@merge_request)
.execute(@merge_request)
@status = :merge_when_build_succeeds
else
MergeWorker.perform_async(@merge_request.id, current_user.id, params)
Loading
Loading
@@ -264,6 +265,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
alias_method :subscribable_resource, :merge_request
alias_method :issuable, :merge_request
alias_method :awardable, :merge_request
 
def closes_issues
@closes_issues ||= @merge_request.closes_issues
Loading
Loading
@@ -299,7 +301,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def define_show_vars
# Build a note object for comment form
@note = @project.notes.new(noteable: @merge_request)
@notes = @merge_request.mr_and_commit_notes.nonawards.inc_author.fresh
@notes = @merge_request.mr_and_commit_notes.inc_author.fresh
@discussions = Note.discussions_from_notes(@notes)
@noteable = @merge_request
 
Loading
Loading
Loading
Loading
@@ -3,7 +3,7 @@ class Projects::NotesController < Projects::ApplicationController
before_action :authorize_read_note!
before_action :authorize_create_note!, only: [:create]
before_action :authorize_admin_note!, only: [:update, :destroy]
before_action :find_current_user_notes, except: [:destroy, :delete_attachment, :award_toggle]
before_action :find_current_user_notes, only: [:index]
 
def index
current_fetched_at = Time.now.to_i
Loading
Loading
@@ -22,8 +22,10 @@ class Projects::NotesController < Projects::ApplicationController
def create
@note = Notes::CreateService.new(project, current_user, note_params).execute
 
@note = note.is_a?(AwardEmoji) ? @note.to_note_json : note_json(@note)
respond_to do |format|
format.json { render json: note_json(@note) }
format.json { render json: @note }
format.html { redirect_back_or_default }
end
end
Loading
Loading
@@ -56,35 +58,12 @@ class Projects::NotesController < Projects::ApplicationController
end
end
 
def award_toggle
noteable = if note_params[:noteable_type] == "issue"
project.issues.find(note_params[:noteable_id])
else
project.merge_requests.find(note_params[:noteable_id])
end
data = {
author: current_user,
is_award: true,
note: note_params[:note].delete(":")
}
note = noteable.notes.find_by(data)
if note
note.destroy
else
Notes::CreateService.new(project, current_user, note_params).execute
end
render json: { ok: true }
end
private
 
def note
@note ||= @project.notes.find(params[:id])
end
alias_method :awardable, :note
 
def note_to_html(note)
render_to_string(
Loading
Loading
@@ -137,7 +116,7 @@ class Projects::NotesController < Projects::ApplicationController
id: note.id,
discussion_id: note.discussion_id,
html: note_to_html(note),
award: note.is_award,
award: false,
note: note.note,
discussion_html: note_to_discussion_html(note),
discussion_with_diff_html: note_to_discussion_with_diff_html(note)
Loading
Loading
@@ -145,7 +124,7 @@ class Projects::NotesController < Projects::ApplicationController
else
{
valid: false,
award: note.is_award,
award: false,
errors: note.errors
}
end
Loading
Loading
Loading
Loading
@@ -145,7 +145,7 @@ class ProjectsController < Projects::ApplicationController
participants = ::Projects::ParticipantsService.new(@project, current_user).execute(note_type, note_id)
 
@suggestions = {
emojis: AwardEmoji.urls,
emojis: Gitlab::AwardEmoji.urls,
issues: autocomplete.issues,
mergerequests: autocomplete.merge_requests,
members: participants
Loading
Loading
Loading
Loading
@@ -144,16 +144,17 @@ module IssuesHelper
end
end
 
def emoji_author_list(notes, current_user)
list = notes.map do |note|
note.author == current_user ? "me" : note.author.name
end
def award_user_list(awards, current_user)
list =
awards.map do |award|
award.user == current_user ? "me" : award.user.name
end
 
list.join(", ")
end
 
def note_active_class(notes, current_user)
if current_user && notes.pluck(:author_id).include?(current_user.id)
def award_active_class(awards, current_user)
if current_user && awards.find { |a| a.user_id == current_user.id }
"active"
else
""
Loading
Loading
class AwardEmoji < ActiveRecord::Base
DOWNVOTE_NAME = "thumbsdown".freeze
UPVOTE_NAME = "thumbsup".freeze
include Participable
belongs_to :awardable, polymorphic: true
belongs_to :user
validates :awardable, :user, presence: true
validates :name, presence: true, inclusion: { in: Emoji.emojis_names }
validates :name, uniqueness: { scope: [:user, :awardable_type, :awardable_id] }
participant :user
scope :downvotes, -> { where(name: DOWNVOTE_NAME) }
scope :upvotes, -> { where(name: UPVOTE_NAME) }
def downvote?
self.name == DOWNVOTE_NAME
end
def upvote?
self.name == UPVOTE_NAME
end
def to_note_json
{
valid: valid?,
award: true,
id: id,
name: name
}
end
end
module Awardable
extend ActiveSupport::Concern
included do
has_many :award_emoji, as: :awardable, dependent: :destroy
if self < Participable
participant :award_emoji
end
end
module ClassMethods
def order_upvotes_desc
order_votes_desc(AwardEmoji::UPVOTE_NAME)
end
def order_downvotes_desc
order_votes_desc(AwardEmoji::DOWNVOTE_NAME)
end
def order_votes_desc(emoji_name)
awardable_table = self.arel_table
awards_table = AwardEmoji.arel_table
join_clause = awardable_table.join(awards_table, Arel::Nodes::OuterJoin).on(
awards_table[:awardable_id].eq(awardable_table[:id]).and(
awards_table[:awardable_type].eq(self.name).and(
awards_table[:name].eq(emoji_name)
)
)
).join_sources
joins(join_clause).group(awardable_table[:id]).reorder("COUNT(award_emoji.id) DESC")
end
end
def grouped_awards(with_thumbs = true)
awards = award_emoji.group_by(&:name)
if with_thumbs
awards[AwardEmoji::UPVOTE_NAME] ||= AwardEmoji.none
awards[AwardEmoji::DOWNVOTE_NAME] ||= AwardEmoji.none
end
awards
end
def downvotes
award_emoji.where(name: AwardEmoji::DOWNVOTE_NAME).count
end
def upvotes
award_emoji.where(name: AwardEmoji::UPVOTE_NAME).count
end
def emoji_awardable?
true
end
def awarded_emoji?(emoji_name, current_user)
award_emoji.where(name: emoji_name, user: current_user).exists?
end
def create_award_emoji(name, current_user)
return unless emoji_awardable?
award_emoji.create(name: name, user: current_user)
end
def remove_award_emoji(name, current_user)
award_emoji.where(name: name, user: current_user).destroy_all
end
def toggle_award_emoji(emoji_name, current_user)
if awarded_emoji?(emoji_name, current_user)
remove_award_emoji(emoji_name, current_user)
else
create_award_emoji(emoji_name, current_user)
end
end
end
Loading
Loading
@@ -10,6 +10,7 @@ module Issuable
include Mentionable
include Subscribable
include StripAttribute
include Awardable
 
included do
belongs_to :author, class_name: "User"
Loading
Loading
@@ -99,29 +100,6 @@ module Issuable
order_by(method)
end
end
def order_downvotes_desc
order_votes_desc('thumbsdown')
end
def order_upvotes_desc
order_votes_desc('thumbsup')
end
def order_votes_desc(award_emoji_name)
issuable_table = self.arel_table
note_table = Note.arel_table
join_clause = issuable_table.join(note_table, Arel::Nodes::OuterJoin).on(
note_table[:noteable_id].eq(issuable_table[:id]).and(
note_table[:noteable_type].eq(self.name).and(
note_table[:is_award].eq(true).and(note_table[:note].eq(award_emoji_name))
)
)
).join_sources
joins(join_clause).group(issuable_table[:id]).reorder("COUNT(notes.id) DESC")
end
end
 
def today?
Loading
Loading
@@ -144,14 +122,6 @@ module Issuable
opened? || reopened?
end
 
def downvotes
notes.awards.where(note: "thumbsdown").count
end
def upvotes
notes.awards.where(note: "thumbsup").count
end
def subscribed_without_subscriptions?(user)
participants(user).include?(user)
end
Loading
Loading
Loading
Loading
@@ -36,6 +36,7 @@ class MergeRequest < ActiveRecord::Base
include Referable
include Sortable
include Taskable
include Awardable
 
belongs_to :target_project, foreign_key: :target_project_id, class_name: "Project"
belongs_to :source_project, foreign_key: :source_project_id, class_name: "Project"
Loading
Loading
Loading
Loading
@@ -16,7 +16,6 @@
# system :boolean default(FALSE), not null
# st_diff :text
# updated_by_id :integer
# is_award :boolean default(FALSE), not null
#
 
require 'carrierwave/orm/activerecord'
Loading
Loading
@@ -43,12 +42,9 @@ class Note < ActiveRecord::Base
delegate :name, to: :project, prefix: true
delegate :name, :email, to: :author, prefix: true
 
before_validation :set_award!
before_validation :clear_blank_line_code!
 
validates :note, :project, presence: true
validates :note, uniqueness: { scope: [:author, :noteable_type, :noteable_id] }, if: ->(n) { n.is_award }
validates :note, inclusion: { in: Emoji.emojis_names }, if: ->(n) { n.is_award }
validates :line_code, line_code: true, allow_blank: true
# Attachments are deprecated and are handled by Markdown uploader
validates :attachment, file_size: { maximum: :max_attachment_size }
Loading
Loading
@@ -60,8 +56,6 @@ class Note < ActiveRecord::Base
mount_uploader :attachment, AttachmentUploader
 
# Scopes
scope :awards, ->{ where(is_award: true) }
scope :nonawards, ->{ where(is_award: false) }
scope :for_commit_id, ->(commit_id) { where(noteable_type: "Commit", commit_id: commit_id) }
scope :inline, ->{ where("line_code IS NOT NULL") }
scope :not_inline, ->{ where(line_code: nil) }
Loading
Loading
@@ -119,19 +113,6 @@ class Note < ActiveRecord::Base
 
where(table[:note].matches(pattern))
end
def grouped_awards
notes = {}
awards.select(:note).distinct.map do |note|
notes[note.note] = where(note: note.note)
end
notes["thumbsup"] ||= Note.none
notes["thumbsdown"] ||= Note.none
notes
end
end
 
def cross_reference?
Loading
Loading
@@ -347,37 +328,25 @@ class Note < ActiveRecord::Base
Event.reset_event_cache_for(self)
end
 
def downvote?
is_award && note == "thumbsdown"
end
def upvote?
is_award && note == "thumbsup"
def system?
read_attribute(:system)
end
 
def editable?
!system? && !is_award
!system?
end
 
def cross_reference_not_visible_for?(user)
cross_reference? && referenced_mentionables(user).empty?
end
 
# Checks if note is an award added as a comment
#
# If note is an award, this method sets is_award to true
# and changes content of the note to award name.
#
# Method is executed as a before_validation callback.
#
def set_award!
return unless awards_supported? && contains_emoji_only?
self.is_award = true
self.note = award_emoji_name
def award_emoji?
award_emoji_supported? && contains_emoji_only?
end
 
private
def create_award_emoji
self.noteable.award_emoji(award_emoji_name, author)
end
 
def clear_blank_line_code!
self.line_code = nil if self.line_code.blank?
Loading
Loading
@@ -389,8 +358,8 @@ class Note < ActiveRecord::Base
diffs.find { |d| d.new_path == self.diff.new_path }
end
 
def awards_supported?
(for_issue? || for_merge_request?) && !for_diff_line?
def award_emoji_supported?
noteable.is_a?(Awardable) && !for_diff_line?
end
 
def contains_emoji_only?
Loading
Loading
@@ -399,6 +368,6 @@ class Note < ActiveRecord::Base
 
def award_emoji_name
original_name = note.match(Banzai::Filter::EmojiFilter.emoji_pattern)[1]
AwardEmoji.normilize_emoji_name(original_name)
Gitlab::AwardEmoji.normilize_emoji_name(original_name)
end
end
Loading
Loading
@@ -144,6 +144,7 @@ class User < ActiveRecord::Base
has_many :builds, dependent: :nullify, class_name: 'Ci::Build'
has_many :todos, dependent: :destroy
has_many :notification_settings, dependent: :destroy
has_many :award_emoji, as: :awardable, dependent: :destroy
 
#
# Validations
Loading
Loading
Loading
Loading
@@ -5,6 +5,11 @@ module Notes
note.author = current_user
note.system = false
 
if note.award_emoji?
return ToggleAwardEmojiService.new(project, current_user, params).
execute(note.noteable, note.note)
end
if note.save
# Finish the harder work in the background
NewNoteWorker.perform_in(2.seconds, note.id, params)
Loading
Loading
Loading
Loading
@@ -8,7 +8,7 @@ module Notes
 
def execute
# Skip system notes, like status changes and cross-references and awards
unless @note.system || @note.is_award
unless @note.system
EventCreateService.new.leave_note(@note, @note.author)
@note.create_cross_references!
execute_note_hooks
Loading
Loading
Loading
Loading
@@ -131,7 +131,6 @@ class NotificationService
# ignore gitlab service messages
return true if note.note.start_with?('Status changed to closed')
return true if note.cross_reference? && note.system == true
return true if note.is_award
 
target = note.noteable
 
Loading
Loading
Loading
Loading
@@ -98,6 +98,14 @@ class TodoService
handle_note(note, current_user)
end
 
# When an emoji is awarded we should:
#
# * mark all pending todos related to the awardable for the current user as done
#
def new_award_emoji(awardable, current_user)
mark_pending_todos_as_done(awardable, current_user)
end
# When marking pending todos as done we should:
#
# * mark all pending todos related to the target for the current user as done
Loading
Loading
require_relative 'base_service'
class ToggleAwardEmojiService < BaseService
# For an award emoji being posted we should:
# - Mark the TODO as done for this issuable (skip on snippets)
# - Save the award emoji
def execute(awardable, emoji)
todo_service.new_award_emoji(awardable, current_user)
# Needed if its posted as a note containing only :+1:
emoji = award_emoji_name(emoji) if emoji.start_with? ':'
awardable.toggle_award_emoji(emoji, current_user)
end
private
def award_emoji_name(emoji)
original_name = emoji.match(Banzai::Filter::EmojiFilter.emoji_pattern)[1]
Gitlab::AwardEmoji.normalize_emoji_name(original_name)
end
end
- grouped_awards = awardable.grouped_awards(inline)
.awards.js-awards-block{ class: ("hidden" if !inline && grouped_emojis.size == 0), data: { award_url: url_for([:toggle_award_emoji, @project.namespace.becomes(Namespace), @project, awardable]) } }
- awards_sort(grouped_awards).each do |emoji, awards|
%button.btn.award-control.js-emoji-btn.has-tooltip{ type: "button", class: (award_active_class(awards, current_user)), title: award_user_list(awards, current_user), data: { placement: "bottom" } }
= emoji_icon(emoji)
%span.award-control-text.js-counter
= awards.count
- if current_user
.award-menu-holder.js-award-holder
%button.btn.award-control.js-add-award{ type: "button", data: { award_menu_url: emojis_path } }
= icon('smile-o', {class: "award-control-icon award-control-icon-normal"})
= icon('spinner spin', {class: "award-control-icon award-control-icon-loading"})
%span.award-control-text
Add
.emoji-menu
.emoji-menu-content
= text_field_tag :emoji_search, "", class: "emoji-search search-input form-control"
- AwardEmoji.emoji_by_category.each do |category, emojis|
- Gitlab::AwardEmoji.emoji_by_category.each do |category, emojis|
%h5.emoji-menu-title
= AwardEmoji::CATEGORIES[category]
= Gitlab::AwardEmoji::CATEGORIES[category]
%ul.clearfix.emoji-menu-list
- emojis.each do |emoji|
%li.pull-left.text-center.emoji-menu-list-item
Loading
Loading
Loading
Loading
@@ -27,7 +27,7 @@
= icon('thumbs-down')
= downvotes
 
- note_count = issue.notes.user.nonawards.count
- note_count = issue.notes.user.count
- if note_count > 0
%li
= link_to issue_path(issue) + "#notes" do
Loading
Loading
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