2019-03-16 09:37:35 +00:00
# frozen_string_literal: true
2019-03-15 01:32:57 +00:00
2012-12-09 14:10:49 +00:00
# Redmine - project management software
2023-01-01 06:19:35 +00:00
# Copyright (C) 2006-2023 Jean-Philippe Lang
2012-12-09 14:10:49 +00:00
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class IssueQuery < Query
2012-12-09 14:44:28 +00:00
self . queried_class = Issue
2016-07-11 18:08:55 +00:00
self . view_permission = :view_issues
2012-12-09 14:44:28 +00:00
2012-12-09 14:10:49 +00:00
self . available_columns = [
2020-11-30 15:48:36 +00:00
QueryColumn . new ( :id , :sortable = > " #{ Issue . table_name } .id " ,
:default_order = > 'desc' , :caption = > '#' , :frozen = > true ) ,
2012-12-09 14:10:49 +00:00
QueryColumn . new ( :project , :sortable = > " #{ Project . table_name } .name " , :groupable = > true ) ,
QueryColumn . new ( :tracker , :sortable = > " #{ Tracker . table_name } .position " , :groupable = > true ) ,
2020-11-30 15:48:36 +00:00
QueryColumn . new ( :parent ,
:sortable = > [ " #{ Issue . table_name } .root_id " , " #{ Issue . table_name } .lft ASC " ] ,
:default_order = > 'desc' , :caption = > :field_parent_issue ) ,
2019-04-24 02:29:49 +00:00
QueryAssociationColumn . new ( :parent , :subject , :caption = > :field_parent_issue_subject ) ,
2012-12-09 14:10:49 +00:00
QueryColumn . new ( :status , :sortable = > " #{ IssueStatus . table_name } .position " , :groupable = > true ) ,
2020-11-30 15:48:36 +00:00
QueryColumn . new ( :priority , :sortable = > " #{ IssuePriority . table_name } .position " ,
:default_order = > 'desc' , :groupable = > true ) ,
2012-12-09 14:10:49 +00:00
QueryColumn . new ( :subject , :sortable = > " #{ Issue . table_name } .subject " ) ,
2020-11-30 15:48:36 +00:00
QueryColumn . new ( :author ,
:sortable = > lambda { User . fields_for_order_statement ( " authors " ) } ,
:groupable = > true ) ,
QueryColumn . new ( :assigned_to ,
:sortable = > lambda { User . fields_for_order_statement } ,
:groupable = > true ) ,
TimestampQueryColumn . new ( :updated_on , :sortable = > " #{ Issue . table_name } .updated_on " ,
:default_order = > 'desc' , :groupable = > true ) ,
2012-12-09 14:10:49 +00:00
QueryColumn . new ( :category , :sortable = > " #{ IssueCategory . table_name } .name " , :groupable = > true ) ,
2020-11-30 15:48:36 +00:00
QueryColumn . new ( :fixed_version , :sortable = > lambda { Version . fields_for_order_statement } ,
:groupable = > true ) ,
2018-12-12 19:10:28 +00:00
QueryColumn . new ( :start_date , :sortable = > " #{ Issue . table_name } .start_date " , :groupable = > true ) ,
QueryColumn . new ( :due_date , :sortable = > " #{ Issue . table_name } .due_date " , :groupable = > true ) ,
2020-11-30 15:48:36 +00:00
QueryColumn . new ( :estimated_hours , :sortable = > " #{ Issue . table_name } .estimated_hours " ,
:totalable = > true ) ,
2019-11-07 03:42:01 +00:00
QueryColumn . new (
:total_estimated_hours ,
2020-11-30 15:48:36 +00:00
:sortable = >
lambda do
" COALESCE((SELECT SUM(estimated_hours) FROM #{ Issue . table_name } subtasks " \
" WHERE #{ Issue . visible_condition ( User . current ) . gsub ( / \ bissues \ b / , 'subtasks' ) } " \
" AND subtasks.root_id = #{ Issue . table_name } .root_id " \
" AND subtasks.lft >= #{ Issue . table_name } .lft " \
" AND subtasks.rgt <= #{ Issue . table_name } .rgt), 0) "
end ,
2015-09-12 12:47:21 +00:00
:default_order = > 'desc' ) ,
2012-12-09 14:10:49 +00:00
QueryColumn . new ( :done_ratio , :sortable = > " #{ Issue . table_name } .done_ratio " , :groupable = > true ) ,
2020-11-30 15:48:36 +00:00
TimestampQueryColumn . new ( :created_on , :sortable = > " #{ Issue . table_name } .created_on " ,
:default_order = > 'desc' , :groupable = > true ) ,
TimestampQueryColumn . new ( :closed_on , :sortable = > " #{ Issue . table_name } .closed_on " ,
:default_order = > 'desc' , :groupable = > true ) ,
QueryColumn . new ( :last_updated_by ,
:sortable = > lambda { User . fields_for_order_statement ( " last_journal_user " ) } ) ,
2012-12-09 14:10:49 +00:00
QueryColumn . new ( :relations , :caption = > :label_related_issues ) ,
2017-04-04 17:07:13 +00:00
QueryColumn . new ( :attachments , :caption = > :label_attachment_plural ) ,
2017-03-05 07:58:07 +00:00
QueryColumn . new ( :description , :inline = > false ) ,
QueryColumn . new ( :last_notes , :caption = > :label_last_notes , :inline = > false )
2012-12-09 14:10:49 +00:00
]
2021-07-06 06:01:50 +00:00
has_many :projects , foreign_key : 'default_issue_query_id' , dependent : :nullify , inverse_of : 'default_issue_query'
after_update { projects . clear unless visibility == VISIBILITY_PUBLIC }
scope :for_all_projects , - > { where ( project_id : nil ) }
def self . default ( project : nil , user : User . current )
2022-01-23 11:55:44 +00:00
# user default
2022-03-21 06:10:54 +00:00
if user & . logged? && ( query_id = user . pref . default_issue_query ) . present?
2022-01-23 11:55:44 +00:00
query = find_by ( id : query_id )
2022-08-26 01:19:10 +00:00
return query if query & . visible? ( user )
2021-07-06 06:01:50 +00:00
end
2022-07-31 13:33:27 +00:00
2022-01-23 11:55:44 +00:00
# project default
2022-07-31 13:33:27 +00:00
query = project & . default_issue_query
return query if query & . visibility == VISIBILITY_PUBLIC
2022-01-23 11:55:44 +00:00
# global default
2022-07-31 13:33:27 +00:00
if ( query_id = Setting . default_issue_query ) . present?
2022-01-23 11:55:44 +00:00
query = find_by ( id : query_id )
2022-07-31 13:33:27 +00:00
return query if query & . visibility == VISIBILITY_PUBLIC
2022-01-23 11:55:44 +00:00
end
2022-07-31 13:33:27 +00:00
nil
2021-07-06 06:01:50 +00:00
end
2012-12-09 14:10:49 +00:00
def initialize ( attributes = nil , * args )
super attributes
2020-11-16 12:22:47 +00:00
self . filters || = { 'status_id' = > { :operator = > " o " , :values = > [ " " ] } }
2012-12-09 14:10:49 +00:00
end
2013-07-14 13:41:30 +00:00
def draw_relations
r = options [ :draw_relations ]
r . nil? || r == '1'
end
def draw_relations = ( arg )
options [ :draw_relations ] = ( arg == '0' ? '0' : nil )
end
def draw_progress_line
r = options [ :draw_progress_line ]
r == '1'
end
def draw_progress_line = ( arg )
options [ :draw_progress_line ] = ( arg == '1' ? '1' : nil )
end
2019-05-16 21:54:33 +00:00
def draw_selected_columns
r = options [ :draw_selected_columns ]
r == '1'
end
def draw_selected_columns = ( arg )
options [ :draw_selected_columns ] = ( arg == '1' ? '1' : nil )
end
2017-07-12 18:13:20 +00:00
def build_from_params ( params , defaults = { } )
2013-07-14 13:41:30 +00:00
super
2020-12-19 17:31:57 +00:00
self . draw_relations =
params [ :draw_relations ] ||
( params [ :query ] && params [ :query ] [ :draw_relations ] ) || options [ :draw_relations ]
self . draw_progress_line =
params [ :draw_progress_line ] ||
( params [ :query ] && params [ :query ] [ :draw_progress_line ] ) ||
options [ :draw_progress_line ]
self . draw_selected_columns =
params [ :draw_selected_columns ] ||
( params [ :query ] && params [ :query ] [ :draw_selected_columns ] ) ||
options [ :draw_progress_line ]
2013-07-14 13:41:30 +00:00
self
end
2013-02-14 20:37:17 +00:00
def initialize_available_filters
2019-11-07 03:41:50 +00:00
add_available_filter (
" status_id " ,
2020-11-16 12:22:47 +00:00
:type = > :list_status , :values = > lambda { issue_statuses_values }
2019-11-07 03:41:50 +00:00
)
add_available_filter (
" project_id " ,
2020-11-16 12:22:47 +00:00
:type = > :list , :values = > lambda { project_values }
2017-01-09 19:59:54 +00:00
) if project . nil?
2019-11-07 03:41:50 +00:00
add_available_filter (
" tracker_id " ,
2020-11-16 12:22:47 +00:00
:type = > :list , :values = > trackers . collect { | s | [ s . name , s . id . to_s ] }
2019-11-07 03:41:50 +00:00
)
add_available_filter (
" priority_id " ,
2020-11-16 12:22:47 +00:00
:type = > :list , :values = > IssuePriority . all . collect { | s | [ s . name , s . id . to_s ] }
2019-11-07 03:41:50 +00:00
)
add_available_filter (
" author_id " ,
2020-11-16 12:22:47 +00:00
:type = > :list , :values = > lambda { author_values }
2017-01-09 19:59:54 +00:00
)
2019-11-07 03:41:50 +00:00
add_available_filter (
" assigned_to_id " ,
2020-11-16 12:22:47 +00:00
:type = > :list_optional , :values = > lambda { assigned_to_values }
2017-01-09 19:59:54 +00:00
)
2019-11-07 03:41:50 +00:00
add_available_filter (
" member_of_group " ,
2020-11-16 12:22:47 +00:00
:type = > :list_optional , :values = > lambda { Group . givable . visible . collect { | g | [ g . name , g . id . to_s ] } }
2017-01-09 19:59:54 +00:00
)
2019-11-07 03:41:50 +00:00
add_available_filter (
" assigned_to_role " ,
2020-11-16 12:22:47 +00:00
:type = > :list_optional , :values = > lambda { Role . givable . collect { | r | [ r . name , r . id . to_s ] } }
2017-01-09 19:59:54 +00:00
)
2019-11-07 03:41:50 +00:00
add_available_filter (
" fixed_version_id " ,
2020-11-16 12:22:47 +00:00
:type = > :list_optional , :values = > lambda { fixed_version_values }
2019-11-07 03:41:50 +00:00
)
add_available_filter (
" fixed_version.due_date " ,
2016-10-02 11:21:11 +00:00
:type = > :date ,
:name = > l ( :label_attribute_of_fixed_version , :name = > l ( :field_effective_date ) )
2019-11-07 03:41:50 +00:00
)
add_available_filter (
" fixed_version.status " ,
2016-10-02 11:21:11 +00:00
:type = > :list ,
:name = > l ( :label_attribute_of_fixed_version , :name = > l ( :field_status ) ) ,
2020-11-16 12:22:47 +00:00
:values = > Version :: VERSION_STATUSES . map { | s | [ l ( " version_status_ #{ s } " ) , s ] }
2019-11-07 03:41:50 +00:00
)
add_available_filter (
" category_id " ,
2015-11-07 09:28:28 +00:00
:type = > :list_optional ,
2020-11-16 12:22:47 +00:00
:values = > lambda { project . issue_categories . collect { | s | [ s . name , s . id . to_s ] } }
2019-11-07 03:41:50 +00:00
) if project
2013-02-14 20:37:17 +00:00
add_available_filter " subject " , :type = > :text
2015-10-15 18:13:55 +00:00
add_available_filter " description " , :type = > :text
2021-04-21 07:01:02 +00:00
add_available_filter " notes " , :type = > :text
2013-02-14 20:37:17 +00:00
add_available_filter " created_on " , :type = > :date_past
add_available_filter " updated_on " , :type = > :date_past
2013-02-16 09:39:52 +00:00
add_available_filter " closed_on " , :type = > :date_past
2013-02-14 20:37:17 +00:00
add_available_filter " start_date " , :type = > :date
add_available_filter " due_date " , :type = > :date
add_available_filter " estimated_hours " , :type = > :float
2019-04-30 10:52:18 +00:00
if User . current . allowed_to? ( :view_time_entries , project , :global = > true )
add_available_filter " spent_time " , :type = > :float , :label = > :label_spent_time
end
2013-02-14 20:37:17 +00:00
add_available_filter " done_ratio " , :type = > :integer
2012-12-09 14:10:49 +00:00
if User . current . allowed_to? ( :set_issues_private , nil , :global = > true ) ||
User . current . allowed_to? ( :set_own_issues_private , nil , :global = > true )
2019-11-07 03:41:50 +00:00
add_available_filter (
" is_private " ,
2013-02-14 20:37:17 +00:00
:type = > :list ,
2012-12-09 14:10:49 +00:00
:values = > [ [ l ( :general_text_yes ) , " 1 " ] , [ l ( :general_text_no ) , " 0 " ] ]
2019-11-07 03:41:50 +00:00
)
2012-12-09 14:10:49 +00:00
end
2019-11-07 03:41:50 +00:00
add_available_filter (
" attachment " ,
2017-03-02 18:13:59 +00:00
:type = > :text , :name = > l ( :label_attachment )
2019-11-07 03:41:50 +00:00
)
2021-06-14 07:01:27 +00:00
add_available_filter (
" attachment_description " ,
:type = > :text , :name = > l ( :label_attachment_description )
)
2013-02-14 20:37:17 +00:00
if User . current . logged?
2019-11-07 03:41:50 +00:00
add_available_filter (
" watcher_id " ,
2020-11-16 12:22:47 +00:00
:type = > :list , :values = > lambda { watcher_values }
2019-11-07 03:41:50 +00:00
)
2013-02-14 20:37:17 +00:00
end
2019-11-07 03:41:50 +00:00
add_available_filter (
" updated_by " ,
2020-11-16 12:22:47 +00:00
:type = > :list , :values = > lambda { author_values }
2017-01-21 10:37:36 +00:00
)
2019-11-07 03:41:50 +00:00
add_available_filter (
" last_updated_by " ,
2020-11-16 12:22:47 +00:00
:type = > :list , :values = > lambda { author_values }
2017-01-21 10:37:36 +00:00
)
2017-01-09 19:59:54 +00:00
if project && ! project . leaf?
2019-11-07 03:41:50 +00:00
add_available_filter (
" subproject_id " ,
2013-02-14 20:37:17 +00:00
:type = > :list_subprojects ,
2020-11-16 12:22:47 +00:00
:values = > lambda { subproject_values }
2019-11-07 03:41:50 +00:00
)
2013-02-14 20:37:17 +00:00
end
2019-11-07 03:41:50 +00:00
add_available_filter (
" project.status " ,
2018-10-29 04:03:52 +00:00
:type = > :list ,
:name = > l ( :label_attribute_of_project , :name = > l ( :field_status ) ) ,
2020-11-16 12:22:47 +00:00
:values = > lambda { project_statuses_values }
2018-10-29 04:03:52 +00:00
) if project . nil? || ! project . leaf?
2013-02-14 20:37:17 +00:00
add_custom_fields_filters ( issue_custom_fields )
add_associations_custom_fields_filters :project , :author , :assigned_to , :fixed_version
IssueRelation :: TYPES . each do | relation_type , options |
2020-12-19 17:31:57 +00:00
add_available_filter (
relation_type , :type = > :relation , :label = > options [ :name ] ,
:values = > lambda { all_projects_values }
)
2013-02-14 20:37:17 +00:00
end
2015-06-13 10:56:44 +00:00
add_available_filter " parent_id " , :type = > :tree , :label = > :field_parent_issue
add_available_filter " child_id " , :type = > :tree , :label = > :label_subtask_plural
2013-02-14 20:37:17 +00:00
2016-05-06 18:59:36 +00:00
add_available_filter " issue_id " , :type = > :integer , :label = > :label_issue
2023-04-05 09:22:34 +00:00
add_available_filter " any_searchable " , :type = > :search
2020-11-07 12:31:18 +00:00
Tracker . disabled_core_fields ( trackers ) . each do | field |
2013-02-14 20:37:17 +00:00
delete_available_filter field
2020-11-07 12:31:18 +00:00
end
2012-12-09 14:10:49 +00:00
end
def available_columns
return @available_columns if @available_columns
2020-11-26 14:46:52 +00:00
2012-12-09 14:10:49 +00:00
@available_columns = self . class . available_columns . dup
2020-11-16 12:22:47 +00:00
@available_columns += issue_custom_fields . visible . collect { | cf | QueryCustomFieldColumn . new ( cf ) }
2012-12-09 14:10:49 +00:00
if User . current . allowed_to? ( :view_time_entries , project , :global = > true )
2017-07-20 16:51:18 +00:00
# insert the columns after total_estimated_hours or at the end
2015-09-19 07:29:08 +00:00
index = @available_columns . find_index { | column | column . name == :total_estimated_hours }
2012-12-09 14:10:49 +00:00
index = ( index ? index + 1 : - 1 )
2017-07-20 16:51:18 +00:00
subselect = " SELECT SUM(hours) FROM #{ TimeEntry . table_name } " +
" JOIN #{ Project . table_name } ON #{ Project . table_name } .id = #{ TimeEntry . table_name } .project_id " +
" WHERE ( #{ TimeEntry . visible_condition ( User . current ) } ) AND #{ TimeEntry . table_name } .issue_id = #{ Issue . table_name } .id "
2019-11-07 03:41:39 +00:00
@available_columns . insert (
index ,
QueryColumn . new ( :spent_hours ,
:sortable = > " COALESCE(( #{ subselect } ), 0) " ,
:default_order = > 'desc' ,
:caption = > :label_spent_time ,
:totalable = > true )
2012-12-09 14:10:49 +00:00
)
2017-07-20 16:51:18 +00:00
subselect = " SELECT SUM(hours) FROM #{ TimeEntry . table_name } " +
" JOIN #{ Project . table_name } ON #{ Project . table_name } .id = #{ TimeEntry . table_name } .project_id " +
" JOIN #{ Issue . table_name } subtasks ON subtasks.id = #{ TimeEntry . table_name } .issue_id " +
" WHERE ( #{ TimeEntry . visible_condition ( User . current ) } ) " +
" AND subtasks.root_id = #{ Issue . table_name } .root_id AND subtasks.lft >= #{ Issue . table_name } .lft AND subtasks.rgt <= #{ Issue . table_name } .rgt "
2019-11-07 03:41:39 +00:00
@available_columns . insert (
index + 1 ,
QueryColumn . new ( :total_spent_hours ,
:sortable = > " COALESCE(( #{ subselect } ), 0) " ,
:default_order = > 'desc' ,
:caption = > :label_total_spent_time )
2015-07-05 12:16:56 +00:00
)
2012-12-09 14:10:49 +00:00
end
if User . current . allowed_to? ( :set_issues_private , nil , :global = > true ) ||
User . current . allowed_to? ( :set_own_issues_private , nil , :global = > true )
2020-12-19 17:31:57 +00:00
@available_columns <<
QueryColumn . new ( :is_private ,
:sortable = > " #{ Issue . table_name } .is_private " , :groupable = > true )
2012-12-09 14:10:49 +00:00
end
disabled_fields = Tracker . disabled_core_fields ( trackers ) . map { | field | field . sub ( / _id$ / , '' ) }
2019-10-19 08:30:15 +00:00
disabled_fields << " total_estimated_hours " if disabled_fields . include? ( " estimated_hours " )
2020-11-07 12:30:54 +00:00
@available_columns . reject! do | column |
2012-12-09 14:10:49 +00:00
disabled_fields . include? ( column . name . to_s )
2020-11-07 12:30:54 +00:00
end
2012-12-09 14:10:49 +00:00
@available_columns
end
2012-12-09 14:44:28 +00:00
def default_columns_names
@default_columns_names || = begin
default_columns = Setting . issue_list_default_columns . map ( & :to_sym )
project . present? ? default_columns : [ :project ] | default_columns
end
end
2016-07-13 19:02:48 +00:00
def default_totalable_names
Setting . issue_list_default_totals . map ( & :to_sym )
end
2017-03-13 19:17:59 +00:00
def default_sort_criteria
[ [ 'id' , 'desc' ] ]
end
2015-10-04 19:42:37 +00:00
def base_scope
Issue . visible . joins ( :status , :project ) . where ( statement )
end
2012-12-09 14:10:49 +00:00
# Returns the issue count
def issue_count
2015-10-04 19:42:37 +00:00
base_scope . count
2012-12-09 14:10:49 +00:00
rescue :: ActiveRecord :: StatementInvalid = > e
raise StatementInvalid . new ( e . message )
end
2015-10-09 09:02:11 +00:00
# Returns sum of all the issue's estimated_hours
def total_for_estimated_hours ( scope )
2015-10-09 12:44:08 +00:00
map_total ( scope . sum ( :estimated_hours ) ) { | t | t . to_f . round ( 2 ) }
2015-10-04 19:42:37 +00:00
end
2015-10-09 09:02:11 +00:00
# Returns sum of all the issue's time entries hours
def total_for_spent_hours ( scope )
2017-07-20 16:51:18 +00:00
total = scope . joins ( :time_entries ) .
where ( TimeEntry . visible_condition ( User . current ) ) .
sum ( " #{ TimeEntry . table_name } .hours " )
2015-10-09 12:44:08 +00:00
map_total ( total ) { | t | t . to_f . round ( 2 ) }
2012-12-09 14:10:49 +00:00
end
# Returns the issues
# Valid options are :order, :offset, :limit, :include, :conditions
def issues ( options = { } )
2017-03-13 19:17:59 +00:00
order_option = [ group_by_sort_order , ( options [ :order ] || sort_clause ) ] . flatten . reject ( & :blank? )
2019-06-20 06:13:11 +00:00
# The default order of IssueQuery is issues.id DESC(by IssueQuery#default_sort_criteria)
unless [ " #{ Issue . table_name } .id ASC " , " #{ Issue . table_name } .id DESC " ] . any? { | i | order_option . include? ( i ) }
order_option << " #{ Issue . table_name } .id DESC "
end
2012-12-09 14:10:49 +00:00
2021-06-19 02:16:38 +00:00
scope = base_scope .
2017-03-02 18:16:54 +00:00
preload ( :priority ) .
2013-06-12 19:13:25 +00:00
includes ( ( [ :status , :project ] + ( options [ :include ] || [ ] ) ) . uniq ) .
where ( options [ :conditions ] ) .
order ( order_option ) .
joins ( joins_for_order_statement ( order_option . join ( ',' ) ) ) .
limit ( options [ :limit ] ) .
2013-10-15 16:38:17 +00:00
offset ( options [ :offset ] )
2020-12-19 17:31:57 +00:00
scope =
scope . preload (
[ :tracker , :author , :assigned_to , :fixed_version ,
:category , :attachments ] & columns . map ( & :name )
)
2017-01-18 12:54:22 +00:00
if has_custom_field_column?
scope = scope . preload ( :custom_values )
2014-02-22 11:21:05 +00:00
end
2013-10-15 16:38:17 +00:00
2014-10-22 17:37:16 +00:00
issues = scope . to_a
2012-12-09 14:10:49 +00:00
if has_column? ( :spent_hours )
Issue . load_visible_spent_hours ( issues )
end
2015-07-05 12:38:39 +00:00
if has_column? ( :total_spent_hours )
Issue . load_visible_total_spent_hours ( issues )
end
2017-03-05 07:42:52 +00:00
if has_column? ( :last_updated_by )
Issue . load_visible_last_updated_by ( issues )
end
2012-12-09 14:10:49 +00:00
if has_column? ( :relations )
Issue . load_visible_relations ( issues )
end
2017-03-05 07:58:07 +00:00
if has_column? ( :last_notes )
Issue . load_visible_last_notes ( issues )
end
2012-12-09 14:10:49 +00:00
issues
rescue :: ActiveRecord :: StatementInvalid = > e
raise StatementInvalid . new ( e . message )
end
# Returns the issues ids
def issue_ids ( options = { } )
2017-03-13 19:17:59 +00:00
order_option = [ group_by_sort_order , ( options [ :order ] || sort_clause ) ] . flatten . reject ( & :blank? )
2019-06-20 06:13:11 +00:00
# The default order of IssueQuery is issues.id DESC(by IssueQuery#default_sort_criteria)
unless [ " #{ Issue . table_name } .id ASC " , " #{ Issue . table_name } .id DESC " ] . any? { | i | order_option . include? ( i ) }
order_option << " #{ Issue . table_name } .id DESC "
end
2012-12-09 14:10:49 +00:00
2021-06-19 02:16:38 +00:00
base_scope .
2013-06-12 19:13:25 +00:00
includes ( ( [ :status , :project ] + ( options [ :include ] || [ ] ) ) . uniq ) .
2014-10-22 17:37:16 +00:00
references ( ( [ :status , :project ] + ( options [ :include ] || [ ] ) ) . uniq ) .
2013-06-12 19:13:25 +00:00
where ( options [ :conditions ] ) .
order ( order_option ) .
joins ( joins_for_order_statement ( order_option . join ( ',' ) ) ) .
limit ( options [ :limit ] ) .
offset ( options [ :offset ] ) .
2014-10-22 17:37:16 +00:00
pluck ( :id )
2012-12-09 14:10:49 +00:00
rescue :: ActiveRecord :: StatementInvalid = > e
raise StatementInvalid . new ( e . message )
end
# Returns the journals
# Valid options are :order, :offset, :limit
def journals ( options = { } )
2013-06-12 19:13:25 +00:00
Journal . visible .
joins ( :issue = > [ :project , :status ] ) .
where ( statement ) .
order ( options [ :order ] ) .
limit ( options [ :limit ] ) .
offset ( options [ :offset ] ) .
preload ( :details , :user , { :issue = > [ :project , :author , :tracker , :status ] } ) .
2014-10-22 17:37:16 +00:00
to_a
2012-12-09 14:10:49 +00:00
rescue :: ActiveRecord :: StatementInvalid = > e
raise StatementInvalid . new ( e . message )
end
# Returns the versions
# Valid options are :conditions
def versions ( options = { } )
2013-06-12 19:13:25 +00:00
Version . visible .
where ( project_statement ) .
where ( options [ :conditions ] ) .
includes ( :project ) .
2014-10-22 17:37:16 +00:00
references ( :project ) .
to_a
2012-12-09 14:10:49 +00:00
rescue :: ActiveRecord :: StatementInvalid = > e
raise StatementInvalid . new ( e . message )
end
2012-12-09 14:44:28 +00:00
2021-04-21 07:01:02 +00:00
def sql_for_notes_field ( field , operator , value )
subquery = " SELECT 1 FROM #{ Journal . table_name } " +
" WHERE #{ Journal . table_name } .journalized_type='Issue' AND #{ Journal . table_name } .journalized_id= #{ Issue . table_name } .id " +
" AND ( #{ sql_for_field field , operator . sub ( / ^! / , '' ) , value , Journal . table_name , 'notes' } ) " +
" AND ( #{ Journal . visible_notes_condition ( User . current , :skip_pre_condition = > true ) } ) "
" #{ / ^ ! / .match?(operator) ? "NOT EXISTS" : "EXISTS"} ( #{ subquery } )"
end
2017-01-19 20:16:49 +00:00
def sql_for_updated_by_field ( field , operator , value )
neg = ( operator == '!' ? 'NOT' : '' )
subquery = " SELECT 1 FROM #{ Journal . table_name } " +
" WHERE #{ Journal . table_name } .journalized_type='Issue' AND #{ Journal . table_name } .journalized_id= #{ Issue . table_name } .id " +
" AND ( #{ sql_for_field field , '=' , value , Journal . table_name , 'user_id' } ) " +
" AND ( #{ Journal . visible_notes_condition ( User . current , :skip_pre_condition = > true ) } ) "
" #{ neg } EXISTS ( #{ subquery } ) "
end
def sql_for_last_updated_by_field ( field , operator , value )
neg = ( operator == '!' ? 'NOT' : '' )
subquery = " SELECT 1 FROM #{ Journal . table_name } sj " +
" WHERE sj.journalized_type='Issue' AND sj.journalized_id= #{ Issue . table_name } .id AND ( #{ sql_for_field field , '=' , value , 'sj' , 'user_id' } ) " +
2019-03-07 08:05:03 +00:00
" AND sj.id IN (SELECT MAX( #{ Journal . table_name } .id) FROM #{ Journal . table_name } " +
2017-01-19 20:16:49 +00:00
" WHERE #{ Journal . table_name } .journalized_type='Issue' AND #{ Journal . table_name } .journalized_id= #{ Issue . table_name } .id " +
" AND ( #{ Journal . visible_notes_condition ( User . current , :skip_pre_condition = > true ) } )) "
" #{ neg } EXISTS ( #{ subquery } ) "
end
2019-04-30 10:52:18 +00:00
def sql_for_spent_time_field ( field , operator , value )
first , second = value . first . to_f , value . second . to_f
sql_op =
case operator
2019-10-17 16:40:54 +00:00
when " = " , " >= " , " <= " then " #{ operator } #{ first } "
when " >< " then " BETWEEN #{ first } AND #{ second } "
when " * " then " > 0 "
when " !* " then " = 0 "
else
return nil
2019-04-30 10:52:18 +00:00
end
" COALESCE(( " +
" SELECT ROUND(CAST(SUM(hours) AS DECIMAL(30,3)), 2) " +
" FROM #{ TimeEntry . table_name } " +
" WHERE issue_id = #{ Issue . table_name } .id), 0) #{ sql_op } "
end
2012-12-09 14:44:28 +00:00
def sql_for_watcher_id_field ( field , operator , value )
db_table = Watcher . table_name
2020-11-16 12:22:47 +00:00
me , others = value . partition { | id | [ '0' , User . current . id . to_s ] . include? ( id ) }
2019-10-17 15:28:23 +00:00
sql =
if others . any?
" SELECT #{ Issue . table_name } .id FROM #{ Issue . table_name } " +
" INNER JOIN #{ db_table } ON #{ Issue . table_name } .id = #{ db_table } .watchable_id AND #{ db_table } .watchable_type = 'Issue' " +
" LEFT OUTER JOIN #{ Project . table_name } ON #{ Project . table_name } .id = #{ Issue . table_name } .project_id " +
" WHERE ( " +
sql_for_field ( field , '=' , me , db_table , 'user_id' ) +
') OR (' +
Project . allowed_to_condition ( User . current , :view_issue_watchers ) +
' AND ' +
sql_for_field ( field , '=' , others , db_table , 'user_id' ) +
')'
else
" SELECT #{ db_table } .watchable_id FROM #{ db_table } " +
" WHERE #{ db_table } .watchable_type='Issue' AND " +
sql_for_field ( field , '=' , me , db_table , 'user_id' )
end
2018-07-08 07:23:23 +00:00
" #{ Issue . table_name } .id #{ operator == '=' ? 'IN' : 'NOT IN' } ( #{ sql } ) "
2012-12-09 14:44:28 +00:00
end
def sql_for_member_of_group_field ( field , operator , value )
if operator == '*' # Any group
2014-09-28 14:51:08 +00:00
groups = Group . givable
2012-12-09 14:44:28 +00:00
operator = '=' # Override the operator since we want to find by assigned_to
elsif operator == " !* "
2014-09-28 14:51:08 +00:00
groups = Group . givable
2012-12-09 14:44:28 +00:00
operator = '!' # Override the operator since we want to find by assigned_to
else
2014-10-22 17:37:16 +00:00
groups = Group . where ( :id = > value ) . to_a
2012-12-09 14:44:28 +00:00
end
groups || = [ ]
2020-11-07 12:31:18 +00:00
members_of_groups = groups . inject ( [ ] ) do | user_ids , group |
2013-01-30 21:31:13 +00:00
user_ids + group . user_ids + [ group . id ]
2020-11-07 12:31:18 +00:00
end . uniq . compact . sort . collect ( & :to_s )
2012-12-09 14:44:28 +00:00
'(' + sql_for_field ( " assigned_to_id " , operator , members_of_groups , Issue . table_name , " assigned_to_id " , false ) + ')'
end
def sql_for_assigned_to_role_field ( field , operator , value )
case operator
when " * " , " !* " # Member / Not member
sw = operator == " !* " ? 'NOT' : ''
nl = operator == " !* " ? " #{ Issue . table_name } .assigned_to_id IS NULL OR " : ''
" ( #{ nl } #{ Issue . table_name } .assigned_to_id #{ sw } IN (SELECT DISTINCT #{ Member . table_name } .user_id FROM #{ Member . table_name } " +
" WHERE #{ Member . table_name } .project_id = #{ Issue . table_name } .project_id)) "
when " = " , " ! "
2020-07-04 12:14:27 +00:00
role_cond =
if value . any?
" #{ MemberRole . table_name } .role_id IN ( " + value . collect { | val | " ' #{ self . class . connection . quote_string ( val ) } ' " } . join ( " , " ) + " ) "
else
" 1=0 "
end
2012-12-09 14:44:28 +00:00
sw = operator == " ! " ? 'NOT' : ''
nl = operator == " ! " ? " #{ Issue . table_name } .assigned_to_id IS NULL OR " : ''
" ( #{ nl } #{ Issue . table_name } .assigned_to_id #{ sw } IN (SELECT DISTINCT #{ Member . table_name } .user_id FROM #{ Member . table_name } , #{ MemberRole . table_name } " +
" WHERE #{ Member . table_name } .project_id = #{ Issue . table_name } .project_id AND #{ Member . table_name } .id = #{ MemberRole . table_name } .member_id AND #{ role_cond } )) "
end
end
2016-10-02 11:21:11 +00:00
def sql_for_fixed_version_status_field ( field , operator , value )
where = sql_for_field ( field , operator , value , Version . table_name , " status " )
2022-03-24 16:52:20 +00:00
version_id_scope = project ? project . shared_versions : Version . visible
version_ids = version_id_scope . where ( where ) . pluck ( :id )
2016-10-02 11:21:11 +00:00
nl = operator == " ! " ? " #{ Issue . table_name } .fixed_version_id IS NULL OR " : ''
" ( #{ nl } #{ sql_for_field ( " fixed_version_id " , " = " , version_ids , Issue . table_name , " fixed_version_id " ) } ) "
end
def sql_for_fixed_version_due_date_field ( field , operator , value )
where = sql_for_field ( field , operator , value , Version . table_name , " effective_date " )
2022-03-24 16:52:20 +00:00
version_id_scope = project ? project . shared_versions : Version . visible
version_ids = version_id_scope . where ( where ) . pluck ( :id )
2016-10-02 11:21:11 +00:00
nl = operator == " !* " ? " #{ Issue . table_name } .fixed_version_id IS NULL OR " : ''
" ( #{ nl } #{ sql_for_field ( " fixed_version_id " , " = " , version_ids , Issue . table_name , " fixed_version_id " ) } ) "
end
2012-12-09 14:44:28 +00:00
def sql_for_is_private_field ( field , operator , value )
op = ( operator == " = " ? 'IN' : 'NOT IN' )
2020-12-19 17:31:57 +00:00
va =
value . map do | v |
v == '0' ? self . class . connection . quoted_false : self . class . connection . quoted_true
end . uniq . join ( ',' )
2012-12-09 14:44:28 +00:00
" #{ Issue . table_name } .is_private #{ op } ( #{ va } ) "
end
2017-03-02 18:13:59 +00:00
def sql_for_attachment_field ( field , operator , value )
case operator
when " * " , " !* "
e = ( operator == " * " ? " EXISTS " : " NOT EXISTS " )
" #{ e } (SELECT 1 FROM #{ Attachment . table_name } a WHERE a.container_type = 'Issue' AND a.container_id = #{ Issue . table_name } .id) "
when " ~ " , " !~ "
c = sql_contains ( " a.filename " , value . first )
e = ( operator == " ~ " ? " EXISTS " : " NOT EXISTS " )
" #{ e } (SELECT 1 FROM #{ Attachment . table_name } a WHERE a.container_type = 'Issue' AND a.container_id = #{ Issue . table_name } .id AND #{ c } ) "
2019-09-30 13:15:45 +00:00
when " ^ " , " $ "
c = sql_contains ( " a.filename " , value . first , ( operator == " ^ " ? :starts_with : :ends_with ) = > true )
" EXISTS (SELECT 1 FROM #{ Attachment . table_name } a WHERE a.container_type = 'Issue' AND a.container_id = #{ Issue . table_name } .id AND #{ c } ) "
2017-03-02 18:13:59 +00:00
end
end
2021-06-14 07:01:27 +00:00
def sql_for_attachment_description_field ( field , operator , value )
cond_description = " a.description IS NOT NULL AND a.description <> '' "
c =
case operator
when '*' , '!*'
( operator == '*' ? cond_description : " NOT ( #{ cond_description } ) " )
when '~' , '!~'
( operator == '~' ? '' : " #{ cond_description } AND " ) +
sql_contains ( 'a.description' , value . first , :match = > ( operator == '~' ) )
when '^' , '$'
sql_contains ( 'a.description' , value . first , ( operator == '^' ? :starts_with : :ends_with ) = > true )
else
'1=0'
end
" EXISTS (SELECT 1 FROM #{ Attachment . table_name } a WHERE a.container_type = 'Issue' AND a.container_id = #{ Issue . table_name } .id AND #{ c } ) "
end
2015-06-13 10:56:44 +00:00
def sql_for_parent_id_field ( field , operator , value )
case operator
when " = "
2019-02-01 23:31:52 +00:00
# accepts a comma separated list of ids
ids = value . first . to_s . scan ( / \ d+ / ) . map ( & :to_i ) . uniq
if ids . present?
" #{ Issue . table_name } .parent_id IN ( #{ ids . join ( " , " ) } ) "
else
" 1=0 "
end
2015-06-13 10:56:44 +00:00
when " ~ "
2021-04-03 01:34:45 +00:00
root_id , lft , rgt = Issue . where ( :id = > value . first . to_i ) . pick ( :root_id , :lft , :rgt )
2015-06-13 10:56:44 +00:00
if root_id && lft && rgt
" #{ Issue . table_name } .root_id = #{ root_id } AND #{ Issue . table_name } .lft > #{ lft } AND #{ Issue . table_name } .rgt < #{ rgt } "
else
" 1=0 "
end
when " !* "
" #{ Issue . table_name } .parent_id IS NULL "
when " * "
" #{ Issue . table_name } .parent_id IS NOT NULL "
end
end
def sql_for_child_id_field ( field , operator , value )
case operator
when " = "
2019-02-28 10:01:45 +00:00
# accepts a comma separated list of child ids
child_ids = value . first . to_s . scan ( / \ d+ / ) . map ( & :to_i ) . uniq
ids = Issue . where ( :id = > child_ids ) . pluck ( :parent_id ) . compact . uniq
if ids . present?
" #{ Issue . table_name } .id IN ( #{ ids . join ( " , " ) } ) "
2015-06-13 10:56:44 +00:00
else
" 1=0 "
end
when " ~ "
2021-04-03 01:34:45 +00:00
root_id , lft , rgt = Issue . where ( :id = > value . first . to_i ) . pick ( :root_id , :lft , :rgt )
2015-06-13 10:56:44 +00:00
if root_id && lft && rgt
" #{ Issue . table_name } .root_id = #{ root_id } AND #{ Issue . table_name } .lft < #{ lft } AND #{ Issue . table_name } .rgt > #{ rgt } "
else
" 1=0 "
end
when " !* "
" #{ Issue . table_name } .rgt - #{ Issue . table_name } .lft = 1 "
when " * "
" #{ Issue . table_name } .rgt - #{ Issue . table_name } .lft > 1 "
end
end
2017-01-18 14:57:14 +00:00
def sql_for_updated_on_field ( field , operator , value )
case operator
when " !* "
" #{ Issue . table_name } .updated_on = #{ Issue . table_name } .created_on "
when " * "
" #{ Issue . table_name } .updated_on > #{ Issue . table_name } .created_on "
else
sql_for_field ( " updated_on " , operator , value , Issue . table_name , " updated_on " )
end
end
2016-05-06 18:59:36 +00:00
def sql_for_issue_id_field ( field , operator , value )
2016-08-30 19:28:00 +00:00
if operator == " = "
# accepts a comma separated list of ids
ids = value . first . to_s . scan ( / \ d+ / ) . map ( & :to_i )
if ids . present?
" #{ Issue . table_name } .id IN ( #{ ids . join ( " , " ) } ) "
else
" 1=0 "
end
2016-05-06 18:59:36 +00:00
else
2016-08-30 19:28:00 +00:00
sql_for_field ( " id " , operator , value , Issue . table_name , " id " )
2016-05-06 18:59:36 +00:00
end
end
2012-12-09 14:44:28 +00:00
def sql_for_relations ( field , operator , value , options = { } )
relation_options = IssueRelation :: TYPES [ field ]
return relation_options unless relation_options
relation_type = field
join_column , target_join_column = " issue_from_id " , " issue_to_id "
if relation_options [ :reverse ] || options [ :reverse ]
relation_type = relation_options [ :reverse ] || relation_type
join_column , target_join_column = target_join_column , join_column
end
2023-03-25 06:27:30 +00:00
ids = value . first . to_s . scan ( / \ d+ / ) . map ( & :to_i ) . uniq
2019-10-17 16:46:05 +00:00
sql =
case operator
2012-12-09 14:44:28 +00:00
when " * " , " !* "
op = ( operator == " * " ? 'IN' : 'NOT IN' )
2020-11-29 12:48:31 +00:00
" #{ Issue . table_name } .id #{ op } " \
" (SELECT DISTINCT #{ IssueRelation . table_name } . #{ join_column } " \
" FROM #{ IssueRelation . table_name } " \
" WHERE #{ IssueRelation . table_name } .relation_type = " \
" ' #{ self . class . connection . quote_string ( relation_type ) } ') "
2012-12-09 14:44:28 +00:00
when " = " , " ! "
op = ( operator == " = " ? 'IN' : 'NOT IN' )
2020-11-29 12:48:31 +00:00
" #{ Issue . table_name } .id #{ op } " \
" (SELECT DISTINCT #{ IssueRelation . table_name } . #{ join_column } " \
" FROM #{ IssueRelation . table_name } " \
" WHERE #{ IssueRelation . table_name } .relation_type = " \
" ' #{ self . class . connection . quote_string ( relation_type ) } ' " \
2023-03-25 06:27:30 +00:00
" AND #{ IssueRelation . table_name } . #{ target_join_column } IN ( #{ ids . join ( " , " ) } )) "
2012-12-09 14:44:28 +00:00
when " =p " , " =!p " , " !p "
op = ( operator == " !p " ? 'NOT IN' : 'IN' )
comp = ( operator == " =!p " ? '<>' : '=' )
2020-11-29 12:48:31 +00:00
" #{ Issue . table_name } .id #{ op } " \
" (SELECT DISTINCT #{ IssueRelation . table_name } . #{ join_column } " \
" FROM #{ IssueRelation . table_name } , #{ Issue . table_name } relissues " \
" WHERE #{ IssueRelation . table_name } .relation_type = " \
" ' #{ self . class . connection . quote_string ( relation_type ) } ' " \
" AND #{ IssueRelation . table_name } . #{ target_join_column } = relissues.id " \
" AND relissues.project_id #{ comp } #{ value . first . to_i } ) "
2015-11-07 09:20:36 +00:00
when " *o " , " !o "
op = ( operator == " !o " ? 'NOT IN' : 'IN' )
2020-11-29 12:48:31 +00:00
" #{ Issue . table_name } .id #{ op } " \
" (SELECT DISTINCT #{ IssueRelation . table_name } . #{ join_column } " \
" FROM #{ IssueRelation . table_name } , #{ Issue . table_name } relissues " \
" WHERE #{ IssueRelation . table_name } .relation_type = " \
" ' #{ self . class . connection . quote_string ( relation_type ) } ' " \
" AND #{ IssueRelation . table_name } . #{ target_join_column } = relissues.id " \
" AND relissues.status_id IN " \
" (SELECT id FROM #{ IssueStatus . table_name } " \
" WHERE is_closed = #{ self . class . connection . quoted_false } )) "
2012-12-09 14:44:28 +00:00
end
if relation_options [ :sym ] == field && ! options [ :reverse ]
sqls = [ sql , sql_for_relations ( field , operator , value , :reverse = > true ) ]
2017-04-06 16:57:44 +00:00
sql = sqls . join ( [ " ! " , " !* " , " !p " , '!o' ] . include? ( operator ) ? " AND " : " OR " )
2012-12-09 14:44:28 +00:00
end
2013-07-28 15:29:31 +00:00
" ( #{ sql } ) "
2012-12-09 14:44:28 +00:00
end
2018-10-29 04:03:52 +00:00
def sql_for_project_status_field ( field , operator , value , options = { } )
sql_for_field ( field , operator , value , Project . table_name , " status " )
end
2023-04-05 09:22:34 +00:00
def sql_for_any_searchable_field ( field , operator , value )
question = value . first
# Fetch search results only from the selected and visible (sub-)projects
project_scope = Project . allowed_to ( :view_issues )
if project
projects = project_scope . where ( project_statement )
elsif has_filter? ( 'project_id' )
projects = project_scope . where (
sql_for_field ( 'project_id' , operator_for ( 'project_id' ) , values_for ( 'project_id' ) , Project . table_name , 'id' )
)
else
projects = nil
end
fetcher = Redmine :: Search :: Fetcher . new (
question , User . current , [ 'issue' ] , projects , attachments : '0'
)
ids = fetcher . result_ids . map ( & :last )
if ids . present?
sw = operator == '!~' ? 'NOT' : ''
" #{ Issue . table_name } .id #{ sw } IN ( #{ ids . join ( ',' ) } ) "
else
'1=0'
end
end
2015-10-21 18:01:11 +00:00
def find_assigned_to_id_filter_values ( values )
Principal . visible . where ( :id = > values ) . map { | p | [ p . name , p . id . to_s ] }
end
alias :find_author_id_filter_values :find_assigned_to_id_filter_values
2018-08-01 14:28:39 +00:00
IssueRelation :: TYPES . each_key do | relation_type |
2012-12-09 14:44:28 +00:00
alias_method " sql_for_ #{ relation_type } _field " . to_sym , :sql_for_relations
end
2017-01-17 19:41:35 +00:00
def joins_for_order_statement ( order_options )
joins = [ super ]
if order_options
if order_options . include? ( 'authors' )
joins << " LEFT OUTER JOIN #{ User . table_name } authors ON authors.id = #{ queried_table_name } .author_id "
end
2017-01-18 12:54:22 +00:00
if order_options . include? ( 'users' )
joins << " LEFT OUTER JOIN #{ User . table_name } ON #{ User . table_name } .id = #{ queried_table_name } .assigned_to_id "
end
2017-03-05 07:42:52 +00:00
if order_options . include? ( 'last_journal_user' )
2020-11-26 14:46:13 +00:00
joins <<
" LEFT OUTER JOIN #{ Journal . table_name } " \
" ON #{ Journal . table_name } .id = (SELECT MAX( #{ Journal . table_name } .id) " \
" FROM #{ Journal . table_name } " \
" WHERE #{ Journal . table_name } .journalized_type = 'Issue' " \
" AND #{ Journal . table_name } .journalized_id = #{ Issue . table_name } .id " \
" AND #{ Journal . visible_notes_condition ( User . current , :skip_pre_condition = > true ) } ) " \
" LEFT OUTER JOIN #{ User . table_name } last_journal_user " \
" ON last_journal_user.id = #{ Journal . table_name } .user_id "
2017-03-05 07:42:52 +00:00
end
2017-01-18 13:17:39 +00:00
if order_options . include? ( 'versions' )
2020-11-26 14:46:13 +00:00
joins <<
" LEFT OUTER JOIN #{ Version . table_name } " \
" ON #{ Version . table_name } .id = #{ queried_table_name } .fixed_version_id "
2017-01-18 12:54:22 +00:00
end
2017-01-18 13:17:39 +00:00
if order_options . include? ( 'issue_categories' )
2020-11-26 14:46:13 +00:00
joins <<
" LEFT OUTER JOIN #{ IssueCategory . table_name } " \
" ON #{ IssueCategory . table_name } .id = #{ queried_table_name } .category_id "
2017-01-18 12:54:22 +00:00
end
2017-01-18 13:17:39 +00:00
if order_options . include? ( 'trackers' )
2020-11-26 14:46:13 +00:00
joins <<
" LEFT OUTER JOIN #{ Tracker . table_name } " \
" ON #{ Tracker . table_name } .id = #{ queried_table_name } .tracker_id "
2017-01-18 12:54:22 +00:00
end
2017-01-18 13:17:39 +00:00
if order_options . include? ( 'enumerations' )
2020-11-26 14:46:13 +00:00
joins <<
" LEFT OUTER JOIN #{ IssuePriority . table_name } " \
" ON #{ IssuePriority . table_name } .id = #{ queried_table_name } .priority_id "
2017-01-18 12:54:22 +00:00
end
2017-01-17 19:41:35 +00:00
end
joins . any? ? joins . join ( ' ' ) : nil
end
2012-12-09 14:10:49 +00:00
end