2013-01-03 02:06:07 -05:00
# == Issuable concern
2012-12-30 09:19:31 -05:00
#
2012-10-09 15:09:46 -04:00
# Contains common functionality shared between Issues and MergeRequests
2012-12-30 09:19:31 -05:00
#
# Used by Issue, MergeRequest
#
2013-01-03 02:06:07 -05:00
module Issuable
2012-08-08 21:40:57 -04:00
extend ActiveSupport :: Concern
2015-04-21 09:23:20 -04:00
include Participable
2015-10-14 10:20:11 -04:00
include Mentionable
2016-02-12 09:58:39 -05:00
include Subscribable
2015-11-26 10:16:50 -05:00
include StripAttribute
2012-08-08 21:40:57 -04:00
included do
2012-08-10 18:07:50 -04:00
belongs_to :author , class_name : " User "
belongs_to :assignee , class_name : " User "
2015-07-30 08:45:54 -04:00
belongs_to :updated_by , class_name : " User "
2012-10-30 03:22:24 -04:00
belongs_to :milestone
2012-08-10 18:07:50 -04:00
has_many :notes , as : :noteable , dependent : :destroy
2014-07-29 12:19:26 -04:00
has_many :label_links , as : :target , dependent : :destroy
has_many :labels , through : :label_links
2016-03-23 22:14:02 -04:00
has_many :todos , as : :target , dependent : :destroy
2012-08-08 21:40:57 -04:00
2012-10-08 20:10:04 -04:00
validates :author , presence : true
validates :title , presence : true , length : { within : 0 .. 255 }
2012-08-08 21:40:57 -04:00
2013-08-10 13:25:53 -04:00
scope :authored , - > ( user ) { where ( author_id : user ) }
2013-06-17 04:57:37 -04:00
scope :assigned_to , - > ( u ) { where ( assignee_id : u . id ) }
2015-11-11 09:17:12 -05:00
scope :recent , - > { reorder ( id : :desc ) }
2013-06-17 04:57:37 -04:00
scope :assigned , - > { where ( " assignee_id IS NOT NULL " ) }
scope :unassigned , - > { where ( " assignee_id IS NULL " ) }
2013-08-08 10:29:31 -04:00
scope :of_projects , - > ( ids ) { where ( project_id : ids ) }
2016-02-22 19:08:00 -05:00
scope :of_milestones , - > ( ids ) { where ( milestone_id : ids ) }
2014-02-28 15:43:16 -05:00
scope :opened , - > { with_state ( :opened , :reopened ) }
2014-06-04 11:10:53 -04:00
scope :only_opened , - > { with_state ( :opened ) }
scope :only_reopened , - > { with_state ( :reopened ) }
2014-02-28 15:43:16 -05:00
scope :closed , - > { with_state ( :closed ) }
2016-04-27 11:35:30 -04:00
scope :left_joins_milestones , - > { joins ( " LEFT OUTER JOIN milestones ON #{ table_name } .milestone_id = milestones.id " ) }
2016-04-27 16:31:57 -04:00
scope :order_milestone_due_desc , - > { left_joins_milestones . reorder ( 'CASE WHEN milestones.due_date IS NULL then 0 ELSE 1 END DESC, CASE WHEN milestones.id IS NULL then 0 ELSE 1 END DESC, milestones.due_date DESC' ) }
scope :order_milestone_due_asc , - > { left_joins_milestones . reorder ( 'CASE WHEN milestones.due_date IS NULL then 1 ELSE 0 END ASC, CASE WHEN milestones.id IS NULL then 1 ELSE 0 END ASC, milestones.due_date ASC' ) }
2013-08-19 15:10:56 -04:00
2016-04-27 11:35:30 -04:00
scope :without_label , - > { joins ( " LEFT OUTER JOIN label_links ON label_links.target_type = ' #{ name } ' AND label_links.target_id = #{ table_name } .id " ) . where ( label_links : { id : nil } ) }
2015-11-11 06:50:36 -05:00
scope :join_project , - > { joins ( :project ) }
scope :references_project , - > { references ( :project ) }
2016-04-02 08:36:41 -04:00
scope :non_archived , - > { join_project . where ( projects : { archived : false } ) }
2016-04-22 06:23:21 -04:00
scope :outer_join_milestone , - > { joins ( " LEFT OUTER JOIN milestones ON milestones.id = #{ table_name } .milestone_id " ) }
2015-11-11 06:50:36 -05:00
2012-08-08 21:40:57 -04:00
delegate :name ,
:email ,
2012-08-10 18:07:50 -04:00
to : :author ,
prefix : true
2012-08-08 21:40:57 -04:00
delegate :name ,
:email ,
2012-08-10 18:07:50 -04:00
to : :assignee ,
allow_nil : true ,
prefix : true
2012-08-08 21:40:57 -04:00
2015-10-22 09:40:02 -04:00
attr_mentionable :title , pipeline : :single_line
2015-10-14 15:29:35 -04:00
attr_mentionable :description , cache : true
2015-10-16 05:26:48 -04:00
participant :author , :assignee , :notes_with_associations
2015-11-26 10:16:50 -05:00
strip_attributes :title
2016-03-14 16:46:44 -04:00
acts_as_paranoid
2012-08-08 21:40:57 -04:00
end
2012-08-09 13:45:12 -04:00
module ClassMethods
2016-03-01 10:59:36 -05:00
# Searches for records with a matching title.
#
# This method uses ILIKE on PostgreSQL and LIKE on MySQL.
#
# query - The search query as a String
#
# Returns an ActiveRecord::Relation.
2012-08-09 13:45:12 -04:00
def search ( query )
2016-03-01 10:59:36 -05:00
where ( arel_table [ :title ] . matches ( " % #{ query } % " ) )
2012-08-09 13:45:12 -04:00
end
2014-01-14 13:49:32 -05:00
2016-03-01 10:59:36 -05:00
# Searches for records with a matching title or description.
#
# This method uses ILIKE on PostgreSQL and LIKE on MySQL.
#
# query - The search query as a String
#
# Returns an ActiveRecord::Relation.
2014-08-27 05:47:30 -04:00
def full_search ( query )
2016-03-01 10:59:36 -05:00
t = arel_table
pattern = " % #{ query } % "
where ( t [ :title ] . matches ( pattern ) . or ( t [ :description ] . matches ( pattern ) ) )
2014-08-27 05:47:30 -04:00
end
2014-01-14 13:49:32 -05:00
def sort ( method )
2015-02-05 23:21:21 -05:00
case method . to_s
when 'milestone_due_asc' then order_milestone_due_asc
when 'milestone_due_desc' then order_milestone_due_desc
2016-02-17 08:32:02 -05:00
when 'downvotes_desc' then order_downvotes_desc
when 'upvotes_desc' then order_upvotes_desc
2015-02-05 23:21:21 -05:00
else
order_by ( method )
end
2014-01-14 13:49:32 -05:00
end
2016-02-17 08:32:02 -05:00
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
2016-04-20 04:56:28 -04:00
def with_label ( title )
2016-04-22 12:32:08 -04:00
if title . is_a? ( Array ) && title . size > 1
joins ( :labels ) . where ( labels : { title : title } ) . group ( arel_table [ :id ] ) . having ( " COUNT(DISTINCT labels.title) = #{ title . size } " )
2016-04-20 04:56:28 -04:00
else
joins ( :labels ) . where ( labels : { title : title } )
end
end
2012-08-08 21:40:57 -04:00
end
def today?
Date . today == created_at . to_date
end
def new?
today? && created_at == updated_at
end
2012-10-09 18:25:29 -04:00
def is_assigned?
! ! assignee_id
end
def is_being_reassigned?
assignee_id_changed?
end
2015-10-13 03:41:46 -04:00
def open?
opened? || reopened?
end
2013-01-14 18:52:25 -05:00
def downvotes
2015-12-25 11:13:55 -05:00
notes . awards . where ( note : " thumbsdown " ) . count
2013-01-03 02:06:07 -05:00
end
2013-01-14 18:52:25 -05:00
def upvotes
2015-12-25 11:13:55 -05:00
notes . awards . where ( note : " thumbsup " ) . count
2013-01-03 02:06:07 -05:00
end
2013-05-09 19:37:47 -04:00
2016-02-23 16:59:32 -05:00
def user_notes_count
notes . user . count
end
2016-03-01 11:33:13 -05:00
def subscribed_without_subscriptions? ( user )
participants ( user ) . include? ( user )
end
2014-10-05 09:03:15 -04:00
def to_hook_data ( user )
2015-10-17 18:11:36 -04:00
hook_data = {
2013-12-03 04:31:56 -05:00
object_kind : self . class . name . underscore ,
2014-10-05 09:03:15 -04:00
user : user . hook_attrs ,
Add new data to project in push, issue, merge-request and note webhooks data
- Add `avatar_url`, `description`, `git_ssh_url`, `git_http_url`,
`path_with_namespace` and `default_branch` in `project` in push, issue,
merge-request and note webhooks data
- Deprecate the `ssh_url` in favor of `git_ssh_url` and `http_url` in
favor of `git_http_url` in `project` for push, issue, merge-request and
note webhooks data
- Deprecate the `repository` key in push, issue, merge-request and
note webhooks data, use `project` instead
2016-02-06 09:20:21 -05:00
project : project . hook_attrs ,
object_attributes : hook_attrs ,
# DEPRECATED
repository : project . hook_attrs . slice ( :name , :url , :description , :homepage )
2013-12-03 04:31:56 -05:00
}
2015-10-17 18:11:36 -04:00
hook_data . merge! ( assignee : assignee . hook_attrs ) if assignee
hook_data
2013-12-03 04:31:56 -05:00
end
2014-07-30 10:17:29 -04:00
def label_names
labels . order ( 'title ASC' ) . pluck ( :title )
end
2014-08-17 16:22:01 -04:00
def remove_labels
labels . delete_all
end
2014-07-30 10:17:29 -04:00
def add_labels_by_names ( label_names )
label_names . each do | label_name |
2015-01-22 12:37:47 -05:00
label = project . labels . create_with ( color : Label :: DEFAULT_COLOR ) .
find_or_create_by ( title : label_name . strip )
2014-07-30 10:17:29 -04:00
self . labels << label
end
end
2014-11-05 15:45:18 -05:00
2015-07-24 15:31:15 -04:00
# Convert this Issuable class name to a format usable by Ability definitions
#
# Examples:
#
# issuable.class # => MergeRequest
# issuable.to_ability_name # => "merge_request"
def to_ability_name
self . class . to_s . underscore
end
2015-12-24 17:03:54 -05:00
# Returns a Hash of attributes to be used for Twitter card metadata
def card_attributes
{
'Author' = > author . try ( :name ) ,
'Assignee' = > assignee . try ( :name )
}
end
2015-10-13 05:49:01 -04:00
def notes_with_associations
notes . includes ( :author , :project )
end
2015-10-22 11:18:59 -04:00
def updated_tasks
Taskable . get_updated_tasks ( old_content : previous_changes [ 'description' ] . first ,
new_content : description )
2014-11-05 15:45:18 -05:00
end
2016-03-18 09:48:55 -04:00
##
# Method that checks if issuable can be moved to another project.
#
# Should be overridden if issuable can be moved.
#
def can_move? ( * )
false
end
2012-08-08 21:26:56 -04:00
end