Skip to content

Commit 46ae55f

Browse files
committed
Adds import across env class
1 parent 09da157 commit 46ae55f

File tree

3 files changed

+134
-4
lines changed

3 files changed

+134
-4
lines changed

lib/superset/services/dashboard_loader.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module Superset
77
module Services
88
class DashboardLoader
99
include FileUtilities
10+
1011
TMP_PATH = '/tmp/superset_dashboard_imports'.freeze
1112

1213
attr_reader :dashboard_export_zip
@@ -43,6 +44,7 @@ def uuid
4344
class DashboardConfig < ::OpenStruct
4445
def config
4546
{
47+
tmp_uniq_dashboard_path: tmp_uniq_dashboard_path,
4648
dashboards: load_yamls_for('dashboards'),
4749
databases: load_yamls_for('databases'),
4850
datasets: load_yamls_for('datasets'),
@@ -59,8 +61,7 @@ def load_yamls_for(object_path, pattern_sufix: '**/*.yaml')
5961
end
6062

6163
def load_yaml_and_symbolize_keys(path)
62-
yaml = YAML.load_file(path)
63-
yaml.deep_symbolize_keys
64+
YAML.load_file(path).deep_symbolize_keys
6465
end
6566
end
6667
end
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
=begin
2+
Superset::Services::ImportDashboardAcrossEnvironments
3+
4+
This service is used to duplicate a dashboard from one environment to another.
5+
It will not create any database connections from an imported dashboard zip, therefore the target database yml configuration
6+
must already exist in the target superset environment.
7+
8+
Currently handles only 1 Database yaml file in the zip file. ( ie only 1 common database connection per dashboards datasets )
9+
10+
Requirements:
11+
- target_database_yaml_file
12+
- target_database_schema
13+
- dashboard_export_zip
14+
15+
Usage:
16+
Assuming you have exported a dashboard from the source environment and have the zip file, and have exported the target database yaml file
17+
Superset::Services::ImportDashboardAcrossEnvironments.new(
18+
target_database_yaml_file: '/tmp/database.yaml',
19+
target_database_schema: 'insert_schema_here',
20+
dashboard_export_zip: '/tmp/dashboard.zip'
21+
).perform
22+
23+
=end
24+
25+
#require 'superset/file_utilities'
26+
require 'yaml'
27+
28+
module Superset
29+
module Services
30+
class ImportDashboardAcrossEnvironments
31+
# include FileUtilities
32+
33+
attr_reader :target_database_yaml_file, :target_database_schema, :dashboard_export_zip
34+
35+
def initialize(target_database_yaml_file:, target_database_schema: ,dashboard_export_zip:)
36+
@target_database_yaml_file = target_database_yaml_file
37+
@target_database_schema = target_database_schema
38+
@dashboard_export_zip = dashboard_export_zip
39+
end
40+
41+
def perform
42+
validate_params
43+
44+
remove_source_database_config
45+
insert_target_database_file
46+
insert_target_database_config
47+
update_dataset_configs
48+
49+
create_new_dashboard_zip
50+
end
51+
52+
private
53+
54+
def dashboard_config
55+
@dashboard_config ||= Superset::Services::DashboardLoader.new(dashboard_export_zip: dashboard_export_zip).perform
56+
end
57+
58+
def remove_source_database_config
59+
return if dashboard_config[:databases].blank?
60+
previous_database_name = dashboard_config[:databases]&.first[:content][:database_name]
61+
File.delete(dashboard_config[:databases].first[:filename])
62+
63+
dashboard_config[:databases].clear
64+
end
65+
66+
def insert_target_database_file
67+
FileUtils.cp(target_database_yaml_file, File.join(dashboard_export_root_path, 'databases'))
68+
69+
pattern = File.join(dashboard_export_root_path, 'databases', '*.yaml')
70+
@new_database_yaml_file_path = Dir.glob(pattern).first
71+
end
72+
73+
def insert_target_database_config
74+
yaml_content = YAML.load_file(target_database_yaml_file).deep_symbolize_keys
75+
dashboard_config[:databases] << { filename: new_database_yaml_file_path, content: yaml_content }
76+
end
77+
78+
def update_dataset_configs
79+
dashboard_config[:datasets].each do |dataset|
80+
dataset[:content][:database_uuid] = dashboard_config[:databases].first[:content][:uuid]
81+
dataset[:content][:schema] = target_database_schema
82+
File.open(dataset[:filename], 'w') { |f| f.write dataset[:content].to_yaml }
83+
end
84+
end
85+
86+
def create_new_dashboard_zip
87+
Zip::File.open(new_zip_file, Zip::File::CREATE) do |zipfile|
88+
Dir[File.join(dashboard_export_root_path, '**', '**')].each do |file|
89+
zipfile.add(file.sub(dashboard_export_root_path + '/', File.basename(dashboard_export_root_path) + '/' ), file) if File.file?(file)
90+
end
91+
end
92+
new_zip_file
93+
end
94+
95+
def new_zip_file
96+
new_database_name = dashboard_config[:databases].first[:content][:database_name]
97+
File.join(dashboard_config[:tmp_uniq_dashboard_path], "dashboard_import_for_#{new_database_name}.zip")
98+
end
99+
100+
def new_database_yaml_file_path
101+
@new_database_yaml_file_path ||= ''
102+
end
103+
104+
def dashboard_export_root_path
105+
# locate the unziped dashboard_export_* directory as named by superset app, eg dashboard_export_20240821T001536
106+
@dashboard_export_root_path ||= begin
107+
pattern = File.join(dashboard_config[:tmp_uniq_dashboard_path], 'dashboard_export_*')
108+
Dir.glob(pattern).first
109+
end
110+
111+
end
112+
113+
def new_database_name
114+
dashboard_config[:databases].first[:content][:database_name]
115+
end
116+
117+
def previous_database_name
118+
@previous_database_name ||= ''
119+
end
120+
121+
def validate_params
122+
raise "Dashboard Export Zip file does not exist" unless File.exist?(dashboard_export_zip)
123+
raise "Target Database YAML file does not exist" unless File.exist?(target_database_yaml_file)
124+
raise "Only 1 Database YAML file is allowed in the zip file" if dashboard_config[:databases].size > 1
125+
raise "Target Database Schema cannot be blank" if target_database_schema.blank?
126+
end
127+
end
128+
end
129+
end

spec/superset/services/dashboard_loader_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
before { loader.perform }
99

1010
it 'populates dashboard_config with each objects filename and content' do
11-
expect(loader.dashboard_config.keys).to contain_exactly(:dashboards, :datasets, :databases, :charts, :metadata)
12-
loader.dashboard_config.keys.each do |object|
11+
expect(loader.dashboard_config.keys).to contain_exactly(:dashboards, :datasets, :databases, :charts, :metadata, :tmp_uniq_dashboard_path)
12+
(loader.dashboard_config.keys - [:tmp_uniq_dashboard_path]).each do |object|
1313
expect(loader.dashboard_config[object]).to all(include(:filename, :content))
1414
end
1515
end

0 commit comments

Comments
 (0)