2014-05-23 08:22:00 +00:00
require 'carrierwave/orm/activerecord'
2011-10-08 21:36:38 +00:00
class Issue < ActiveRecord :: Base
2013-08-21 09:16:26 +00:00
include InternalId
2015-05-03 03:11:21 +00:00
include Issuable
include Referable
2015-02-06 00:49:41 +00:00
include Sortable
2015-05-03 03:11:21 +00:00
include Taskable
2012-06-07 12:44:57 +00:00
2016-04-19 11:03:28 +00:00
DueDateStruct = Struct . new ( :title , :name ) . freeze
NoDueDate = DueDateStruct . new ( 'No Due Date' , '0' ) . freeze
AnyDueDate = DueDateStruct . new ( 'Any Due Date' , '' ) . freeze
Overdue = DueDateStruct . new ( 'Overdue' , 'overdue' ) . freeze
DueThisWeek = DueDateStruct . new ( 'Due This Week' , 'week' ) . freeze
DueThisMonth = DueDateStruct . new ( 'Due This Month' , 'month' ) . freeze
2016-03-10 14:26:56 +00:00
2013-10-02 13:18:28 +00:00
ActsAsTaggableOn . strict_case_match = true
2013-04-25 14:15:33 +00:00
belongs_to :project
2016-03-17 09:31:17 +00:00
belongs_to :moved_to , class_name : 'Issue'
2013-04-25 14:15:33 +00:00
validates :project , presence : true
2013-04-02 22:28:12 +00:00
scope :cared , - > ( user ) { where ( assignee_id : user ) }
2013-06-17 10:29:50 +00:00
scope :open_for , - > ( user ) { opened . assigned_to ( user ) }
2016-01-22 09:24:38 +00:00
scope :in_projects , - > ( project_ids ) { where ( project_id : project_ids ) }
2013-02-19 09:01:19 +00:00
2016-04-20 12:41:50 +00:00
scope :without_due_date , - > { where ( due_date : nil ) }
scope :due_before , - > ( date ) { where ( 'issues.due_date < ?' , date ) }
scope :due_between , - > ( from_date , to_date ) { where ( 'issues.due_date >= ?' , from_date ) . where ( 'issues.due_date <= ?' , to_date ) }
2016-04-19 11:10:25 +00:00
scope :order_due_date_asc , - > { reorder ( 'issues.due_date IS NULL, issues.due_date ASC' ) }
scope :order_due_date_desc , - > { reorder ( 'issues.due_date IS NULL, issues.due_date DESC' ) }
2013-02-18 13:22:18 +00:00
state_machine :state , initial : :opened do
2013-02-18 09:10:58 +00:00
event :close do
transition [ :reopened , :opened ] = > :closed
end
event :reopen do
2013-02-18 13:22:18 +00:00
transition closed : :reopened
2013-02-18 09:10:58 +00:00
end
state :opened
state :reopened
state :closed
end
2013-04-09 12:04:31 +00:00
2015-05-14 20:59:39 +00:00
def hook_attrs
attributes
end
2016-03-17 19:38:51 +00:00
def self . visible_to_user ( user )
return where ( confidential : false ) if user . blank?
return all if user . admin?
where ( 'issues.confidential = false OR (issues.confidential = true AND (issues.author_id = :user_id OR issues.assignee_id = :user_id OR issues.project_id IN(:project_ids)))' , user_id : user . id , project_ids : user . authorized_projects . select ( :id ) )
end
2015-05-03 03:11:21 +00:00
def self . reference_prefix
'#'
end
2015-05-14 20:59:39 +00:00
# Pattern used to extract `#123` issue references from text
#
# This pattern supports cross-project references.
def self . reference_pattern
2016-03-24 15:41:48 +00:00
@reference_pattern || = %r{
2015-12-01 14:51:27 +00:00
( #{Project.reference_pattern})?
#{Regexp.escape(reference_prefix)}(?<issue>\d+)
2015-05-14 20:59:39 +00:00
} x
2014-09-15 07:10:35 +00:00
end
2015-11-30 20:14:46 +00:00
def self . link_reference_pattern
2016-03-24 15:41:48 +00:00
@link_reference_pattern || = super ( " issues " , / (?<issue> \ d+) / )
2015-11-30 20:14:46 +00:00
end
2016-05-13 15:26:18 +00:00
def self . sort ( method , excluded_labels : [ ] )
2016-04-19 11:10:25 +00:00
case method . to_s
when 'due_date_asc' then order_due_date_asc
2016-05-13 15:26:18 +00:00
when 'due_date_desc' then order_due_date_desc
2016-04-19 11:10:25 +00:00
else
super
end
end
2015-05-03 03:11:21 +00:00
def to_reference ( from_project = nil )
reference = " #{ self . class . reference_prefix } #{ iid } "
if cross_project_reference? ( from_project )
reference = project . to_reference + reference
end
reference
end
2016-01-12 17:10:06 +00:00
def referenced_merge_requests ( current_user = nil )
2016-05-26 11:38:28 +00:00
ext = all_references ( current_user )
notes_with_associations . each do | object |
object . all_references ( current_user , extractor : ext )
2016-02-22 08:20:04 +00:00
end
2016-05-26 11:38:28 +00:00
ext . merge_requests . sort_by ( & :iid )
2015-12-04 19:00:07 +00:00
end
2016-04-12 06:13:15 +00:00
# All branches containing the current issue's ID, except for
2016-04-15 04:20:53 +00:00
# those with a merge request open referencing the current issue.
2016-04-12 06:13:15 +00:00
def related_branches ( current_user )
branches_with_iid = project . repository . branch_names . select do | branch |
2016-04-13 19:20:03 +00:00
branch =~ / \ A #{ iid } -(?! \ d+-stable) /i
2016-03-18 11:47:36 +00:00
end
2016-04-12 06:13:15 +00:00
branches_with_merge_request = self . referenced_merge_requests ( current_user ) . map ( & :source_branch )
branches_with_iid - branches_with_merge_request
2016-02-12 18:42:25 +00:00
end
2013-12-13 19:40:45 +00:00
# Reset issue events cache
#
# Since we do cache @event we need to reset cache in special cases:
# * when an issue is updated
# Events cache stored like events/23-20130109142513.
# The cache key includes updated_at timestamp.
# Thus it will automatically generate a new fragment
# when the event is updated because the key changes.
def reset_events_cache
2014-07-16 18:44:24 +00:00
Event . reset_event_cache_for ( self )
2013-12-13 19:40:45 +00:00
end
2014-09-20 14:06:35 +00:00
# To allow polymorphism with MergeRequest.
def source_project
project
end
2015-10-12 10:04:20 +00:00
# From all notes on this issue, we'll select the system notes about linked
# merge requests. Of those, the MRs closing `self` are returned.
2015-10-13 07:41:46 +00:00
def closed_by_merge_requests ( current_user = nil )
return [ ] unless open ?
2016-05-26 11:38:28 +00:00
ext = all_references ( current_user )
notes . system . each do | note |
note . all_references ( current_user , extractor : ext )
end
ext . merge_requests . select { | mr | mr . open? && mr . closes_issue? ( self ) }
2015-10-12 10:04:20 +00:00
end
2016-02-12 18:42:25 +00:00
2016-03-17 10:11:22 +00:00
def moved?
! moved_to . nil?
end
def can_move? ( user , to_project = nil )
if to_project
return false unless user . can? ( :admin_issue , to_project )
end
2016-03-23 08:39:37 +00:00
! moved? && persisted? &&
user . can? ( :admin_issue , self . project )
2016-03-17 10:11:22 +00:00
end
2016-03-19 17:50:15 +00:00
2016-02-12 18:42:25 +00:00
def to_branch_name
2016-04-12 04:29:01 +00:00
if self . confidential?
2016-04-19 03:52:55 +00:00
" #{ iid } -confidential-issue "
2016-04-12 04:29:01 +00:00
else
2016-04-15 04:20:53 +00:00
" #{ iid } - #{ title . parameterize } "
2016-04-12 04:29:01 +00:00
end
2016-02-12 18:42:25 +00:00
end
2016-02-17 06:11:48 +00:00
def can_be_worked_on? ( current_user )
2016-02-12 18:42:25 +00:00
! self . closed? &&
2016-02-17 06:11:48 +00:00
! self . project . forked? &&
2016-04-12 06:13:15 +00:00
self . related_branches ( current_user ) . empty? &&
2016-03-15 19:17:51 +00:00
self . closed_by_merge_requests ( current_user ) . empty?
2016-02-12 18:42:25 +00:00
end
2016-03-18 17:59:57 +00:00
def overdue?
2016-04-19 11:03:28 +00:00
due_date . try ( :past? ) || false
2016-03-18 17:59:57 +00:00
end
2011-10-08 21:36:38 +00:00
end