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.
228 lines
6.9 KiB
228 lines
6.9 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.
|
|
#++
|
|
|
|
##
|
|
# Adds a table for storing possible values (options) for custom fields.
|
|
# If a custom field has no possible values then arbitrary values
|
|
# are allowed which may be further restriced by other means other than
|
|
# specific values.
|
|
class AddCustomOptions < ActiveRecord::Migration[5.0]
|
|
require 'globalize'
|
|
|
|
class OldCustomField < ActiveRecord::Base
|
|
self.table_name = :custom_fields
|
|
self.inheritance_column = nil
|
|
|
|
translates :name, :default_value, :possible_values
|
|
end
|
|
|
|
class OldCustomField::Translation
|
|
serialize :possible_values, Array
|
|
end
|
|
|
|
def table_name
|
|
:custom_options
|
|
end
|
|
|
|
def up
|
|
create_table table_name do |t|
|
|
t.integer :custom_field_id
|
|
t.integer :position
|
|
t.boolean :default_value
|
|
t.text :value
|
|
end
|
|
|
|
migrate_data!
|
|
|
|
add_column :custom_fields, :multi_value, :boolean, default: false
|
|
add_column :custom_fields, :default_value, :text
|
|
|
|
remove_column :custom_field_translations, :default_value
|
|
remove_column :custom_field_translations, :possible_values # replaced by custom options
|
|
end
|
|
|
|
##
|
|
# Dropping the translations for custom field default values and possible values is irreversible.
|
|
# We can just create one default translation.
|
|
def down
|
|
CustomOption.transaction do
|
|
add_column :custom_field_translations, :default_value, :text
|
|
add_column :custom_field_translations, :possible_values, :text
|
|
|
|
rollback_custom_fields!
|
|
rollback_values!
|
|
rollback_journals!
|
|
|
|
remove_column :custom_fields, :multi_value
|
|
remove_column :custom_fields, :default_value
|
|
|
|
drop_table table_name
|
|
end
|
|
end
|
|
|
|
def migrate_data!
|
|
say "Migrating #{list_custom_fields.length} custom fields of type list. This will take a while, please be patient."
|
|
CustomOption.transaction do
|
|
initialize_custom_options!
|
|
end
|
|
migrate_all_values!
|
|
end
|
|
|
|
def initialize_custom_options!
|
|
list_custom_fields.each do |custom_field|
|
|
translations = custom_field_translations[custom_field.id] = get_translations custom_field
|
|
|
|
create_custom_options_for_translations! translations, custom_field
|
|
end
|
|
end
|
|
|
|
def get_translations(custom_field)
|
|
OldCustomField::Translation
|
|
.where(custom_field_id: custom_field.id)
|
|
.order(id: :asc)
|
|
end
|
|
|
|
def create_custom_options_for_translations!(translations, custom_field)
|
|
return if translations.empty?
|
|
|
|
# we don't support translations anymore, assume first as canonical
|
|
translation = translations.first
|
|
|
|
if translation.possible_values.is_a? String
|
|
translation.possible_values = YAML.load translation.possible_values
|
|
end
|
|
|
|
translation.possible_values.each_with_index.map do |value, i|
|
|
custom_field.custom_options.create!(
|
|
value: value,
|
|
position: i + 1,
|
|
default_value: (value == translation.default_value)
|
|
)
|
|
end
|
|
end
|
|
|
|
def custom_field_translations
|
|
@custom_field_translations ||= {}
|
|
end
|
|
|
|
def migrate_all_values!
|
|
list_custom_fields.each do |custom_field|
|
|
name = custom_field.translations.first.name rescue custom_field.id
|
|
id_map = custom_values_id_map(custom_field.id)
|
|
|
|
say_with_time "Migrating CF '#{name}'" do
|
|
CustomField.transaction do
|
|
migrate_values!(custom_field, id_map)
|
|
migrate_journals!(custom_field, id_map)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def migrate_values!(custom_field, id_map)
|
|
CustomValue.where(custom_field: custom_field).each do |custom_value|
|
|
option_id = id_map[custom_value.value]
|
|
|
|
custom_value.update_columns(value: option_id.to_s) if option_id
|
|
end
|
|
end
|
|
|
|
def migrate_journals!(custom_field, id_map)
|
|
CustomizableJournal.where(custom_field: custom_field).each do |journal|
|
|
option_id = id_map[journal.value]
|
|
|
|
journal.update_columns(value: option_id.to_s) if option_id
|
|
end
|
|
end
|
|
|
|
def rollback_custom_fields!
|
|
Globalize.with_locale(Setting.default_language.to_sym) do
|
|
rollback_list_custom_fields!
|
|
rollback_other_custom_fields!
|
|
end
|
|
end
|
|
|
|
def rollback_list_custom_fields!
|
|
OldCustomField.where(field_format: "list").each do |old_custom_field|
|
|
new_custom_field = CustomField.find old_custom_field.id
|
|
|
|
old_custom_field.default_value =
|
|
new_custom_field.custom_options.select(&:default_value?).map(&:value).first
|
|
old_custom_field.possible_values =
|
|
new_custom_field.custom_options.map(&:value)
|
|
|
|
old_custom_field.save
|
|
end
|
|
end
|
|
|
|
def rollback_other_custom_fields!
|
|
OldCustomField.where.not(field_format: "list").each do |old_custom_field|
|
|
new_custom_field = CustomField.find old_custom_field.id
|
|
|
|
old_custom_field.default_value = new_custom_field.default_value
|
|
|
|
old_custom_field.save
|
|
end
|
|
end
|
|
|
|
def rollback_values!
|
|
list_custom_fields.each do |custom_field|
|
|
CustomValue.where(custom_field: custom_field).each do |custom_value|
|
|
option_value = CustomOption.find_by(id: custom_value.value, custom_field_id: custom_field.id).try(:value)
|
|
|
|
if option_value
|
|
custom_value.value = option_value
|
|
custom_value.save(validate: false) # with the new code the validation will fail as an ID is expected
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def rollback_journals!
|
|
list_custom_fields.each do |custom_field|
|
|
CustomizableJournal.where(custom_field: custom_field).each do |journal|
|
|
option_value = CustomOption.find_by(id: journal.value, custom_field_id: custom_field.id).try(:value)
|
|
|
|
journal.update! value: option_value if option_value
|
|
end
|
|
end
|
|
end
|
|
|
|
def custom_values_id_map(custom_field_id)
|
|
values = CustomOption
|
|
.where(custom_field_id: custom_field_id)
|
|
.pluck(:value, :id)
|
|
|
|
Hash[values]
|
|
end
|
|
|
|
def list_custom_fields
|
|
@list_custom_fields ||= CustomField.where(field_format: "list")
|
|
end
|
|
end
|
|
|