2014-05-23 04:22:00 -04:00
require 'carrierwave/orm/activerecord'
2011-10-08 17:36:38 -04:00
class Issue < ActiveRecord :: Base
2013-08-21 05:16:26 -04:00
include InternalId
2015-05-02 23:11:21 -04:00
include Issuable
include Referable
2015-02-05 19:49:41 -05:00
include Sortable
2015-05-02 23:11:21 -04:00
include Taskable
2012-06-07 08:44:57 -04:00
2016-04-19 07:03:28 -04: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 09:26:56 -05:00
2013-10-02 09:18:28 -04:00
ActsAsTaggableOn . strict_case_match = true
2013-04-25 10:15:33 -04:00
belongs_to :project
2016-03-17 05:31:17 -04:00
belongs_to :moved_to , class_name : 'Issue'
2013-04-25 10:15:33 -04:00
validates :project , presence : true
2013-04-02 18:28:12 -04:00
scope :cared , - > ( user ) { where ( assignee_id : user ) }
2013-06-17 06:29:50 -04:00
scope :open_for , - > ( user ) { opened . assigned_to ( user ) }
2016-01-22 04:24:38 -05:00
scope :in_projects , - > ( project_ids ) { where ( project_id : project_ids ) }
2013-02-19 04:01:19 -05:00
2016-04-20 08:41:50 -04: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 07:10:25 -04: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 08:22:18 -05:00
state_machine :state , initial : :opened do
2013-02-18 04:10:58 -05:00
event :close do
transition [ :reopened , :opened ] = > :closed
end
event :reopen do
2013-02-18 08:22:18 -05:00
transition closed : :reopened
2013-02-18 04:10:58 -05:00
end
state :opened
state :reopened
state :closed
end
2013-04-09 08:04:31 -04:00
2015-05-14 16:59:39 -04:00
def hook_attrs
attributes
end
2016-03-17 15:38:51 -04: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-02 23:11:21 -04:00
def self . reference_prefix
'#'
end
2015-05-14 16:59:39 -04:00
# Pattern used to extract `#123` issue references from text
#
# This pattern supports cross-project references.
def self . reference_pattern
2016-03-24 11:41:48 -04:00
@reference_pattern || = %r{
2015-12-01 09:51:27 -05:00
( #{Project.reference_pattern})?
#{Regexp.escape(reference_prefix)}(?<issue>\d+)
2015-05-14 16:59:39 -04:00
} x
2014-09-15 03:10:35 -04:00
end
2015-11-30 15:14:46 -05:00
def self . link_reference_pattern
2016-03-24 11:41:48 -04:00
@link_reference_pattern || = super ( " issues " , / (?<issue> \ d+) / )
2015-11-30 15:14:46 -05:00
end
2016-04-19 07:10:25 -04:00
def self . sort ( method )
case method . to_s
when 'due_date_asc' then order_due_date_asc
when 'due_date_desc' then order_due_date_desc
else
super
end
end
2015-05-02 23:11:21 -04: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 12:10:06 -05:00
def referenced_merge_requests ( current_user = nil )
2016-03-15 15:17:51 -04:00
@referenced_merge_requests || = { }
@referenced_merge_requests [ current_user ] || = begin
Gitlab :: ReferenceExtractor . lazily do
[ self , * notes ] . flat_map do | note |
note . all_references ( current_user ) . merge_requests
end
end . sort_by ( & :iid ) . uniq
2016-02-22 03:20:04 -05:00
end
2015-12-04 14:00:07 -05:00
end
2016-04-12 02:13:15 -04:00
# All branches containing the current issue's ID, except for
2016-04-15 00:20:53 -04:00
# those with a merge request open referencing the current issue.
2016-04-12 02:13:15 -04:00
def related_branches ( current_user )
branches_with_iid = project . repository . branch_names . select do | branch |
2016-04-13 15:20:03 -04:00
branch =~ / \ A #{ iid } -(?! \ d+-stable) /i
2016-03-18 07:47:36 -04:00
end
2016-04-12 02:13:15 -04:00
branches_with_merge_request = self . referenced_merge_requests ( current_user ) . map ( & :source_branch )
branches_with_iid - branches_with_merge_request
2016-02-12 13:42:25 -05:00
end
2013-12-13 14:40:45 -05: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 14:44:24 -04:00
Event . reset_event_cache_for ( self )
2013-12-13 14:40:45 -05:00
end
2014-09-20 10:06:35 -04:00
# To allow polymorphism with MergeRequest.
def source_project
project
end
2015-10-12 06:04:20 -04: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 03:41:46 -04:00
def closed_by_merge_requests ( current_user = nil )
return [ ] unless open ?
2015-10-12 06:04:20 -04:00
notes . system . flat_map do | note |
2015-10-13 03:41:46 -04:00
note . all_references ( current_user ) . merge_requests
end . uniq . select { | mr | mr . open? && mr . closes_issue? ( self ) }
2015-10-12 06:04:20 -04:00
end
2016-02-12 13:42:25 -05:00
2016-03-17 06:11:22 -04: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 04:39:37 -04:00
! moved? && persisted? &&
user . can? ( :admin_issue , self . project )
2016-03-17 06:11:22 -04:00
end
2016-03-19 13:50:15 -04:00
2016-02-12 13:42:25 -05:00
def to_branch_name
2016-04-12 00:29:01 -04:00
if self . confidential?
2016-04-18 23:52:55 -04:00
" #{ iid } -confidential-issue "
2016-04-12 00:29:01 -04:00
else
2016-04-15 00:20:53 -04:00
" #{ iid } - #{ title . parameterize } "
2016-04-12 00:29:01 -04:00
end
2016-02-12 13:42:25 -05:00
end
2016-02-17 01:11:48 -05:00
def can_be_worked_on? ( current_user )
2016-02-12 13:42:25 -05:00
! self . closed? &&
2016-02-17 01:11:48 -05:00
! self . project . forked? &&
2016-04-12 02:13:15 -04:00
self . related_branches ( current_user ) . empty? &&
2016-03-15 15:17:51 -04:00
self . closed_by_merge_requests ( current_user ) . empty?
2016-02-12 13:42:25 -05:00
end
2016-03-18 13:59:57 -04:00
def overdue?
2016-04-19 07:03:28 -04:00
due_date . try ( :past? ) || false
2016-03-18 13:59:57 -04:00
end
2011-10-08 17:36:38 -04:00
end