????
Current Path : C:/inetpub/vhost/redmine/plugins/redmine_agile/app/models/ |
Current File : C:/inetpub/vhost/redmine/plugins/redmine_agile/app/models/agile_query.rb |
# This file is a part of Redmin Agile (redmine_agile) plugin, # Agile board plugin for redmine # # Copyright (C) 2011-2023 RedmineUP # http://www.redmineup.com/ # # redmine_agile 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 3 of the License, or # (at your option) any later version. # # redmine_agile 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 redmine_agile. If not, see <http://www.gnu.org/licenses/>. class AgileQuery < Query include Redmine::SafeAttributes attr_reader :truncated self.queried_class = Issue self.view_permission = :view_issues if Redmine::VERSION.to_s >= '3.4' self.available_columns = [ QueryColumn.new(:id, sortable: "#{Issue.table_name}.id", default_order: 'desc', caption: :label_agile_issue_id), QueryColumn.new(:project, groupable: "#{Issue.table_name}.project_id", sortable: "#{Project.table_name}.id"), QueryColumn.new(:tracker, sortable: "#{Tracker.table_name}.position", groupable: true), QueryColumn.new(:estimated_hours, sortable: "#{Issue.table_name}.estimated_hours"), QueryColumn.new(:done_ratio, sortable: "#{Issue.table_name}.done_ratio"), QueryColumn.new(:day_in_state, caption: :label_agile_day_in_state), QueryColumn.new(:parent, groupable: "#{Issue.table_name}.parent_id", sortable: "#{AgileData.table_name}.position", caption: :field_parent_issue), QueryColumn.new(:assigned_to, sortable: lambda { User.fields_for_order_statement }, groupable: "#{Issue.table_name}.assigned_to_id"), QueryColumn.new(:relations, caption: :label_related_issues), QueryColumn.new(:last_comment, caption: :label_agile_last_comment), QueryColumn.new(:story_points, caption: :label_agile_story_points) ] self.available_columns << QueryColumn.new(:checklists, caption: :label_checklist_plural) if RedmineAgile.use_checklist? def self.build_from_params(params, attributes = {}) new(attributes).build_from_params(params) end scope :visible, lambda { |*args| user = args.shift || User.current base = Project.allowed_to_condition(user, :view_issues, *args) scope = eager_load(:project).where("#{table_name}.project_id IS NULL OR (#{base})") if user.admin? scope.where("#{table_name}.visibility <> ? OR #{table_name}.user_id = ?", VISIBILITY_PRIVATE, user.id) elsif user.memberships.any? scope.where("#{table_name}.visibility = ?" + " OR (#{table_name}.visibility = ? AND #{table_name}.id IN (" + "SELECT DISTINCT q.id FROM #{table_name} q" + " INNER JOIN #{table_name_prefix}queries_roles#{table_name_suffix} qr on qr.query_id = q.id" + " INNER JOIN #{MemberRole.table_name} mr ON mr.role_id = qr.role_id" + " INNER JOIN #{Member.table_name} m ON m.id = mr.member_id AND m.user_id = ?" + " WHERE q.project_id IS NULL OR q.project_id = m.project_id))" + " OR #{table_name}.user_id = ?", VISIBILITY_PUBLIC, VISIBILITY_ROLES, user.id, user.id) elsif user.logged? scope.where("#{table_name}.visibility = ? OR #{table_name}.user_id = ?", VISIBILITY_PUBLIC, user.id) else scope.where("#{table_name}.visibility = ?", VISIBILITY_PUBLIC) end } def initialize(attributes = nil, *args) super attributes unless Redmine::VERSION.to_s > '2.4' self.filters ||= { 'status_id' => { operator: '*', values: [''] } } end self.filters ||= {} @truncated = false end def card_columns self.inline_columns.select { |c| !%w(day_in_state tracker thumbnails description assigned_to done_ratio spent_hours estimated_hours project id sub_issues checklists last_comment story_points).include?(c.name.to_s) } end def visible?(user=User.current) return true if user.admin? return false unless project.nil? || user.allowed_to?(:view_issues, project) case visibility when VISIBILITY_PUBLIC true when VISIBILITY_ROLES if project (user.roles_for_project(project) & roles).any? else Member.where(user_id: user.id).joins(:roles).where(member_roles: {role_id: roles.map(&:id)}).any? end else user == self.user end end def is_private? visibility == VISIBILITY_PRIVATE end def is_public? !is_private? end def color_base end def color_base=(value) end def default_chart end def default_chart=(value) end def chart_unit @chart_unit ||= RedmineAgile::Charts::Helper.valid_chart_unit_by(options[:chart], options[:chart_unit]) end def chart_unit=(value) options[:chart_unit] = value end 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 with_totals? end def build_from_params(params) params = params.permit!.to_h if params.is_a?(ActionController::Parameters) && Rails.version > '5.0' if params[:fields] || params[:f] self.filters = {} add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v]) else available_filters.keys.each do |field| add_short_filter(field, params[field]) if params[field] end end self.group_by = params[:group_by] || (params[:query] && params[:query][:group_by]) self.column_names = params[:c] || (params[:query] && params[:query][:column_names]) self.color_base = params[:color_base] || (params[:query] && params[:query][:color_base]) self.draw_relations = params[:draw_relations] || (params[:query] && params[:query][:draw_relations]) if params[:f_status] || params[:wp] self.options = options.merge({ :f_status => params[:f_status], :wp => params[:wp] }) end self end def initialize_available_filters principals = [] subprojects = [] versions = [] categories = [] issue_custom_fields = [] if project principals += project.principals.sort unless project.leaf? subprojects = project.descendants.visible.all principals += Principal.member_of(subprojects) end versions = project.shared_versions.all categories = project.issue_categories.all issue_custom_fields = project.all_issue_custom_fields else if all_projects.any? principals += Principal.member_of(all_projects) end versions = Version.visible.where(sharing: 'system').all issue_custom_fields = IssueCustomField.where(is_for_all: true) end principals.uniq! principals.sort! users = principals.select { |p| p.is_a?(User) } unless Redmine::VERSION.to_s > '2.4' add_available_filter 'status_id', type: :list_status, values: IssueStatus.sorted.collect{|s| [s.name, s.id.to_s] } end if project.nil? project_values = [] if User.current.logged? && User.current.memberships.any? project_values << ["<< #{l(:label_my_projects).downcase} >>", "mine"] end project_values += all_projects_values add_available_filter("project_id", type: :list, values: project_values ) unless project_values.empty? end add_available_filter "tracker_id", type: :list, values: trackers.collect{|s| [s.name, s.id.to_s] } add_available_filter "priority_id", type: :list, values: IssuePriority.all.collect{|s| [s.name, s.id.to_s] } author_values = [] author_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged? author_values += users.collect{|s| [s.name, s.id.to_s] } add_available_filter("author_id", type: :list, values: author_values ) unless author_values.empty? assigned_to_values = [] assigned_to_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged? assigned_to_values += (Setting.issue_group_assignment? ? principals : users).collect{|s| [s.name, s.id.to_s] } add_available_filter("assigned_to_id", type: :list_optional, values: assigned_to_values ) unless assigned_to_values.empty? group_values = Group.visible.all.collect {|g| [g.name, g.id.to_s] } add_available_filter("member_of_group", type: :list_optional, values: group_values ) unless group_values.empty? role_values = Role.givable.collect {|r| [r.name, r.id.to_s] } add_available_filter("assigned_to_role", type: :list_optional, values: role_values ) unless role_values.empty? if versions.any? fixed_versions = [] fixed_versions << ["<< #{l(:label_current_version)} >>", 'current_version'] versions.sort.each{ |s| fixed_versions << ["#{s.project.name} - #{s.name}", s.id.to_s] } add_available_filter "fixed_version_id", type: :list_optional, values: fixed_versions end if categories.any? add_available_filter "category_id", type: :list_optional, values: categories.collect{|s| [s.name, s.id.to_s] } end add_available_filter "subject", type: :text add_available_filter "created_on", type: :date_past add_available_filter "updated_on", type: :date_past add_available_filter "closed_on", type: :date_past add_available_filter "start_date", type: :date add_available_filter "due_date", type: :date add_available_filter "estimated_hours", type: :float add_available_filter "done_ratio", type: :integer add_available_filter "parent_issue_id", type: :relation, values: all_projects_values add_available_filter "has_sub_issues", type: :list, values: [l(:general_text_yes), l(:general_text_no)], label: :label_agile_has_sub_issues add_available_filter "version_status", type: :list, name: l("label_attribute_of_fixed_version", name: 'status'), values: Version::VERSION_STATUSES.collect {|s| [l("version_status_#{s}"), s]} add_available_filter "parent_issue_tracker_id", type: :list, label: :label_agile_parent_issue_tracker_id, values: Tracker.pluck(:name) if subprojects.any? add_available_filter "subproject_id", type: :list_subprojects, values: subprojects.collect{|s| [s.name, s.id.to_s] } end 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| add_available_filter relation_type, type: :relation, label: options[:name], values: all_projects_values end Tracker.disabled_core_fields(trackers).each { |field| delete_available_filter field } add_available_filter "issue_id", type: :integer, label: :label_issue if User.current.allowed_to?(:set_issues_private, nil, global: true) || User.current.allowed_to?(:set_own_issues_private, nil, global: true) add_available_filter 'is_private', type: :list, values: [[l(:general_text_yes), '1'], [l(:general_text_no), '0']] end if User.current.logged? add_available_filter 'watcher_id', type: :list, values: author_values end end def available_columns return @available_columns if @available_columns @available_columns = self.class.available_columns.dup @available_columns += (project ? project.all_issue_custom_fields : IssueCustomField).visible.collect { |cf| QueryCustomFieldColumn.new(cf) } if User.current.allowed_to?(:view_time_entries, project, global: true) index = nil @available_columns.each_with_index { |column, i| index = i if column.name == :estimated_hours} index = (index ? index + 1 : -1) # insert the column after estimated_hours or at the end @available_columns.insert index, QueryColumn.new(:spent_hours, sortable: "COALESCE((SELECT SUM(hours) FROM #{TimeEntry.table_name} WHERE #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id), 0)", default_order: 'desc', caption: :label_spent_time ) end if User.current.allowed_to?(:set_issues_private, nil, global: true) || User.current.allowed_to?(:set_own_issues_private, nil, global: true) @available_columns << QueryColumn.new(:is_private, sortable: "#{Issue.table_name}.is_private") end disabled_fields = Tracker.disabled_core_fields(trackers).map { |field| field.sub(/_id$/, '')} @available_columns.reject! { |column| disabled_fields.include?(column.name.to_s) } @available_columns.reject! { |column| column.name == :done_ratio} unless Issue.use_field_for_done_ratio? @available_columns end def editable_by?(user) return false unless user # Admin can edit them all and regular users can edit their private queries return true if user.admin? || (is_private? && user_id == user.id) # Members can not edit public queries that are for all project (only admin is allowed to) is_public? && !@is_for_all && user.allowed_to?(:manage_public_agile_queries, project, global: true) end def default_columns_names @default_columns_names = RedmineAgile.default_columns.map(&:to_sym) end def has_column_name?(name) columns.detect { |c| c.name == name} end def groupable_columns groupable_method = Redmine::VERSION.to_s > '4.2' ? :groupable? : :groupable available_columns.select { |c| c.public_send(groupable_method) && !c.is_a?(QueryCustomFieldColumn) } end def sql_for_issue_id_field(field, operator, value) 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 else sql_for_field("id", operator, value, Issue.table_name, "id") end end def sql_for_watcher_id_field(field, operator, value) db_table = Watcher.table_name "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND " + sql_for_field(field, '=', value, db_table, 'user_id') + ')' end def sql_for_version_status_field(field, operator, value) sql_for_field(field, operator, value, Version.table_name, "status") end def sql_for_has_sub_issues_field(field, operator, value) cond = '' cond = 'NOT' if operator == '=' && value.include?(I18n.t(:general_text_no)) cond = 'NOT' if operator == '!' && value.include?(I18n.t(:general_text_yes)) "( #{cond} EXISTS ( SELECT * FROM #{Issue.table_name} AS subissues WHERE subissues.parent_id = issues.id ) )" end def sql_for_parent_issue_id_field(field, operator, value, options={}) value = value.first.split(',') if value.is_a? Array value = value.split(',') if value.is_a? String sql = case operator when '*', '!*', '=', '!' sql_for_field(field, operator, value, queried_table_name, 'parent_id') when '=p', '=!p', '!p' op = (operator == '!p' ? 'NOT IN' : 'IN') comp = (operator == '=!p' ? '<>' : '=') "#{Issue.table_name}.parent_id #{op} (SELECT DISTINCT #{Issue.table_name}.id FROM #{Issue.table_name} WHERE #{Issue.table_name}.project_id #{comp} #{value.first.to_i})" when '*o', '!o' op = (operator == '!o' ? 'NOT IN' : 'IN') "#{Issue.table_name}.parent_id #{op} (SELECT DISTINCT #{Issue.table_name}.id FROM #{Issue.table_name} WHERE #{Issue.table_name}.status_id IN (SELECT DISTINCT #{IssueStatus.table_name}.id FROM #{IssueStatus.table_name} WHERE is_closed=#{self.class.connection.quoted_false}))" end "(#{sql})" end def sql_for_parent_issue_tracker_id_field(field, operator, value) cond = if operator == '=' then '' else 'NOT' end selected_trackers_ids = Tracker.where(name: value).pluck(:id).join(',') "( EXISTS (SELECT * FROM #{Issue.table_name} AS parents WHERE parents.tracker_id #{cond} IN (#{selected_trackers_ids}) AND parents.id = issues.parent_id ) )" end def sql_for_member_of_group_field(field, operator, value) if operator == '*' # Any group groups = Group.all operator = '=' # Override the operator since we want to find by assigned_to elsif operator == "!*" groups = Group.all operator = '!' # Override the operator since we want to find by assigned_to else groups = Group.where(:id => value).all end groups ||= [] members_of_groups = groups.inject([]) {|user_ids, group| user_ids + group.user_ids + [group.id] }.uniq.compact.sort.collect(&:to_s) '(' + 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 "=", "!" role_cond = value.any? ? "#{MemberRole.table_name}.role_id IN (" + value.collect{|val| "'#{self.class.connection.quote_string(val)}'"}.join(",") + ")" : "1=0" 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 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 sql = case operator when "*", "!*" op = (operator == "*" ? 'IN' : 'NOT IN') "#{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)}')" when "=", "!" op = (operator == "=" ? 'IN' : 'NOT IN') "#{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)}' AND #{IssueRelation.table_name}.#{target_join_column} = #{value.first.to_i})" when "=p", "=!p", "!p" op = (operator == "!p" ? 'NOT IN' : 'IN') comp = (operator == "=!p" ? '<>' : '=') "#{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})" when "*o", "!o" op = (operator == "!o" ? 'NOT IN' : 'IN') "#{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}))" end if relation_options[:sym] == field && !options[:reverse] sqls = [sql, sql_for_relations(field, operator, value, :reverse => true)] sql = sqls.join(["!", "!*", "!p"].include?(operator) ? " AND " : " OR ") end "(#{sql})" end IssueRelation::TYPES.keys.each do |relation_type| alias_method "sql_for_#{relation_type}_field".to_sym, :sql_for_relations end def condition_for_status if Redmine::VERSION.to_s > '2.4' return { status_id: options[:f_status] || IssueStatus.where(is_closed: false) } end '1=1' end def issues(options={}) @issues_cache ||= {} return @issues_cache[options.to_s] if @issues_cache.has_key?(options.to_s) order_option = [group_by_sort_order, options[:order]].flatten.reject(&:blank?) scope = options[:scope] ? options[:scope] : issue_scope scope = scope. joins(:status). eager_load((options[:include] || []).uniq). where(options[:conditions]). order(order_option). joins(joins_for_order_statement(order_option.join(','))). limit(options[:limit]). offset(options[:offset]) scope = scope.preload(:custom_values) scope = scope.preload(:author) if has_column?(:author) if has_column_name?(:checklists) scope = scope.preload(:checklists) end if order_option.detect {|x| x.match("agile_data.position")} scope = scope.sorted_by_rank end if has_column_name?(:last_comment) journal_comment = Journal.joins(:issue).where("#{Journal.table_name}.notes <> ''"). where(:issues => {:id => issues_ids(scope)}).order("#{Journal.table_name}.id ASC") @last_comments = {} journal_comment.each do |lc| @last_comments[lc.journalized_id] = lc end end if has_column_name?(:day_in_state) @journals_for_state = Journal.joins(:details).where( :journals => { :journalized_id => issues_ids(scope), :journalized_type => "Issue" }, :journal_details => {:prop_key => 'status_id'}).order("created_on DESC") end @issues_cache[options.to_s] = scope rescue ::ActiveRecord::StatementInvalid => e raise StatementInvalid.new(e.message) end def issues_ids(scope) @issues_ids ||= scope.map(&:id) end def issues_paginator(issues, page = nil) Redmine::Pagination::Paginator.new(issues.count, 20, page) end def journals_for_state @journals_for_state end def issue_last_comment(issue, options = {}) return unless has_column_name?(:last_comment) || options[:inline_adding] return issue.last_comment unless @last_comments @last_comments[issue.id] end def board_statuses return @board_statuses if @board_statuses @board_statuses = if Redmine::VERSION.to_s > '2.4' statuses = Redmine::VERSION.to_s >= '3.4' && project ? project.rolled_up_statuses : board_issue_statuses status_filter_values = (options[:f_status] if options) if status_filter_values result_statuses = statuses.where(id: status_filter_values) else result_statuses = statuses.where(is_closed: false) end result_statuses.sorted.map do |s| s.instance_variable_set "@issue_count", self.issue_count_by_status[s.id].to_i if has_column_name?(:estimated_hours) s.instance_variable_set "@estimated_hours_sum", self.issue_count_by_estimated_hours[s.id].to_f end if RedmineAgile.use_story_points? && has_column_name?(:story_points) s.instance_variable_set "@story_points", self.issue_count_by_story_points[s.id].to_i end s end else status_filter_operator = filters.fetch("status_id", {}).fetch(:operator, nil) status_filter_values = filters.fetch("status_id", {}).fetch(:values, []) result_statuses = case status_filter_operator when "o" board_issue_statuses.where(is_closed: false).sorted when "c" board_issue_statuses.where(is_closed: true).sorted when "=" board_issue_statuses.where(id: status_filter_values).sorted when "!" board_issue_statuses.where("#{IssueStatus.table_name}.id NOT IN (" + status_filter_values.map{|val| "'#{self.class.connection.quote_string(val)}'"}.join(",") + ")").sorted else board_issue_statuses.sorted end result_statuses.map do |s| s.instance_variable_set "@issue_count", self.issue_count_by_status[s.id].to_i if has_column_name?(:estimated_hours) s.instance_variable_set "@estimated_hours_sum", self.issue_count_by_estimated_hours[s.id].to_f end s end s end @board_statuses end def board_issue_statuses return @board_issue_statuses if @board_issue_statuses status_ids = if tracker_ids = Tracker.eager_load(issues: [{ project: :versions }]).where(statement).pluck(:id) WorkflowTransition.where(tracker_id: tracker_ids).distinct.pluck(:old_status_id, :new_status_id).flatten.uniq else [] end @board_issue_statuses = IssueStatus.where(id: status_ids) end def issue_count_by_status @issue_count_by_status ||= issue_scope.group("#{Issue.table_name}.status_id").count end def issue_count_by_estimated_hours @issue_count_by_estimated_hours ||= issue_scope.group("#{Issue.table_name}.status_id").sum("estimated_hours") end def issue_count_by_story_points @issue_count_by_story_points ||= issue_scope.group("#{Issue.table_name}.status_id").sum("#{AgileData.table_name}.story_points") end def issue_board @truncated = RedmineAgile.board_items_limit <= issue_scope.count all_issues = self.issues.limit(RedmineAgile.board_items_limit).sorted_by_rank all_issues.group_by{|i| [i.status_id]} end def statement incoming_values = filters['fixed_version_id'][:values] if filters['fixed_version_id'] if values_for('fixed_version_id') == ['current_version'] && project && !current_version filters.delete('fixed_version_id') elsif values_for('fixed_version_id') && values_for('fixed_version_id').include?('current_version') && project # convert identifier of current version to integer filters['fixed_version_id'][:values] = incoming_values.map { |el| el == 'current_version' ? current_version.id.to_s : el } end clauses = super # return of incoming filter for correct value in a select on a form filters['fixed_version_id'][:values] = incoming_values if incoming_values clauses end private def base_agile_query_scope Issue.visible .eager_load(:status, :project, :assigned_to, :tracker, :priority, :category, :fixed_version, :agile_data) .where(agile_projects) .where(statement) .where(condition_for_status) end def agile_projects return '1=1' unless project p_ids = [project.id] p_ids += project.descendants.select { |sub| sub.module_enabled?('agile') }.map(&:id) if Setting.display_subprojects_issues? || has_filter?('subproject_id') p_ids.any? ? "#{Project.table_name}.id IN (#{p_ids.join(',')})" : '1=0' end def issue_scope return @agile_scope if @agile_scope @agile_scope = base_agile_query_scope @agile_scope end def project_statement return super end def current_version return @current_version if @current_version versions = project.shared_versions.open.where("LOWER(#{Version.table_name}.name) NOT LIKE LOWER(?)", 'backlog') versions -= versions.select(&:completed?).reverse @current_version = versions.to_a.uniq.sort.first end end