kanbanworkflowstimelinescrumrubyroadmapproject-planningproject-managementopenprojectangularissue-trackerifcgantt-chartganttbug-trackerboardsbcf
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
176 lines
5.8 KiB
176 lines
5.8 KiB
#-- encoding: UTF-8
|
|
|
|
#-- copyright
|
|
# OpenProject is a project management system.
|
|
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License version 3.
|
|
#
|
|
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
|
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
|
# Copyright (C) 2010-2013 the ChiliProject Team
|
|
#
|
|
# 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.
|
|
#
|
|
# See docs/COPYRIGHT.rdoc for more details.
|
|
#++
|
|
|
|
module Relation::HierarchyPaths
|
|
extend ActiveSupport::Concern
|
|
|
|
included do
|
|
after_create :add_hierarchy_path
|
|
after_destroy :remove_hierarchy_path
|
|
after_update :update_hierarchy_path
|
|
|
|
def self.rebuild_hierarchy_paths!
|
|
execute_sql remove_hierarchy_path_sql
|
|
execute_sql add_hierarchy_path_sql
|
|
end
|
|
|
|
def self.execute_sql(sql)
|
|
ActiveRecord::Base.connection.execute sql
|
|
end
|
|
|
|
private
|
|
|
|
def add_hierarchy_path
|
|
return unless hierarchy?
|
|
|
|
self.class.execute_sql self.class.add_hierarchy_path_sql(to_id)
|
|
end
|
|
|
|
def remove_hierarchy_path
|
|
self.class.execute_sql self.class.remove_hierarchy_path_sql(to_id)
|
|
self.class.execute_sql self.class.add_hierarchy_path_sql(to_id)
|
|
end
|
|
|
|
def update_hierarchy_path
|
|
if was_hierarchy_relation?
|
|
remove_hierarchy_path
|
|
elsif now_hierarchy_relation_or_former_id_changed?
|
|
add_hierarchy_path
|
|
elsif hierarchy_relation_and_to_id_changed?
|
|
alter_hierarchy_path
|
|
end
|
|
end
|
|
|
|
def was_hierarchy_relation?
|
|
saved_change_to_relation_type? && relation_type_before_last_save == Relation::TYPE_HIERARCHY
|
|
end
|
|
|
|
def now_hierarchy_relation_or_former_id_changed?
|
|
(saved_change_to_relation_type? || saved_change_to_from_id?) && hierarchy?
|
|
end
|
|
|
|
def hierarchy_relation_and_to_id_changed?
|
|
hierarchy? && saved_change_to_to_id?
|
|
end
|
|
|
|
def alter_hierarchy_path
|
|
self.class.execute_sql self.class.remove_hierarchy_path_sql(to_id_before_last_save)
|
|
self.class.execute_sql self.class.add_hierarchy_path_sql(to_id)
|
|
end
|
|
|
|
def self.add_hierarchy_path_sql(id = nil)
|
|
if id.nil?
|
|
stmt = <<-SQL
|
|
INSERT INTO
|
|
#{hierarchy_table_name}
|
|
(work_package_id, path)
|
|
SELECT
|
|
to_id, #{add_hierarchy_agg_function} AS path
|
|
FROM
|
|
(SELECT to_id, from_id, hierarchy
|
|
FROM relations
|
|
WHERE hierarchy > 0 AND relates = 0 AND blocks = 0 AND duplicates = 0 AND includes = 0 AND requires = 0 AND follows = 0
|
|
) ordered_by_hierarchy
|
|
GROUP BY to_id
|
|
#{add_hierarchy_conflict_statement}
|
|
SQL
|
|
else
|
|
stmt = <<-SQL
|
|
INSERT INTO
|
|
#{hierarchy_table_name}
|
|
(work_package_id, path)
|
|
SELECT
|
|
to_id, #{add_hierarchy_agg_function} AS path
|
|
FROM (
|
|
SELECT to_id, from_id, hierarchy
|
|
FROM relations
|
|
WHERE to_id = #{id} AND
|
|
hierarchy > 0 AND relates = 0 AND blocks = 0 AND duplicates = 0 AND includes = 0 AND requires = 0 AND follows = 0
|
|
UNION SELECT to_id,from_id,hierarchy
|
|
FROM relations
|
|
WHERE from_id=#{id} AND
|
|
hierarchy > 0 AND relates = 0 AND blocks = 0 AND duplicates = 0 AND includes = 0 AND requires = 0 AND follows = 0
|
|
UNION SELECT b.to_id, b.from_id, b.hierarchy FROM relations a
|
|
JOIN relations b ON b.to_id = a.to_id
|
|
WHERE a.from_id = #{id} AND
|
|
a.hierarchy > 0 AND a.relates = 0 AND a.blocks = 0 AND a.duplicates = 0 AND a.includes = 0 AND a.requires = 0 AND a.follows = 0 AND
|
|
b.hierarchy > 0 AND b.relates = 0 AND b.blocks = 0 AND b.duplicates = 0 AND b.includes = 0 AND b.requires = 0 AND b.follows = 0
|
|
) AS Foo
|
|
GROUP BY to_id
|
|
#{add_hierarchy_conflict_statement}
|
|
SQL
|
|
end
|
|
stmt
|
|
end
|
|
|
|
def self.remove_hierarchy_path_sql(id = nil)
|
|
id_constraint = if id
|
|
"WHERE work_package_id = #{id}"
|
|
end
|
|
|
|
<<-SQL
|
|
DELETE FROM
|
|
#{hierarchy_table_name}
|
|
#{id_constraint}
|
|
SQL
|
|
end
|
|
|
|
def self.add_hierarchy_id_constraint(id)
|
|
if id
|
|
<<-SQL
|
|
AND (to_id = #{id}
|
|
OR to_id IN (#{Relation.hierarchy.where(from_id: id).select(:to_id).to_sql}))
|
|
SQL
|
|
end
|
|
end
|
|
|
|
def self.add_hierarchy_conflict_statement
|
|
if ActiveRecord::Base.connection.adapter_name == 'Mysql2'
|
|
"ON DUPLICATE KEY
|
|
UPDATE #{hierarchy_table_name}.path = VALUES(path)"
|
|
else
|
|
"ON CONFLICT (work_package_id)
|
|
DO UPDATE SET path = EXCLUDED.path"
|
|
end
|
|
end
|
|
|
|
def self.add_hierarchy_agg_function
|
|
if ActiveRecord::Base.connection.adapter_name == 'Mysql2'
|
|
"GROUP_CONCAT(from_id ORDER BY hierarchy DESC SEPARATOR ',')"
|
|
else
|
|
"string_agg(from_id::TEXT, ',' ORDER BY hierarchy DESC)"
|
|
end
|
|
end
|
|
|
|
def self.hierarchy_table_name
|
|
'hierarchy_paths'
|
|
end
|
|
end
|
|
end
|
|
|