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.
234 lines
8.9 KiB
234 lines
8.9 KiB
# OpenProject is a project management system.
|
|
#
|
|
# Copyright (C) 2012-2013 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.
|
|
#
|
|
# See doc/COPYRIGHT.rdoc for more details.
|
|
#++
|
|
|
|
require_relative 'utils'
|
|
|
|
module Migration::Utils
|
|
module CustomizableUtils
|
|
MissingCustomValue = Struct.new(:journaled_id,
|
|
:journaled_type,
|
|
:custom_field_id,
|
|
:value,
|
|
:last_version)
|
|
|
|
def add_missing_customizable_journals
|
|
delete_invalid_work_package_custom_values
|
|
|
|
result = missing_custom_values
|
|
|
|
repair_journals(result)
|
|
end
|
|
|
|
def repair_customizable_journal_entries(journal_type, legacy_journal_type)
|
|
result = invalid_custom_values(legacy_journal_type)
|
|
|
|
result.map { |m| m.journaled_type = journal_type }
|
|
|
|
repair_journals(result)
|
|
end
|
|
|
|
def remove_customizable_journal_entries(journal_type, legacy_journal_type)
|
|
result = invalid_custom_values(legacy_journal_type)
|
|
|
|
result.map { |m| m.journaled_type = journal_type }
|
|
|
|
remove_initial_journals(result)
|
|
end
|
|
|
|
def repair_journals(result)
|
|
result.each do |m|
|
|
journal_ids = affected_journal_ids(m.journaled_id, m.last_version, m.journaled_type)
|
|
|
|
journal_ids.each do |journal_id|
|
|
insert <<-SQL
|
|
INSERT INTO customizable_journals (journal_id, custom_field_id, value)
|
|
VALUES (#{journal_id}, #{m.custom_field_id}, '#{m.value}')
|
|
SQL
|
|
end
|
|
end
|
|
end
|
|
|
|
def remove_initial_journals(result)
|
|
result.each do |m|
|
|
journal_ids = affected_journal_ids(m.journaled_id, m.last_version, m.journaled_type)
|
|
|
|
delete <<-SQL
|
|
DELETE FROM customizable_journals
|
|
WHERE journal_id IN (#{journal_ids.join(", ")})
|
|
SQL
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
# Removes all work package custom values that are not referenced by the
|
|
# work package's project AND type.
|
|
def delete_invalid_work_package_custom_values
|
|
if mysql?
|
|
delete <<-SQL
|
|
DELETE cv.* FROM custom_values AS cv
|
|
JOIN work_packages AS w ON (w.id = cv.customized_id AND cv.customized_type = 'WorkPackage')
|
|
JOIN custom_fields AS cf ON (cv.custom_field_id = cf.id)
|
|
JOIN projects AS p ON (w.project_id = p.id)
|
|
LEFT JOIN custom_fields_projects AS cfp ON (cv.custom_field_id = cfp.custom_field_id AND w.project_id = cfp.project_id)
|
|
LEFT JOIN custom_fields_types AS cft ON (cv.custom_field_id = cft.custom_field_id AND w.type_id = cft.type_id)
|
|
WHERE (cfp.project_id IS NULL AND cf.is_for_all = FALSE)
|
|
OR cft.type_id IS NULL
|
|
SQL
|
|
else
|
|
delete <<-SQL
|
|
DELETE FROM custom_values AS cvd
|
|
WHERE EXISTS
|
|
(
|
|
SELECT w.id, cf.id, cfp.project_id, p.name, cft.type_id
|
|
FROM work_packages AS w
|
|
JOIN custom_values AS cv ON (w.id = cv.customized_id AND cv.customized_type = 'WorkPackage')
|
|
JOIN custom_fields AS cf ON (cv.custom_field_id = cf.id)
|
|
JOIN projects AS p ON (w.project_id = p.id)
|
|
LEFT JOIN custom_fields_projects AS cfp ON (cv.custom_field_id = cfp.custom_field_id AND w.project_id = cfp.project_id)
|
|
LEFT JOIN custom_fields_types AS cft ON (cv.custom_field_id = cft.custom_field_id AND w.type_id = cft.type_id)
|
|
WHERE (cfp.project_id IS NULL AND cf.is_for_all = FALSE
|
|
OR cft.type_id IS NULL)
|
|
AND cv.id = cvd.id
|
|
);
|
|
SQL
|
|
end
|
|
end
|
|
|
|
def missing_custom_values
|
|
result = select_all <<-SQL
|
|
SELECT tmp.customized_id,
|
|
tmp.customized_type,
|
|
tmp.custom_field_id,
|
|
tmp.current_value,
|
|
tmp.last_version,
|
|
tmp.journal_value
|
|
FROM (
|
|
SELECT cv.customized_id,
|
|
cv.customized_type,
|
|
cv.custom_field_id,
|
|
cv.value AS current_value,
|
|
MAX(j.version) AS last_version,
|
|
cj.value AS journal_value
|
|
FROM custom_values AS cv
|
|
JOIN journals AS j ON (cv.customized_id = j.journable_id AND cv.customized_type = j.journable_type AND cv.value <> '')
|
|
LEFT JOIN customizable_journals AS cj ON (j.id = cj.journal_id AND cv.custom_field_id = cj.custom_field_id)
|
|
GROUP BY cv.customized_id,
|
|
cv.customized_type,
|
|
cv.custom_field_id,
|
|
cv.value,
|
|
cj.value
|
|
) AS tmp
|
|
WHERE tmp.last_version = (SELECT MAX(version) AS last_version
|
|
FROM journals AS j
|
|
WHERE j.journable_id = tmp.customized_id AND j.journable_type = tmp.customized_type
|
|
GROUP BY j.journable_id, j.journable_type)
|
|
AND tmp.journal_value IS NULL
|
|
SQL
|
|
|
|
result.collect { |row| MissingCustomValue.new(row['customized_id'],
|
|
row['customized_type'],
|
|
row['custom_field_id'],
|
|
row['current_value'],
|
|
row['last_version']) }
|
|
end
|
|
|
|
COLUMNS = ['changed_data', 'version', 'journaled_id']
|
|
|
|
def invalid_custom_values(legacy_journal_type)
|
|
result = []
|
|
|
|
update_column_values('legacy_journals',
|
|
COLUMNS,
|
|
find_work_package_with_missing_initial_custom_values(legacy_journal_type,
|
|
result),
|
|
journal_filter(legacy_journal_type))
|
|
|
|
result.flatten
|
|
end
|
|
|
|
def journal_filter(legacy_journal_type)
|
|
"type = '#{legacy_journal_type}' AND changed_data LIKE '%custom_values%'"
|
|
end
|
|
|
|
def find_work_package_with_missing_initial_custom_values(legacy_journal_type, result)
|
|
Proc.new do |row|
|
|
missing_entries = missing_initial_customizable_journals(legacy_journal_type,
|
|
row['id'],
|
|
row['journaled_id'],
|
|
row['version'],
|
|
row['changed_data'])
|
|
|
|
result << missing_entries unless missing_entries.empty?
|
|
|
|
UpdateResult.new(row, false)
|
|
end
|
|
end
|
|
|
|
def missing_initial_customizable_journals(legacy_journal_type, journal_id, journaled_id, version, changed_data)
|
|
removed_customvalues = parse_custom_value_changes(changed_data)
|
|
|
|
missing_entries = missing_initial_custom_value_entries(legacy_journal_type,
|
|
journaled_id,
|
|
version,
|
|
removed_customvalues)
|
|
|
|
missing_entries.map { |e| MissingCustomValue.new(journaled_id,
|
|
nil,
|
|
e[:id],
|
|
e[:value],
|
|
version.to_i - 1) }
|
|
end
|
|
|
|
#############################################
|
|
# Matches custom value changed of the form: #
|
|
# #
|
|
# custom_values<id>: #
|
|
# - "<old_value>" #
|
|
# - "<new_value>" #
|
|
#############################################
|
|
CUSTOM_VALUE_CHANGE_REGEX = /custom_values(?<id>\d+): \n-\s"(?<old_value>.*)"\n-\s"(?<new_value>.*)"$/
|
|
|
|
def parse_custom_value_changes(changed_data)
|
|
matches = changed_data.scan(CUSTOM_VALUE_CHANGE_REGEX)
|
|
|
|
matches.each_with_object([]) { |m, l| l << { id: m[0], value: m[1] } }
|
|
end
|
|
|
|
def missing_initial_custom_value_entries(legacy_journal_type, journaled_id, version, custom_values)
|
|
custom_values.select do |c|
|
|
result = select_all <<-SQL
|
|
SELECT version
|
|
FROM legacy_journals
|
|
WHERE journaled_id = #{journaled_id}
|
|
AND type = '#{legacy_journal_type}'
|
|
AND version < #{version}
|
|
AND changed_data LIKE '%custom_values#{c[:id]}:%'
|
|
AND changed_data LIKE '%- "#{c[:value]}%"'
|
|
ORDER BY version
|
|
SQL
|
|
|
|
result.empty?
|
|
end
|
|
end
|
|
|
|
def affected_journal_ids(journaled_id, last_version, journal_type)
|
|
result_set = select_all <<-SQL
|
|
SELECT id
|
|
FROM journals
|
|
WHERE journable_id = #{journaled_id}
|
|
AND journable_type = '#{journal_type}'
|
|
AND version <= #{last_version}
|
|
SQL
|
|
|
|
result_set.collect { |r| r['id'] }
|
|
end
|
|
end
|
|
end
|
|
|