Commit 3bdc57f0 authored by Zeger-Jan van de Weg's avatar Zeger-Jan van de Weg

Create table for award emoji

parent 05920a79
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 class Projects::IssuesController < Projects::ApplicationController
include ToggleSubscriptionAction include ToggleSubscriptionAction
include IssuableActions include IssuableActions
include ToggleAwardEmoji
before_action :module_enabled before_action :module_enabled
before_action :issue, before_action :issue,
...@@ -61,7 +62,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -61,7 +62,7 @@ class Projects::IssuesController < Projects::ApplicationController
def show def show
@note = @project.notes.new(noteable: @issue) @note = @project.notes.new(noteable: @issue)
@notes = @issue.notes.nonawards.with_associations.fresh @notes = @issue.notes.with_associations.fresh
@noteable = @issue @noteable = @issue
respond_to do |format| respond_to do |format|
...@@ -158,6 +159,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -158,6 +159,7 @@ class Projects::IssuesController < Projects::ApplicationController
end end
alias_method :subscribable_resource, :issue alias_method :subscribable_resource, :issue
alias_method :issuable, :issue alias_method :issuable, :issue
alias_method :awardable, :issue
def authorize_read_issue! def authorize_read_issue!
return render_404 unless can?(current_user, :read_issue, @issue) return render_404 unless can?(current_user, :read_issue, @issue)
......
...@@ -2,6 +2,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -2,6 +2,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
include ToggleSubscriptionAction include ToggleSubscriptionAction
include DiffHelper include DiffHelper
include IssuableActions include IssuableActions
include ToggleAwardEmoji
before_action :module_enabled before_action :module_enabled
before_action :merge_request, only: [ before_action :merge_request, only: [
...@@ -195,7 +196,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -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? if params[:merge_when_build_succeeds].present? && @merge_request.ci_commit && @merge_request.ci_commit.active?
MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user, merge_params) MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user, merge_params)
.execute(@merge_request) .execute(@merge_request)
@status = :merge_when_build_succeeds @status = :merge_when_build_succeeds
else else
MergeWorker.perform_async(@merge_request.id, current_user.id, params) MergeWorker.perform_async(@merge_request.id, current_user.id, params)
...@@ -264,6 +265,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -264,6 +265,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
alias_method :subscribable_resource, :merge_request alias_method :subscribable_resource, :merge_request
alias_method :issuable, :merge_request alias_method :issuable, :merge_request
alias_method :awardable, :merge_request
def closes_issues def closes_issues
@closes_issues ||= @merge_request.closes_issues @closes_issues ||= @merge_request.closes_issues
...@@ -299,7 +301,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -299,7 +301,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def define_show_vars def define_show_vars
# Build a note object for comment form # Build a note object for comment form
@note = @project.notes.new(noteable: @merge_request) @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) @discussions = Note.discussions_from_notes(@notes)
@noteable = @merge_request @noteable = @merge_request
......
...@@ -3,7 +3,7 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -3,7 +3,7 @@ class Projects::NotesController < Projects::ApplicationController
before_action :authorize_read_note! before_action :authorize_read_note!
before_action :authorize_create_note!, only: [:create] before_action :authorize_create_note!, only: [:create]
before_action :authorize_admin_note!, only: [:update, :destroy] 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 def index
current_fetched_at = Time.now.to_i current_fetched_at = Time.now.to_i
...@@ -22,8 +22,10 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -22,8 +22,10 @@ class Projects::NotesController < Projects::ApplicationController
def create def create
@note = Notes::CreateService.new(project, current_user, note_params).execute @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| respond_to do |format|
format.json { render json: note_json(@note) } format.json { render json: @note }
format.html { redirect_back_or_default } format.html { redirect_back_or_default }
end end
end end
...@@ -56,35 +58,12 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -56,35 +58,12 @@ class Projects::NotesController < Projects::ApplicationController
end end
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 private
def note def note
@note ||= @project.notes.find(params[:id]) @note ||= @project.notes.find(params[:id])
end end
alias_method :awardable, :note
def note_to_html(note) def note_to_html(note)
render_to_string( render_to_string(
...@@ -137,7 +116,7 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -137,7 +116,7 @@ class Projects::NotesController < Projects::ApplicationController
id: note.id, id: note.id,
discussion_id: note.discussion_id, discussion_id: note.discussion_id,
html: note_to_html(note), html: note_to_html(note),
award: note.is_award, award: false,
note: note.note, note: note.note,
discussion_html: note_to_discussion_html(note), discussion_html: note_to_discussion_html(note),
discussion_with_diff_html: note_to_discussion_with_diff_html(note) discussion_with_diff_html: note_to_discussion_with_diff_html(note)
...@@ -145,7 +124,7 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -145,7 +124,7 @@ class Projects::NotesController < Projects::ApplicationController
else else
{ {
valid: false, valid: false,
award: note.is_award, award: false,
errors: note.errors errors: note.errors
} }
end end
......
...@@ -145,7 +145,7 @@ class ProjectsController < Projects::ApplicationController ...@@ -145,7 +145,7 @@ class ProjectsController < Projects::ApplicationController
participants = ::Projects::ParticipantsService.new(@project, current_user).execute(note_type, note_id) participants = ::Projects::ParticipantsService.new(@project, current_user).execute(note_type, note_id)
@suggestions = { @suggestions = {
emojis: AwardEmoji.urls, emojis: Gitlab::AwardEmoji.urls,
issues: autocomplete.issues, issues: autocomplete.issues,
mergerequests: autocomplete.merge_requests, mergerequests: autocomplete.merge_requests,
members: participants members: participants
......
...@@ -144,16 +144,17 @@ module IssuesHelper ...@@ -144,16 +144,17 @@ module IssuesHelper
end end
end end
def emoji_author_list(notes, current_user) def award_user_list(awards, current_user)
list = notes.map do |note| list =
note.author == current_user ? "me" : note.author.name awards.map do |award|
end award.user == current_user ? "me" : award.user.name
end
list.join(", ") list.join(", ")
end end
def note_active_class(notes, current_user) def award_active_class(awards, current_user)
if current_user && notes.pluck(:author_id).include?(current_user.id) if current_user && awards.find { |a| a.user_id == current_user.id }
"active" "active"
else else
"" ""
......
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
...@@ -10,6 +10,7 @@ module Issuable ...@@ -10,6 +10,7 @@ module Issuable
include Mentionable include Mentionable
include Subscribable include Subscribable
include StripAttribute include StripAttribute
include Awardable
included do included do
belongs_to :author, class_name: "User" belongs_to :author, class_name: "User"
...@@ -99,29 +100,6 @@ module Issuable ...@@ -99,29 +100,6 @@ module Issuable
order_by(method) order_by(method)
end end
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 end
def today? def today?
...@@ -144,14 +122,6 @@ module Issuable ...@@ -144,14 +122,6 @@ module Issuable
opened? || reopened? opened? || reopened?
end end
def downvotes
notes.awards.where(note: "thumbsdown").count
end
def upvotes
notes.awards.where(note: "thumbsup").count
end
def subscribed_without_subscriptions?(user) def subscribed_without_subscriptions?(user)
participants(user).include?(user) participants(user).include?(user)
end end
......
...@@ -36,6 +36,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -36,6 +36,7 @@ class MergeRequest < ActiveRecord::Base
include Referable include Referable
include Sortable include Sortable
include Taskable include Taskable
include Awardable
belongs_to :target_project, foreign_key: :target_project_id, class_name: "Project" belongs_to :target_project, foreign_key: :target_project_id, class_name: "Project"
belongs_to :source_project, foreign_key: :source_project_id, class_name: "Project" belongs_to :source_project, foreign_key: :source_project_id, class_name: "Project"
......
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
# system :boolean default(FALSE), not null # system :boolean default(FALSE), not null
# st_diff :text # st_diff :text
# updated_by_id :integer # updated_by_id :integer
# is_award :boolean default(FALSE), not null
# #
require 'carrierwave/orm/activerecord' require 'carrierwave/orm/activerecord'
...@@ -43,12 +42,9 @@ class Note < ActiveRecord::Base ...@@ -43,12 +42,9 @@ class Note < ActiveRecord::Base
delegate :name, to: :project, prefix: true delegate :name, to: :project, prefix: true
delegate :name, :email, to: :author, prefix: true delegate :name, :email, to: :author, prefix: true
before_validation :set_award!
before_validation :clear_blank_line_code! before_validation :clear_blank_line_code!
validates :note, :project, presence: true 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 validates :line_code, line_code: true, allow_blank: true
# Attachments are deprecated and are handled by Markdown uploader # Attachments are deprecated and are handled by Markdown uploader
validates :attachment, file_size: { maximum: :max_attachment_size } validates :attachment, file_size: { maximum: :max_attachment_size }
...@@ -60,8 +56,6 @@ class Note < ActiveRecord::Base ...@@ -60,8 +56,6 @@ class Note < ActiveRecord::Base
mount_uploader :attachment, AttachmentUploader mount_uploader :attachment, AttachmentUploader
# Scopes # 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 :for_commit_id, ->(commit_id) { where(noteable_type: "Commit", commit_id: commit_id) }
scope :inline, ->{ where("line_code IS NOT NULL") } scope :inline, ->{ where("line_code IS NOT NULL") }
scope :not_inline, ->{ where(line_code: nil) } scope :not_inline, ->{ where(line_code: nil) }
...@@ -119,19 +113,6 @@ class Note < ActiveRecord::Base ...@@ -119,19 +113,6 @@ class Note < ActiveRecord::Base
where(table[:note].matches(pattern)) where(table[:note].matches(pattern))
end 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 end
def cross_reference? def cross_reference?
...@@ -347,37 +328,25 @@ class Note < ActiveRecord::Base ...@@ -347,37 +328,25 @@ class Note < ActiveRecord::Base
Event.reset_event_cache_for(self) Event.reset_event_cache_for(self)
end end
def downvote? def system?
is_award && note == "thumbsdown" read_attribute(:system)
end
def upvote?
is_award && note == "thumbsup"
end end
def editable? def editable?
!system? && !is_award !system?
end end
def cross_reference_not_visible_for?(user) def cross_reference_not_visible_for?(user)
cross_reference? && referenced_mentionables(user).empty? cross_reference? && referenced_mentionables(user).empty?
end end
# Checks if note is an award added as a comment def award_emoji?
# award_emoji_supported? && contains_emoji_only?
# 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
end end
private def create_award_emoji
self.noteable.award_emoji(award_emoji_name, author)
end
def clear_blank_line_code! def clear_blank_line_code!
self.line_code = nil if self.line_code.blank? self.line_code = nil if self.line_code.blank?
...@@ -389,8 +358,8 @@ class Note < ActiveRecord::Base ...@@ -389,8 +358,8 @@ class Note < ActiveRecord::Base
diffs.find { |d| d.new_path == self.diff.new_path } diffs.find { |d| d.new_path == self.diff.new_path }
end end
def awards_supported? def award_emoji_supported?
(for_issue? || for_merge_request?) && !for_diff_line? noteable.is_a?(Awardable) && !for_diff_line?
end end
def contains_emoji_only? def contains_emoji_only?
...@@ -399,6 +368,6 @@ class Note < ActiveRecord::Base ...@@ -399,6 +368,6 @@ class Note < ActiveRecord::Base
def award_emoji_name def award_emoji_name
original_name = note.match(Banzai::Filter::EmojiFilter.emoji_pattern)[1] 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
end end
...@@ -144,6 +144,7 @@ class User < ActiveRecord::Base ...@@ -144,6 +144,7 @@ class User < ActiveRecord::Base
has_many :builds, dependent: :nullify, class_name: 'Ci::Build' has_many :builds, dependent: :nullify, class_name: 'Ci::Build'
has_many :todos, dependent: :destroy has_many :todos, dependent: :destroy
has_many :notification_settings, dependent: :destroy has_many :notification_settings, dependent: :destroy
has_many :award_emoji, as: :awardable, dependent: :destroy
# #
# Validations # Validations
......
...@@ -5,6 +5,11 @@ module Notes ...@@ -5,6 +5,11 @@ module Notes
note.author = current_user note.author = current_user
note.system = false note.system = false
if note.award_emoji?
return ToggleAwardEmojiService.new(project, current_user, params).
execute(note.noteable, note.note)
end
if note.save if note.save
# Finish the harder work in the background # Finish the harder work in the background
NewNoteWorker.perform_in(2.seconds, note.id, params) NewNoteWorker.perform_in(2.seconds, note.id, params)
......
...@@ -8,7 +8,7 @@ module Notes ...@@ -8,7 +8,7 @@ module Notes
def execute def execute
# Skip system notes, like status changes and cross-references and awards # 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) EventCreateService.new.leave_note(@note, @note.author)
@note.create_cross_references! @note.create_cross_references!
execute_note_hooks execute_note_hooks
......
...@@ -131,7 +131,6 @@ class NotificationService ...@@ -131,7 +131,6 @@ class NotificationService
# ignore gitlab service messages # ignore gitlab service messages
return true if note.note.start_with?('Status changed to closed') return true if note.note.start_with?('Status changed to closed')
return true if note.cross_reference? && note.system == true return true if note.cross_reference? && note.system == true
return true if note.is_award
target = note.noteable target = note.noteable
......
...@@ -98,6 +98,14 @@ class TodoService ...@@ -98,6 +98,14 @@ class TodoService
handle_note(note, current_user) handle_note(note, current_user)
end 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: # When marking pending todos as done we should:
# #
# * mark all pending todos related to the target for the current user as done # * mark all pending todos related to the target for the current user as done
......
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
.emoji-menu-content .emoji-menu-content
= text_field_tag :emoji_search, "", class: "emoji-search search-input form-control" = 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 %h5.emoji-menu-title
= AwardEmoji::CATEGORIES[category] = Gitlab::AwardEmoji::CATEGORIES[category]
%ul.clearfix.emoji-menu-list %ul.clearfix.emoji-menu-list
- emojis.each do |emoji| - emojis.each do |emoji|
%li.pull-left.text-center.emoji-menu-list-item %li.pull-left.text-center.emoji-menu-list-item
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
= icon('thumbs-down') = icon('thumbs-down')
= downvotes = downvotes
- note_count = issue.notes.user.nonawards.count - note_count = issue.notes.user.count
- if note_count > 0 - if note_count > 0
%li %li
= link_to issue_path(issue) + "#notes" do = link_to issue_path(issue) + "#notes" do
......
...@@ -72,7 +72,7 @@ ...@@ -72,7 +72,7 @@
.content-block.content-block-small .content-block.content-block-small
= render 'new_branch' = render 'new_branch'
= render 'votes/votes_block', votable: @issue = render 'award_emoji/awards_block', awardable: @issue, inline: true
.row .row
%section.col-md-12 %section.col-md-12
......
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
= icon('thumbs-down') = icon('thumbs-down')
= downvotes = downvotes
- note_count = merge_request.mr_and_commit_notes.user.nonawards.count - note_count = merge_request.mr_and_commit_notes.user.count
- if note_count > 0 - if note_count > 0
%li %li
= link_to merge_request_path(merge_request) + "#notes" do = link_to merge_request_path(merge_request) + "#notes" do
......
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
%li.notes-tab %li.notes-tab
= link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#notes', action: 'notes', toggle: 'tab'} do = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#notes', action: 'notes', toggle: 'tab'} do
Discussion Discussion
%span.badge= @merge_request.mr_and_commit_notes.user.nonawards.count %span.badge= @merge_request.mr_and_commit_notes.user.count
%li.commits-tab %li.commits-tab
= link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#commits', action: 'commits', toggle: 'tab'} do = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#commits', action: 'commits', toggle: 'tab'} do
Commits Commits
...@@ -68,7 +68,7 @@ ...@@ -68,7 +68,7 @@
.tab-content .tab-content
#notes.notes.tab-pane.voting_notes #notes.notes.tab-pane.voting_notes
.content-block.content-block-small.oneline-block .content-block.content-block-small.oneline-block
= render 'votes/votes_block', votable: @merge_request = render 'award_emoji/awards_block', awardable: @merge_request, inline: true
.row .row
%section.col-md-12 %section.col-md-12
......
.awards.votes-block .awards.votes-block{data: { toggle_url: url_for([:toggle_award_emoji, @project.namespace.becomes(Namespace), @project, awardable]) }}
- awards_sort(votable.notes.awards.grouped_awards).each do |emoji, notes| - awards_sort(awardable.grouped_awards).each do |emoji, awards|
%button.btn.award-control.js-emoji-btn.has-tooltip{class: (note_active_class(notes, current_user)), data: {placement: "top", original_title: emoji_author_list(notes, current_user)}} %button.btn.award-control.js-emoji-btn.has-tooltip{class: (note_active_class(awards, current_user)), data: {placement: "top", original_title: emoji_author_list(awards, current_user)}}
= emoji_icon(emoji, sprite: false) = emoji_icon(emoji, sprite: false)
%span.award-control-text.js-counter %span.award-control-text.js-counter
= notes.count = awards.count
- if current_user - if current_user
%div.award-menu-holder.js-award-holder %div.award-menu-holder.js-award-holder
......
...@@ -8,3 +8,7 @@ ...@@ -8,3 +8,7 @@
# inflect.irregular 'person', 'people' # inflect.irregular 'person', 'people'
# inflect.uncountable %w( fish sheep ) # inflect.uncountable %w( fish sheep )
# end # end
#
ActiveSupport::Inflector.inflections do |inflect|
inflect.uncountable %w(award_emoji)
end
...@@ -639,6 +639,7 @@ Rails.application.routes.draw do ...@@ -639,6 +639,7 @@ Rails.application.routes.draw do
post :cancel_merge_when_build_succeeds post :cancel_merge_when_build_succeeds
get :ci_status get :ci_status
post :toggle_subscription post :toggle_subscription
post :toggle_award_emoji
post :remove_wip post :remove_wip
end end
...@@ -703,6 +704,7 @@ Rails.application.routes.draw do ...@@ -703,6 +704,7 @@ Rails.application.routes.draw do
resources :issues, constraints: { id: /\d+/ } do resources :issues, constraints: { id: /\d+/ } do
member do member do
post :toggle_subscription post :toggle_subscription
post :toggle_award_emoji
get :referenced_merge_requests get :referenced_merge_requests
get :related_branches get :related_branches
end end
...@@ -731,10 +733,7 @@ Rails.application.routes.draw do ...@@ -731,10 +733,7 @@ Rails.application.routes.draw do
resources :notes, only: [:index, :create, :destroy, :update], constraints: { id: /\d+/ } do resources :notes, only: [:index, :create, :destroy, :update], constraints: { id: /\d+/ } do
member do member do
delete :delete_attachment delete :delete_attachment
end post :toggle_award_emoji
collection do
post :award_toggle
end end
end end
......
class AddAwardEmoji < ActiveRecord::Migration
def change
create_table :award_emoji do |t|
t.string :name
t.references :user
t.references :awardable, polymorphic: true
t.timestamps
end
add_index :award_emoji, :user_id
add_index :award_emoji, :awardable_type
add_index :award_emoji, :awardable_id
end
end
class ConvertAwardNoteToEmojiAward < ActiveRecord::Migration
def change
def up
execute "INSERT INTO award_emoji (awardable_type, awardable_id, user_id, name, created_at, updated_at) (SELECT noteable_type, noteable_id, author_id, note, created_at, updated_at FROM notes WHERE is_award = true)"
end
def down
execute <<-SQL
INSERT INTO notes (noteable_type, noteable_id, author_id, note, created_at, updated_at, is_award)
(SELECT awardable_type, awardable_id, user_id, name, created_at, updated_at, TRUE
FROM award_emoji
WHERE awardable_type IN ('Issue', 'MergeRequest')
)
SQL
end
end
end
class RemoveNoteIsAward < ActiveRecord::Migration
def change
remove_column :notes, :is_award, :boolean
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160412140240) do ActiveRecord::Schema.define(version: 20160416190505) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -94,6 +94,19 @@ ActiveRecord::Schema.define(version: 20160412140240) do ...@@ -94,6 +94,19 @@ ActiveRecord::Schema.define(version: 20160412140240) do
add_index "audit_events", ["entity_id", "entity_type"], name: "index_audit_events_on_entity_id_and_entity_type", using: :btree add_index "audit_events", ["entity_id", "entity_type"], name: "index_audit_events_on_entity_id_and_entity_type", using: :btree
add_index "audit_events", ["type"], name: "index_audit_events_on_type", using: :btree add_index "audit_events", ["type"], name: "index_audit_events_on_type", using: :btree
create_table "award_emoji", force: :cascade do |t|
t.string "name"
t.integer "user_id"
t.integer "awardable_id"
t.string "awardable_type"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "award_emoji", ["awardable_id"], name: "index_award_emoji_on_awardable_id", using: :btree
add_index "award_emoji", ["awardable_type"], name: "index_award_emoji_on_awardable_type", using: :btree
add_index "award_emoji", ["user_id"], name: "index_award_emoji_on_user_id", using: :btree
create_table "broadcast_messages", force: :cascade do |t| create_table "broadcast_messages", force: :cascade do |t|
t.text "message", null: false t.text "message", null: false
t.datetime "starts_at" t.datetime "starts_at"
...@@ -622,14 +635,12 @@ ActiveRecord::Schema.define(version: 20160412140240) do ...@@ -622,14 +635,12 @@ ActiveRecord::Schema.define(version: 20160412140240) do
t.boolean "system", default: false, null: false t.boolean "system", default: false, null: false
t.text "st_diff" t.text "st_diff"
t.integer "updated_by_id" t.integer "updated_by_id"
t.boolean "is_award", default: false, null: false
end end
add_index "notes", ["author_id"], name: "index_notes_on_author_id", using: :btree add_index "notes", ["author_id"], name: "index_notes_on_author_id", using: :btree
add_index "notes", ["commit_id"], name: "index_notes_on_commit_id", using: :btree add_index "notes", ["commit_id"], name: "index_notes_on_commit_id", using: :btree
add_index "notes", ["created_at", "id"], name: "index_notes_on_created_at_and_id", using: :btree add_index "notes", ["created_at", "id"], name: "index_notes_on_created_at_and_id", using: :btree
add_index "notes", ["created_at"], name: "index_notes_on_created_at", using: :btree add_index "notes", ["created_at"], name: "index_notes_on_created_at", using: :btree
add_index "notes", ["is_award"], name: "index_notes_on_is_award", using: :btree
add_index "notes", ["line_code"], name: "index_notes_on_line_code", using: :btree add_index "notes", ["line_code"], name: "index_notes_on_line_code", using: :btree
add_index "notes", ["note"], name: "index_notes_on_note_trigram", using: :gin, opclasses: {"note"=>"gin_trgm_ops"} add_index "notes", ["note"], name: "index_notes_on_note_trigram", using: :gin, opclasses: {"note"=>"gin_trgm_ops"}
add_index "notes", ["noteable_id", "noteable_type"], name: "index_notes_on_noteable_id_and_noteable_type", using: :btree add_index "notes", ["noteable_id", "noteable_type"], name: "index_notes_on_noteable_id_and_noteable_type", using: :btree
...@@ -716,37 +727,37 @@ ActiveRecord::Schema.define(version: 20160412140240) do ...@@ -716,37 +727,37 @@ ActiveRecord::Schema.define(version: 20160412140240) do
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
t.integer "creator_id" t.integer "creator_id"
t.boolean "issues_enabled", default: true, null: false t.boolean "issues_enabled", default: true, null: false
t.boolean "wall_enabled", default: true, null: false t.boolean "wall_enabled", default: true, null: false
t.boolean "merge_requests_enabled", default: true, null: false t.boolean "merge_requests_enabled", default: true, null: false
t.boolean "wiki_enabled", default: true, null: false t.boolean "wiki_enabled", default: true, null: false
t.integer "namespace_id" t.integer "namespace_id"
t.string "issues_tracker", default: "gitlab", null: false t.string "issues_tracker", default: "gitlab", null: false
t.string "issues_tracker_id" t.string "issues_tracker_id"
t.boolean "snippets_enabled", default: true, null: false t.boolean "snippets_enabled", default: true, null: false
t.datetime "last_activity_at" t.datetime "last_activity_at"
t.string "import_url" t.string "import_url"
t.integer "visibility_level", default: 0, null: false t.integer "visibility_level", default: 0, null: false
t.boolean "archived", default: false, null: false t.boolean "archived", default: false, null: false
t.string "avatar" t.string "avatar"
t.string "import_status" t.string "import_status"
t.float "repository_size", default: 0.0 t.float "repository_size", default: 0.0
t.integer "star_count", default: 0, null: false t.integer "star_count", default: 0, null: false
t.string "import_type" t.string "import_type"
t.string "import_source" t.string "import_source"
t.integer "commit_count", default: 0 t.integer "commit_count", default: 0
t.text "import_error" t.text "import_error"
t.integer "ci_id" t.integer "ci_id"
t.boolean "builds_enabled", default: true, null: false t.boolean "builds_enabled", default: true, null: false
t.boolean "shared_runners_enabled", default: true, null: false t.boolean "shared_runners_enabled", default: true, null: false
t.string "runners_token" t.string "runners_token"
t.string "build_coverage_regex" t.string "build_coverage_regex"
t.boolean "build_allow_git_fetch", default: true, null: false t.boolean "build_allow_git_fetch", default: true, null: false
t.integer "build_timeout", default: 3600, null: false t.integer "build_timeout", default: 3600, null: false
t.boolean "pending_delete", default: false t.boolean "pending_delete", default: false
t.boolean "public_builds", default: true, null: false t.boolean "public_builds", default: true, null: false
t.string "main_language" t.string "main_language"
t.integer "pushes_since_gc", default: 0 t.integer "pushes_since_gc", default: 0
t.boolean "last_repository_check_failed" t.boolean "last_repository_check_failed"
t.datetime "last_repository_check_at" t.datetime "last_repository_check_at"
end end
......
...@@ -170,6 +170,7 @@ module API ...@@ -170,6 +170,7 @@ module API
expose :label_names, as: :labels expose :label_names, as: :labels
expose :milestone, using: Entities::Milestone expose :milestone, using: Entities::Milestone
expose :assignee, :author, using: Entities::UserBasic expose :assignee, :author, using: Entities::UserBasic
expose :upvotes, :downvotes
expose :subscribed do |issue, options| expose :subscribed do |issue, options|
issue.subscribed?(options[:current_user]) issue.subscribed?(options[:current_user])
...@@ -178,7 +179,7 @@ module API ...@@ -178,7 +179,7 @@ module API
class MergeRequest < ProjectEntity class MergeRequest < ProjectEntity
expose :target_branch, :source_branch expose :target_branch, :source_branch
expose :upvotes, :downvotes expose :upvotes, :downvotes
expose :author, :assignee, using: Entities::UserBasic expose :author, :assignee, using: Entities::UserBasic
expose :source_project_id, :target_project_id expose :source_project_id, :target_project_id
expose :label_names, as: :labels expose :label_names, as: :labels
...@@ -216,8 +217,8 @@ module API ...@@ -216,8 +217,8 @@ module API
expose :system?, as: :system expose :system?, as: :system
expose :noteable_id, :noteable_type expose :noteable_id, :noteable_type
# upvote? and downvote? are deprecated, always return false # upvote? and downvote? are deprecated, always return false
expose :upvote?, as: :upvote expose(:upvote?) { |note| false }
expose :downvote?, as: :downvote expose(:downvote?) { |note| false }
end end
class MRNote < Grape::Entity class MRNote < Grape::Entity
......
class AwardEmoji
CATEGORIES = {
other: "Other",
objects: "Objects",
places: "Places",
travel_places: "Travel",
emoticons: "Emoticons",
objects_symbols: "Symbols",
nature: "Nature",
celebration: "Celebration",
people: "People",
activity: "Activity",
flags: "Flags",
food_drink: "Food"
}.with_indifferent_access
CATEGORY_ALIASES = {
symbols: "objects_symbols",
foods: "food_drink",
travel: "travel_places"
}.with_indifferent_access
def self.normilize_emoji_name(name)
aliases[name] || name
end
def self.emoji_by_category
unless @emoji_by_category
@emoji_by_category = Hash.new { |h, key| h[key] = [] }
emojis.each do |emoji_name, data|
data["name"] = emoji_name
# Skip Fitzpatrick(tone) modifiers
next if data["category"] == "modifier"
category = CATEGORY_ALIASES[data["category"]] || data["category"]
@emoji_by_category[category] << data
end
@emoji_by_category = @emoji_by_category.sort.to_h
end
@emoji_by_category
end
def self.emojis
@emojis ||= begin
json_path = File.join(Rails.root, 'fixtures', 'emojis', 'index.json' )
JSON.parse(File.read(json_path))
end
end
def self.aliases
@aliases ||= begin
json_path = File.join(Rails.root, 'fixtures', 'emojis', 'aliases.json' )
JSON.parse(File.read(json_path))
end
end
# Returns an Array of Emoji names and their asset URLs.
def self.urls
@urls ||= begin
path = File.join(Rails.root, 'fixtures', 'emojis', 'digests.json')
prefix = Gitlab::Application.config.assets.prefix
digest = Gitlab::Application.config.assets.digest
JSON.parse(File.read(path)).map do |hash|
if digest
fname = "#{hash['unicode']}-#{hash['digest']}"
else
fname = hash['unicode']
end
{ name: hash['name'], path: "#{prefix}/#{fname}.png" }
end
end
end
end
module Gitlab
class AwardEmoji
CATEGORIES = {
other: "Other",
objects: "Objects",
places: "Places",
travel_places: "Travel",
emoticons: "Emoticons",
objects_symbols: "Symbols",
nature: "Nature",
celebration: "Celebration",
people: "People",
activity: "Activity",
flags: "Flags",
food_drink: "Food"
}.with_indifferent_access
CATEGORY_ALIASES = {
symbols: "objects_symbols",
foods: "food_drink",
travel: "travel_places"
}.with_indifferent_access
def self.normalize_emoji_name(name)
aliases[name] || name
end
def self.emoji_by_category
unless @emoji_by_category
@emoji_by_category = Hash.new { |h, key| h[key] = [] }
emojis.each do |emoji_name, data|
data["name"] = emoji_name
# Skip Fitzpatrick(tone) modifiers
next if data["category"] == "modifier"
category = CATEGORY_ALIASES[data["category"]] || data["category"]
@emoji_by_category[category] << data
end
@emoji_by_category = @emoji_by_category.sort.to_h
end
@emoji_by_category
end
def self.emojis
@emojis ||= begin
json_path = File.join(Rails.root, 'fixtures', 'emojis', 'index.json' )
JSON.parse(File.read(json_path))
end
end
def self.aliases
@aliases ||= begin
json_path = File.join(Rails.root, 'fixtures', 'emojis', 'aliases.json' )
JSON.parse(File.read(json_path))
end
end
# Returns an Array of Emoji names and their asset URLs.
def self.urls
@urls ||= begin
path = File.join(Rails.root, 'fixtures', 'emojis', 'digests.json')
prefix = Gitlab::Application.config.assets.prefix
digest = Gitlab::Application.config.assets.digest
JSON.parse(File.read(path)).map do |hash|
if digest
fname = "#{hash['unicode']}-#{hash['digest']}"
else
fname = hash['unicode']
end
{ name: hash['name'], path: "#{prefix}/#{fname}.png" }
end
end
end
end
end
...@@ -31,9 +31,9 @@ describe GroupsController do ...@@ -31,9 +31,9 @@ describe GroupsController do
let(:issue_2) { create(:issue, project: project) } let(:issue_2) { create(:issue, project: project) }
before do before do
create_list(:upvote_note, 3, project: project, noteable: issue_2) create_list(:award_emoji, 3, awardable: issue_2)
create_list(:upvote_note, 2, project: project, noteable: issue_1) create_list(:award_emoji, 2, awardable: issue_1)
create_list(:downvote_note, 2, project: project, noteable: issue_2) create_list(:award_emoji, 2, awardable: issue_2, name: "thumbsdown")
sign_in(user) sign_in(user)
end end
...@@ -56,9 +56,9 @@ describe GroupsController do ...@@ -56,9 +56,9 @@ describe GroupsController do
let(:merge_request_2) { create(:merge_request, :simple, source_project: project) } let(:merge_request_2) { create(:merge_request, :simple, source_project: project) }
before do before do
create_list(:upvote_note, 3, project: project, noteable: merge_request_2) create_list(:award_emoji, 3, awardable: merge_request_2)
create_list(:upvote_note, 2, project: project, noteable: merge_request_1) create_list(:award_emoji, 2, awardable: merge_request_1)
create_list(:downvote_note, 2, project: project, noteable: merge_request_2) create_list(:award_emoji, 2, awardable: merge_request_2, name: "thumbsdown")
sign_in(user) sign_in(user)
end end
......
FactoryGirl.define do
factory :award_emoji do
name "thumbsup"
user
awardable factory: :issue
end
end
...@@ -36,8 +36,6 @@ FactoryGirl.define do ...@@ -36,8 +36,6 @@ FactoryGirl.define do
factory :note_on_merge_request_diff, traits: [:on_merge_request, :on_diff] factory :note_on_merge_request_diff, traits: [:on_merge_request, :on_diff]
factory :note_on_project_snippet, traits: [:on_project_snippet] factory :note_on_project_snippet, traits: [:on_project_snippet]
factory :system_note, traits: [:system] factory :system_note, traits: [:system]
factory :downvote_note, traits: [:award, :downvote]
factory :upvote_note, traits: [:award, :upvote]
trait :on_commit do trait :on_commit do
project project
...@@ -69,10 +67,6 @@ FactoryGirl.define do ...@@ -69,10 +67,6 @@ FactoryGirl.define do
system true system true
end end
trait :award do
is_award true
end
trait :downvote do trait :downvote do
note "thumbsdown" note "thumbsdown"
end end
......
...@@ -127,18 +127,15 @@ describe IssuesHelper do ...@@ -127,18 +127,15 @@ describe IssuesHelper do
it { is_expected.to eq("!1, !2, or !3") } it { is_expected.to eq("!1, !2, or !3") }
end end
describe "note_active_class" do describe '#award_active_class' do
before do let!(:upvote) { create(:award_emoji) }
@note = create :note
@note1 = create :note
end
it "returns empty string for unauthenticated user" do it "returns empty string for unauthenticated user" do
expect(note_active_class(Note.all, nil)).to eq("") expect(award_active_class(AwardEmoji.all, nil)).to eq("")
end end
it "returns active string for author" do it "returns active string for author" do
expect(note_active_class(Note.all, @note.author)).to eq("active") expect(award_active_class(AwardEmoji.all, upvote.user)).to eq("active")
end end
end end
......
require 'spec_helper' require 'spec_helper'
describe AwardEmoji do describe Gitlab::AwardEmoji do
describe '.urls' do describe '.urls' do
subject { AwardEmoji.urls } subject { Gitlab::AwardEmoji.urls }
it { is_expected.to be_an_instance_of(Array) } it { is_expected.to be_an_instance_of(Array) }
it { is_expected.to_not be_empty } it { is_expected.to_not be_empty }
...@@ -19,7 +19,7 @@ describe AwardEmoji do ...@@ -19,7 +19,7 @@ describe AwardEmoji do
describe '.emoji_by_category' do describe '.emoji_by_category' do
it "only contains known categories" do it "only contains known categories" do
undefined_categories = AwardEmoji.emoji_by_category.keys - AwardEmoji::CATEGORIES.keys undefined_categories = Gitlab::AwardEmoji.emoji_by_category.keys - Gitlab::AwardEmoji::CATEGORIES.keys
expect(undefined_categories).to be_empty expect(undefined_categories).to be_empty
end end
end end
......
require 'spec_helper'
describe AwardEmoji, models: true do
describe 'Associations' do
it { is_expected.to belong_to(:awardable) }
it { is_expected.to belong_to(:user) }
end
describe 'modules' do
it { is_expected.to include_module(Participable) }
end
describe "validations" do
it { is_expected.to validate_presence_of(:awardable) }
it { is_expected.to validate_presence_of(:user) }
it { is_expected.to validate_presence_of(:name) }
it { is_expected.to validate_presence_of(:awardable) }
# To circumvent a bug in the shoulda matchers
describe "scoped uniqueness validation" do
it "rejects duplicate award emoji" do
user = create(:user)
issue = create(:issue)
create(:award_emoji, user: user, awardable: issue)
new_award = AwardEmoji.new(user: user, awardable: issue, name: "thumbsup")
expect(new_award).not_to be_valid
end
end
end
end
...@@ -198,18 +198,4 @@ describe Issue, "Issuable" do ...@@ -198,18 +198,4 @@ describe Issue, "Issuable" do
to eq({ 'Author' => 'Robert', 'Assignee' => 'Douwe' }) to eq({ 'Author' => 'Robert', 'Assignee' => 'Douwe' })
end end
end end
describe "votes" do
before do
author = create :user
project = create :empty_project
issue.notes.awards.create!(note: "thumbsup", author: author, project: project)
issue.notes.awards.create!(note: "thumbsdown", author: author, project: project)
end
it "returns correct values" do
expect(issue.upvotes).to eq(1)
expect(issue.downvotes).to eq(1)
end
end
end end
...@@ -152,23 +152,6 @@ describe Note, models: true do ...@@ -152,23 +152,6 @@ describe Note, models: true do
end end
end end
describe '.grouped_awards' do
before do
create :note, note: "smile", is_award: true
create :note, note: "smile", is_award: true
end
it "returns grouped hash of notes" do
expect(Note.grouped_awards.keys.size).to eq(3)
expect(Note.grouped_awards["smile"]).to match_array(Note.all)
end
it "returns thumbsup and thumbsdown always" do
expect(Note.grouped_awards["thumbsup"]).to match_array(Note.none)
expect(Note.grouped_awards["thumbsdown"]).to match_array(Note.none)
end
end
describe '#active?' do describe '#active?' do
it 'is always true when the note has no associated diff' do it 'is always true when the note has no associated diff' do
note = build(:note) note = build(:note)
...@@ -239,11 +222,6 @@ describe Note, models: true do ...@@ -239,11 +222,6 @@ describe Note, models: true do
note = build(:note, system: true) note = build(:note, system: true)
expect(note.editable?).to be_falsy expect(note.editable?).to be_falsy
end end
it "returns false" do
note = build(:note, is_award: true, note: "smiley")
expect(note.editable?).to be_falsy
end
end end
describe "cross_reference_not_visible_for?" do describe "cross_reference_not_visible_for?" do
...@@ -270,23 +248,6 @@ describe Note, models: true do ...@@ -270,23 +248,6 @@ describe Note, models: true do
end end
end end
describe "set_award!" do
let(:merge_request) { create :merge_request }
it "converts aliases to actual name" do
note = create(:note, note: ":+1:", noteable: merge_request)
expect(note.reload.note).to eq("thumbsup")
end
it "is not an award emoji when comment is on a diff" do
note = create(:note, note: ":blowfish:", noteable: merge_request, line_code: "11d5d2e667e9da4f7f610f81d86c974b146b13bd_0_2")
note = note.reload
expect(note.note).to eq(":blowfish:")
expect(note.is_award?).to be_falsy
end
end
describe 'clear_blank_line_code!' do describe 'clear_blank_line_code!' do
it 'clears a blank line code before validation' do it 'clears a blank line code before validation' do
note = build(:note, line_code: ' ') note = build(:note, line_code: ' ')
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment