173 lines
6.5 KiB
Ruby
173 lines
6.5 KiB
Ruby
|
require 'uuidtools'
|
||
|
require_dependency 'carto/uuidhelper'
|
||
|
require_dependency 'carto/query_rewriter'
|
||
|
|
||
|
module Carto
|
||
|
# Export/import is versioned, but importing should generate a model tree that can be persisted by this class
|
||
|
class VisualizationsExportPersistenceService
|
||
|
include Carto::UUIDHelper
|
||
|
include Carto::QueryRewriter
|
||
|
|
||
|
# `full_restore = true` means keeping the visualization id and permission. Its intended use is to restore an exact
|
||
|
# copy of a visualization (e.g: user migration). When it's `false` it will skip restoring ids and permissions. This
|
||
|
# is the default, and how it's used by the Import API to allow to import a visualization into a different user
|
||
|
def save_import(user, visualization, renamed_tables: {}, full_restore: false)
|
||
|
old_username = visualization.user.username if visualization.user
|
||
|
apply_user_limits(user, visualization)
|
||
|
ActiveRecord::Base.transaction do
|
||
|
visualization.id = random_uuid unless visualization.id && full_restore
|
||
|
visualization.user = user
|
||
|
|
||
|
ensure_unique_name(user, visualization)
|
||
|
|
||
|
visualization.layers.each { |layer| layer.fix_layer_user_information(old_username, user, renamed_tables) }
|
||
|
visualization.analyses.each do |analysis|
|
||
|
analysis.analysis_node.fix_analysis_node_queries(old_username, user, renamed_tables)
|
||
|
end
|
||
|
|
||
|
saved_acl = visualization.permission.access_control_list if full_restore
|
||
|
visualization.permission = Carto::Permission.new(owner: user, owner_username: user.username)
|
||
|
|
||
|
map = visualization.map
|
||
|
map.user = user if map
|
||
|
|
||
|
visualization.analyses.each do |analysis|
|
||
|
analysis.user_id = user.id
|
||
|
end
|
||
|
|
||
|
sync = visualization.synchronization
|
||
|
if sync
|
||
|
sync.id = random_uuid unless sync.id && full_restore
|
||
|
sync.user = user
|
||
|
sync.log.user_id = user.id if sync.log
|
||
|
end
|
||
|
|
||
|
user_table = visualization.map.user_table if map
|
||
|
if user_table
|
||
|
user_table.user = user
|
||
|
user_table.service.register_table_only = true
|
||
|
raise 'Cannot import a dataset without physical table' unless user_table.service.real_table_exists?
|
||
|
|
||
|
data_import = user_table.data_import
|
||
|
if data_import
|
||
|
existing_data_import = Carto::DataImport.where(id: data_import.id).first
|
||
|
user_table.data_import = existing_data_import if existing_data_import
|
||
|
data_import.synchronization_id = sync.id if sync
|
||
|
data_import.user_id = user.id
|
||
|
end
|
||
|
end
|
||
|
|
||
|
unless full_restore
|
||
|
visualization.mapcaps.clear
|
||
|
visualization.created_at = DateTime.now
|
||
|
visualization.updated_at = DateTime.now
|
||
|
visualization.locked = false
|
||
|
end
|
||
|
|
||
|
unless visualization.save
|
||
|
error_message = "Errors saving imported visualization: #{visualization.errors.full_messages}"
|
||
|
raise Carto::UnauthorizedError.new(error_message) if visualization.errors.include?(:privacy)
|
||
|
raise error_message
|
||
|
end
|
||
|
|
||
|
# Save permissions after visualization, in order to be able to regenerate shared_entities
|
||
|
if saved_acl
|
||
|
visualization.permission.access_control_list = saved_acl
|
||
|
visualization.permission.save!
|
||
|
end
|
||
|
|
||
|
visualization.layers.map do |layer|
|
||
|
# Flag needed because `.changed?` won't realize about options hash changes
|
||
|
changed = false
|
||
|
if layer.options.has_key?(:id)
|
||
|
layer.options[:id] = layer.id
|
||
|
changed = true
|
||
|
end
|
||
|
if layer.options.has_key?(:stat_tag)
|
||
|
layer.options[:stat_tag] = visualization.id
|
||
|
changed = true
|
||
|
end
|
||
|
layer.save if changed
|
||
|
end
|
||
|
|
||
|
if map
|
||
|
map.data_layers.each(&:register_table_dependencies)
|
||
|
|
||
|
new_user_layers = map.base_layers.select(&:custom?).select { |l| !contains_equivalent_base_layer?(user.layers, l) }
|
||
|
new_user_layers.map(&:dup).map { |l| user.layers << l }
|
||
|
data_import = map.user_table.try(:data_import)
|
||
|
if data_import
|
||
|
data_import.table_id = map.user_table.id
|
||
|
data_import.save!
|
||
|
data_import.external_data_imports.each do |edi|
|
||
|
edi.synchronization_id = sync.id if sync
|
||
|
edi.save!
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
visualization.save!
|
||
|
visualization
|
||
|
end
|
||
|
|
||
|
private
|
||
|
|
||
|
def contains_equivalent_base_layer?(layers, layer)
|
||
|
layers.any? { |l| equivalent_base_layer?(l, layer) }
|
||
|
end
|
||
|
|
||
|
def equivalent_base_layer?(layer_a, layer_b)
|
||
|
layer_a.kind == 'tiled' && layer_a.kind == layer_b.kind && layer_a.options == layer_b.options
|
||
|
end
|
||
|
|
||
|
def ensure_unique_name(user, visualization)
|
||
|
existing_names = Carto::Visualization.uniq
|
||
|
.where(user_id: user.id)
|
||
|
.where("name ~ ?", "#{Regexp.escape(visualization.name)}( Import)?( \d*)?$")
|
||
|
.where(type: visualization.type)
|
||
|
.pluck(:name)
|
||
|
if existing_names.include?(visualization.name)
|
||
|
raise 'Cannot rename a dataset during import' if visualization.canonical?
|
||
|
import_name = "#{visualization.name} Import"
|
||
|
i = 1
|
||
|
while existing_names.include?(import_name)
|
||
|
import_name = "#{visualization.name} Import #{i += 1}"
|
||
|
end
|
||
|
visualization.name = import_name
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def apply_user_limits(user, visualization)
|
||
|
can_be_private = visualization.derived? ? user.private_maps_enabled : user.private_tables_enabled
|
||
|
unless can_be_private
|
||
|
visualization.privacy = Carto::Visualization::PRIVACY_PUBLIC
|
||
|
visualization.user_table.privacy = Carto::UserTable::PRIVACY_PUBLIC if visualization.canonical?
|
||
|
end
|
||
|
# If password is not exported we must fallback to private
|
||
|
if visualization.password_protected? && !visualization.has_password?
|
||
|
visualization.privacy = Carto::Visualization::PRIVACY_PRIVATE
|
||
|
end
|
||
|
|
||
|
layers = []
|
||
|
data_layer_count = 0
|
||
|
|
||
|
if visualization.map
|
||
|
visualization.map.layers.each do |layer|
|
||
|
if layer.data_layer?
|
||
|
if data_layer_count < user.max_layers
|
||
|
layers.push(layer)
|
||
|
data_layer_count += 1
|
||
|
end
|
||
|
else
|
||
|
layers.push(layer)
|
||
|
end
|
||
|
end
|
||
|
visualization.map.layers.clear
|
||
|
visualization.map.layers_maps.clear
|
||
|
layers.each { |l| visualization.map.layers << l }
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|