superset 0.2.5 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.ruby-version +1 -0
- data/CHANGELOG.md +12 -3
- data/doc/migrating_dashboards_across_environments.md +4 -4
- data/lib/superset/chart/list.rb +5 -5
- data/lib/superset/dashboard/import.rb +74 -25
- data/lib/superset/dashboard/list.rb +35 -9
- data/lib/superset/database/get_catalogs.rb +38 -0
- data/lib/superset/database/list.rb +2 -2
- data/lib/superset/dataset/get.rb +8 -4
- data/lib/superset/dataset/list.rb +2 -2
- data/lib/superset/dataset/update_schema.rb +4 -2
- data/lib/superset/display.rb +19 -10
- data/lib/superset/request.rb +6 -4
- data/lib/superset/security/permissions_resources/list.rb +2 -2
- data/lib/superset/security/user/list.rb +2 -2
- data/lib/superset/services/duplicate_dashboard.rb +13 -2
- data/lib/superset/services/import_dashboard_across_environment.rb +3 -0
- data/lib/superset/tag/list.rb +1 -1
- data/lib/superset/version.rb +1 -1
- data/superset.gemspec +2 -2
- metadata +8 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c6b2b4cb255df5113b61de937f565c6f004c5df347bb047f0c47e6f1c06eadd2
|
|
4
|
+
data.tar.gz: a34377f58a2dbbfc0bcbcf7a897bee918760707c01b33c41d1119af0d940840d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c734a5177395f6981fa7733ee0a6c44c1839ca080eb18ad9c4c6e8d12f7adb2948f7710c5d5d2c13015c855cce03169b0ddf5b93254e17929173e3a81503501b
|
|
7
|
+
data.tar.gz: 3db00f88ca7dd063ace17729b41cc5c5c7e30b3b77ef37a15ccee83873cbe02caf1460bd30b397a4d28a7d3971179f57d9b0194a186ebf05b194e90a050ed650
|
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2.7.8
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
## Change Log
|
|
2
2
|
|
|
3
|
+
## 0.2.7 - 2025-10-16
|
|
4
|
+
* add to_h method to list classes for Hash output
|
|
5
|
+
* add ids method to list classes for array of ids output
|
|
6
|
+
* add ability to over ride default page_size
|
|
7
|
+
* add ruby console auto timeout set to 30 mins by default
|
|
8
|
+
* add GetCatalogs endpoint caller
|
|
9
|
+
* fix catalog bug related to dashboard export and import across env
|
|
10
|
+
* add tag_equals param to dashboard list class
|
|
11
|
+
|
|
12
|
+
## 0.2.6 - 2025-08-05
|
|
13
|
+
* Bump terminal-table from 1.8.0 to 4.0.0
|
|
14
|
+
|
|
3
15
|
## 0.2.5 - 2025-05-22
|
|
4
16
|
* add refresh dataset call in `Superset::Dataset::Refresh.call(id)`
|
|
5
17
|
|
|
@@ -79,6 +91,3 @@
|
|
|
79
91
|
- add security/user endpoints
|
|
80
92
|
- add security/role endpoints
|
|
81
93
|
- add security/role/permission endpoints
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
@@ -20,12 +20,12 @@ Assuming your API env for ruby is setup for your target superset environment.
|
|
|
20
20
|
|
|
21
21
|
new_import_zip = Superset::Services::ImportDashboardAcrossEnvironments.new(
|
|
22
22
|
dashboard_export_zip: 'path_to/dashboard_101_export_20241010.zip',
|
|
23
|
-
target_database_yaml_file: 'path_to/env2_db_config.yaml',
|
|
24
|
-
target_database_schema: 'acme',
|
|
23
|
+
target_database_yaml_file: 'path_to/env2_db_config.yaml',
|
|
24
|
+
target_database_schema: 'acme',
|
|
25
25
|
).perform
|
|
26
26
|
|
|
27
27
|
# now import the adjusted zip to the target superset env
|
|
28
|
-
Superset::Dashboard::Import.new(
|
|
28
|
+
Superset::Dashboard::Import.new(source: new_import_file).perform
|
|
29
29
|
|
|
30
30
|
```
|
|
31
31
|
|
|
@@ -170,4 +170,4 @@ It will attempt to update the same dashboard as the UUID for the dashboard has n
|
|
|
170
170
|
|
|
171
171
|
Some helpful references relating to cross-environment workflows:
|
|
172
172
|
- [Managing Content Across Workspaces](https://docs.preset.io/docs/managing-content-across-workspaces)
|
|
173
|
-
- [Superset Slack AI Explanation](https://apache-superset.slack.com/archives/C072KSLBTC1/p1722382347022689)
|
|
173
|
+
- [Superset Slack AI Explanation](https://apache-superset.slack.com/archives/C072KSLBTC1/p1722382347022689)
|
data/lib/superset/chart/list.rb
CHANGED
|
@@ -2,13 +2,13 @@ module Superset
|
|
|
2
2
|
module Chart
|
|
3
3
|
class List < Superset::Request
|
|
4
4
|
|
|
5
|
-
attr_reader :
|
|
5
|
+
attr_reader :title_contains, :dashboard_id_eq, :dataset_id_eq
|
|
6
6
|
|
|
7
|
-
def initialize(
|
|
8
|
-
@
|
|
7
|
+
def initialize(title_contains: '', dashboard_id_eq: '', dataset_id_eq: '', **kwargs)
|
|
8
|
+
@title_contains = title_contains
|
|
9
9
|
@dashboard_id_eq = dashboard_id_eq
|
|
10
10
|
@dataset_id_eq = dataset_id_eq
|
|
11
|
-
super(
|
|
11
|
+
super(**kwargs)
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def self.call
|
|
@@ -38,7 +38,7 @@ module Superset
|
|
|
38
38
|
def filters
|
|
39
39
|
# TODO filtering across all classes needs a refactor to support multiple options in a more flexible way
|
|
40
40
|
filter_set = []
|
|
41
|
-
filter_set << "(col:slice_name,opr:ct,value:'#{
|
|
41
|
+
filter_set << "(col:slice_name,opr:ct,value:'#{title_contains}')" if title_contains.present?
|
|
42
42
|
filter_set << "(col:dashboards,opr:rel_m_m,value:#{dashboard_id_eq})" if dashboard_id_eq.present? # rel many to many
|
|
43
43
|
filter_set << "(col:datasource_id,opr:eq,value:#{dataset_id_eq})" if dataset_id_eq.present?
|
|
44
44
|
|
|
@@ -1,29 +1,32 @@
|
|
|
1
|
-
#
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Import the provided Dashboard zip file or directory (aka source)
|
|
2
4
|
# In the context of this API import process, assumption is that the database.yaml file details will match
|
|
3
5
|
# an existing database in the Target Superset Environment.
|
|
4
6
|
|
|
5
7
|
# Scenario 1: Export from Env1 -- Import to Env1 into the SAME Environment
|
|
6
|
-
# Will result in updating/over writing the dashboard with the contents of the
|
|
8
|
+
# Will result in updating/over writing the dashboard with the contents of the source
|
|
7
9
|
|
|
8
10
|
# Scenario 2: Export from Env1 -- Import to Env2 into a DIFFERENT Environment
|
|
9
|
-
# Assumption is that the database.yaml will match a database configuration in the target env.
|
|
10
|
-
# Initial import will result in creating a new dashboard with the contents of the
|
|
11
|
-
# Subsequent imports will result in updating/over writing the previous imported dashboard with the contents of the
|
|
11
|
+
# Assumption is that the database.yaml will match a database configuration in the target env.
|
|
12
|
+
# Initial import will result in creating a new dashboard with the contents of the source.
|
|
13
|
+
# Subsequent imports will result in updating/over writing the previous imported dashboard with the contents of the source.
|
|
12
14
|
|
|
13
15
|
# the overwrite flag will determine if the dashboard will be updated or created new
|
|
14
16
|
# overwrite: false .. will result in an error if a dashboard with the same UUID already exists
|
|
15
17
|
|
|
16
18
|
# Usage
|
|
17
|
-
# Superset::Dashboard::Import.new(
|
|
19
|
+
# Superset::Dashboard::Import.new(source: '/tmp/dashboard.zip').perform
|
|
20
|
+
# Superset::Dashboard::Import.new(source: '/tmp/dashboard').perform
|
|
18
21
|
#
|
|
19
22
|
|
|
20
23
|
module Superset
|
|
21
24
|
module Dashboard
|
|
22
25
|
class Import < Request
|
|
23
|
-
attr_reader :
|
|
26
|
+
attr_reader :source, :overwrite
|
|
24
27
|
|
|
25
|
-
def initialize(
|
|
26
|
-
@
|
|
28
|
+
def initialize(source:, overwrite: true)
|
|
29
|
+
@source = source
|
|
27
30
|
@overwrite = overwrite
|
|
28
31
|
end
|
|
29
32
|
|
|
@@ -42,16 +45,20 @@ module Superset
|
|
|
42
45
|
private
|
|
43
46
|
|
|
44
47
|
def validate_params
|
|
45
|
-
raise ArgumentError,
|
|
46
|
-
raise ArgumentError,
|
|
47
|
-
raise ArgumentError,
|
|
48
|
-
raise ArgumentError,
|
|
49
|
-
|
|
48
|
+
raise ArgumentError, "source is required" if source.nil?
|
|
49
|
+
raise ArgumentError, "source does not exist" unless File.exist?(source)
|
|
50
|
+
raise ArgumentError, "source is not a zip file or directory" unless zip? || directory?
|
|
51
|
+
raise ArgumentError, "overwrite must be a boolean" unless [true, false].include?(overwrite)
|
|
52
|
+
|
|
53
|
+
return unless database_config_not_found_in_superset.present?
|
|
54
|
+
|
|
55
|
+
raise ArgumentError,
|
|
56
|
+
"target database does not exist: #{database_config_not_found_in_superset}"
|
|
50
57
|
end
|
|
51
58
|
|
|
52
59
|
def payload
|
|
53
60
|
{
|
|
54
|
-
formData: Faraday::UploadIO.new(source_zip_file,
|
|
61
|
+
formData: Faraday::UploadIO.new(source_zip_file, "application/zip"),
|
|
55
62
|
overwrite: overwrite.to_s
|
|
56
63
|
}
|
|
57
64
|
end
|
|
@@ -60,24 +67,66 @@ module Superset
|
|
|
60
67
|
"dashboard/import/"
|
|
61
68
|
end
|
|
62
69
|
|
|
63
|
-
def
|
|
64
|
-
|
|
70
|
+
def zip?
|
|
71
|
+
File.extname(source) == ".zip"
|
|
65
72
|
end
|
|
66
73
|
|
|
67
|
-
def
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
74
|
+
def directory?
|
|
75
|
+
File.directory?(source)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def source_zip_file
|
|
79
|
+
return source if zip?
|
|
80
|
+
|
|
81
|
+
Zip::File.open(new_zip_file, Zip::File::CREATE) do |zipfile|
|
|
82
|
+
Dir[File.join(source, "**", "**")].each do |file|
|
|
83
|
+
next unless File.file?(file)
|
|
84
|
+
|
|
85
|
+
# add a base folder to the relative path: "dashboard_import_#{timestamp}"
|
|
86
|
+
zipfile.add(File.join("dashboard_import_#{timestamp}", relative_path(file)), file)
|
|
87
|
+
end
|
|
72
88
|
end
|
|
89
|
+
new_zip_file
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def relative_path(file)
|
|
93
|
+
Pathname.new(file).relative_path_from(Pathname.new(source)).to_s
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def new_zip_file
|
|
97
|
+
File.join(source, "dashboard_import_#{timestamp}.zip")
|
|
73
98
|
end
|
|
74
99
|
|
|
75
|
-
def
|
|
76
|
-
|
|
100
|
+
def timestamp
|
|
101
|
+
@timestamp ||= Time.now.strftime("%Y%m%d%H%M%S")
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def database_config_not_found_in_superset
|
|
105
|
+
databases_details.reject { |s| superset_database_uuids_found.include?(s[:uuid]) }
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def superset_database_uuids_found
|
|
109
|
+
@superset_database_uuids_found ||= databases_details.map { |i| i[:uuid] }.map do |uuid|
|
|
110
|
+
uuid if Superset::Database::List.new(uuid_equals: uuid).result.present?
|
|
111
|
+
end.compact
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def databases_details
|
|
115
|
+
dashboard_config[:databases].map { |d| { uuid: d[:content][:uuid], name: d[:content][:database_name] } }
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def dashboard_config
|
|
119
|
+
@dashboard_config ||= zip? ? zip_dashboard_config : directory_dashboard_config
|
|
77
120
|
end
|
|
78
121
|
|
|
79
122
|
def zip_dashboard_config
|
|
80
|
-
|
|
123
|
+
Superset::Services::DashboardLoader.new(dashboard_export_zip: source).perform
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def directory_dashboard_config
|
|
127
|
+
Superset::Services::DashboardLoader::DashboardConfig.new(
|
|
128
|
+
dashboard_export_zip: "", tmp_uniq_dashboard_path: source
|
|
129
|
+
).config
|
|
81
130
|
end
|
|
82
131
|
end
|
|
83
132
|
end
|
|
@@ -5,15 +5,18 @@
|
|
|
5
5
|
module Superset
|
|
6
6
|
module Dashboard
|
|
7
7
|
class List < Superset::Request
|
|
8
|
-
attr_reader :title_contains, :title_equals,
|
|
8
|
+
attr_reader :title_contains, :title_equals,
|
|
9
|
+
:tags_contain, :tags_equal,
|
|
10
|
+
:ids_not_in, :include_filter_dataset_schemas
|
|
9
11
|
|
|
10
|
-
def initialize(
|
|
12
|
+
def initialize(title_contains: '', title_equals: '', tags_contain: [], tags_equal: [], ids_not_in: [], include_filter_dataset_schemas: false, **kwargs)
|
|
11
13
|
@title_contains = title_contains
|
|
12
14
|
@title_equals = title_equals
|
|
15
|
+
@tags_contain = tags_contain
|
|
13
16
|
@tags_equal = tags_equal
|
|
14
17
|
@ids_not_in = ids_not_in
|
|
15
18
|
@include_filter_dataset_schemas = include_filter_dataset_schemas
|
|
16
|
-
super(
|
|
19
|
+
super(**kwargs)
|
|
17
20
|
end
|
|
18
21
|
|
|
19
22
|
def self.call
|
|
@@ -57,8 +60,16 @@ module Superset
|
|
|
57
60
|
end
|
|
58
61
|
end
|
|
59
62
|
|
|
63
|
+
def to_h
|
|
64
|
+
result.map do |d|
|
|
65
|
+
list_attributes.to_h do |la|
|
|
66
|
+
la == :url ? [la, "#{superset_host}#{d[la]}"] : [la, d[la]]
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
60
71
|
def ids
|
|
61
|
-
result.map { |d| d[:id] }
|
|
72
|
+
result.is_a?(Hash) ? [result[:id]] : result.map { |d| d[:id] }
|
|
62
73
|
end
|
|
63
74
|
|
|
64
75
|
private
|
|
@@ -72,15 +83,30 @@ module Superset
|
|
|
72
83
|
filter_set = []
|
|
73
84
|
filter_set << "(col:dashboard_title,opr:ct,value:'#{title_contains}')" if title_contains.present?
|
|
74
85
|
filter_set << "(col:dashboard_title,opr:eq,value:'#{title_equals}')" if title_equals.present?
|
|
75
|
-
filter_set <<
|
|
86
|
+
filter_set << tags_contain_filters if tags_contain.present?
|
|
87
|
+
filter_set << tags_equal_filters if tags_equal.present?
|
|
76
88
|
filter_set << ids_not_in_filters if ids_not_in.present?
|
|
89
|
+
|
|
77
90
|
unless filter_set.empty?
|
|
78
91
|
"filters:!(" + filter_set.join(',') + "),"
|
|
79
92
|
end
|
|
80
93
|
end
|
|
81
94
|
|
|
82
|
-
def
|
|
83
|
-
|
|
95
|
+
def tags_equal_filters
|
|
96
|
+
tags_equal_ids.map { |id| "(col:tags,opr:dashboard_tag_id,value:#{id})" }.join(',')
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def tags_equal_ids
|
|
100
|
+
tags_equal.map do |tag_name|
|
|
101
|
+
ids = Superset::Tag::List.new(name_equals: tag_name).rows.map(&:first)
|
|
102
|
+
raise "No ID found for tag: #{tag_name}" if ids.empty?
|
|
103
|
+
raise "Multiple IDs found for tag: #{tag_name}" if ids.size > 1
|
|
104
|
+
ids.first
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def tags_contain_filters
|
|
109
|
+
tags_contain.map {|tag| "(col:tags,opr:dashboard_tags,value:'#{tag}')"}.join(',')
|
|
84
110
|
end
|
|
85
111
|
|
|
86
112
|
def ids_not_in_filters
|
|
@@ -94,8 +120,8 @@ module Superset
|
|
|
94
120
|
def validate_constructor_args
|
|
95
121
|
raise InvalidParameterError, "title_contains must be a String type" unless title_contains.is_a?(String)
|
|
96
122
|
raise InvalidParameterError, "title_equals must be a String type" unless title_equals.is_a?(String)
|
|
97
|
-
raise InvalidParameterError, "
|
|
98
|
-
raise InvalidParameterError, "tags_equal
|
|
123
|
+
raise InvalidParameterError, "tags_contain must be an Array type of String values" unless tags_contain.is_a?(Array) && tags_contain.all? { |item| item.is_a?(String) }
|
|
124
|
+
raise InvalidParameterError, "tags_equal must be an Array type of String values" unless tags_equal.is_a?(Array) && tags_equal.all? { |item| item.is_a?(String) }
|
|
99
125
|
raise InvalidParameterError, "ids_not_in must be an Array type" unless ids_not_in.is_a?(Array)
|
|
100
126
|
end
|
|
101
127
|
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module Superset
|
|
2
|
+
module Database
|
|
3
|
+
class GetCatalogs < Superset::Request
|
|
4
|
+
|
|
5
|
+
attr_reader :id, :include_system_catalogs
|
|
6
|
+
|
|
7
|
+
def initialize(id, include_system_catalogs: false)
|
|
8
|
+
@id = id
|
|
9
|
+
@include_system_catalogs = include_system_catalogs
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.call(id)
|
|
13
|
+
self.new(id).catalogs
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def catalogs
|
|
17
|
+
return result if include_system_catalogs
|
|
18
|
+
|
|
19
|
+
remove_system_catalogs
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def route
|
|
25
|
+
"database/#{id}/catalogs/"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# exclude system catalog values for certain databases that support them
|
|
29
|
+
def remove_system_catalogs
|
|
30
|
+
result - postgres_system_catalogs
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def postgres_system_catalogs
|
|
34
|
+
%w(postgres rdsadmin template1 template0)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -6,10 +6,10 @@ module Superset
|
|
|
6
6
|
class List < Superset::Request
|
|
7
7
|
attr_reader :title_contains, :uuid_equals
|
|
8
8
|
|
|
9
|
-
def initialize(
|
|
9
|
+
def initialize(title_contains: '', uuid_equals: '', **kwargs)
|
|
10
10
|
@title_contains = title_contains
|
|
11
11
|
@uuid_equals = uuid_equals
|
|
12
|
-
super(
|
|
12
|
+
super(**kwargs)
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def self.call
|
data/lib/superset/dataset/get.rb
CHANGED
|
@@ -18,7 +18,11 @@ module Superset
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def rows
|
|
21
|
-
[
|
|
21
|
+
[to_h.values]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def to_h
|
|
25
|
+
list_attributes.to_h { |a| [a, send(a)] }
|
|
22
26
|
end
|
|
23
27
|
|
|
24
28
|
def schema
|
|
@@ -26,7 +30,7 @@ module Superset
|
|
|
26
30
|
end
|
|
27
31
|
|
|
28
32
|
def title
|
|
29
|
-
result['
|
|
33
|
+
result['table_name']
|
|
30
34
|
end
|
|
31
35
|
|
|
32
36
|
def database_name
|
|
@@ -47,8 +51,8 @@ module Superset
|
|
|
47
51
|
"dataset/#{id}"
|
|
48
52
|
end
|
|
49
53
|
|
|
50
|
-
def
|
|
51
|
-
|
|
54
|
+
def list_attributes
|
|
55
|
+
[:title, :schema, :database_name, :database_id]
|
|
52
56
|
end
|
|
53
57
|
end
|
|
54
58
|
end
|
|
@@ -3,12 +3,12 @@ module Superset
|
|
|
3
3
|
class List < Superset::Request
|
|
4
4
|
attr_reader :title_contains, :title_equals, :schema_equals, :database_id_eq
|
|
5
5
|
|
|
6
|
-
def initialize(
|
|
6
|
+
def initialize(title_contains: '', title_equals: '', schema_equals: '', database_id_eq: '', **kwargs)
|
|
7
7
|
@title_contains = title_contains
|
|
8
8
|
@title_equals = title_equals
|
|
9
9
|
@schema_equals = schema_equals
|
|
10
10
|
@database_id_eq = database_id_eq
|
|
11
|
-
super(
|
|
11
|
+
super(**kwargs)
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def self.call
|
|
@@ -2,12 +2,13 @@ module Superset
|
|
|
2
2
|
module Dataset
|
|
3
3
|
class UpdateSchema < Superset::Request
|
|
4
4
|
|
|
5
|
-
attr_reader :source_dataset_id, :target_database_id, :target_schema, :remove_copy_suffix
|
|
5
|
+
attr_reader :source_dataset_id, :target_database_id, :target_schema, :target_catalog, :remove_copy_suffix
|
|
6
6
|
|
|
7
|
-
def initialize(source_dataset_id: , target_database_id: , target_schema: , remove_copy_suffix: false)
|
|
7
|
+
def initialize(source_dataset_id: , target_database_id: , target_schema: , target_catalog: nil, remove_copy_suffix: false)
|
|
8
8
|
@source_dataset_id = source_dataset_id
|
|
9
9
|
@target_database_id = target_database_id
|
|
10
10
|
@target_schema = target_schema
|
|
11
|
+
@target_catalog = target_catalog
|
|
11
12
|
@remove_copy_suffix = remove_copy_suffix
|
|
12
13
|
end
|
|
13
14
|
|
|
@@ -37,6 +38,7 @@ module Superset
|
|
|
37
38
|
|
|
38
39
|
# primary database and schema changes
|
|
39
40
|
new_params.merge!("database_id": target_database_id) # add the target database id
|
|
41
|
+
new_params['catalog'] = target_catalog
|
|
40
42
|
new_params['schema'] = target_schema
|
|
41
43
|
new_params['owners'] = new_params['owners'].map {|o| o['id'] } # expects an array of user ids
|
|
42
44
|
new_params['table_name'] = new_params['table_name'].gsub(/ \(COPY\)/, '') if remove_copy_suffix
|
data/lib/superset/display.rb
CHANGED
|
@@ -7,28 +7,37 @@ module Superset
|
|
|
7
7
|
def table
|
|
8
8
|
Terminal::Table.new(
|
|
9
9
|
title: title,
|
|
10
|
-
headings:
|
|
10
|
+
headings: list_attributes.map(&:to_s).map(&:humanize),
|
|
11
11
|
rows: rows
|
|
12
12
|
)
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def rows
|
|
16
|
-
result.
|
|
17
|
-
list_attributes.map { |la|
|
|
16
|
+
if result.is_a?(Hash)
|
|
17
|
+
list_attributes.map { |la| result[la].to_s }
|
|
18
|
+
else
|
|
19
|
+
result.map do |d|
|
|
20
|
+
list_attributes.map { |la| d[la].to_s }
|
|
21
|
+
end
|
|
18
22
|
end
|
|
19
23
|
end
|
|
20
24
|
|
|
21
|
-
def
|
|
22
|
-
|
|
25
|
+
def to_h
|
|
26
|
+
if result.is_a?(Hash)
|
|
27
|
+
list_attributes.to_h { |la| [la, result[la]] }
|
|
28
|
+
else
|
|
29
|
+
result.map do |d|
|
|
30
|
+
list_attributes.to_h { |la| [la, d[la]] }
|
|
31
|
+
end
|
|
32
|
+
end
|
|
23
33
|
end
|
|
24
34
|
|
|
25
|
-
def
|
|
26
|
-
|
|
27
|
-
headings.map(&:to_s).map(&:humanize)
|
|
35
|
+
def ids
|
|
36
|
+
result.map { |d| d[:id] }
|
|
28
37
|
end
|
|
29
38
|
|
|
30
|
-
def
|
|
31
|
-
|
|
39
|
+
def title
|
|
40
|
+
self.class.to_s
|
|
32
41
|
end
|
|
33
42
|
|
|
34
43
|
def list_attributes
|
data/lib/superset/request.rb
CHANGED
|
@@ -5,12 +5,13 @@ module Superset
|
|
|
5
5
|
class InvalidParameterError < StandardError; end
|
|
6
6
|
class ValidationError < StandardError; end
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
DEFAULT_PAGE_SIZE = 100
|
|
9
9
|
|
|
10
|
-
attr_accessor :page_num
|
|
10
|
+
attr_accessor :page_num, :page_size
|
|
11
11
|
|
|
12
|
-
def initialize(page_num: 0)
|
|
12
|
+
def initialize(page_num: 0, page_size: nil)
|
|
13
13
|
@page_num = page_num
|
|
14
|
+
@page_size = page_size || DEFAULT_PAGE_SIZE
|
|
14
15
|
end
|
|
15
16
|
|
|
16
17
|
def self.call
|
|
@@ -51,7 +52,8 @@ module Superset
|
|
|
51
52
|
end
|
|
52
53
|
|
|
53
54
|
def pagination
|
|
54
|
-
"
|
|
55
|
+
raise InvalidParameterError, "page_size max is 1000 records" if page_size.to_i > 1000
|
|
56
|
+
"page:#{page_num},page_size:#{page_size}"
|
|
55
57
|
end
|
|
56
58
|
|
|
57
59
|
def filters
|
|
@@ -4,10 +4,10 @@ module Superset
|
|
|
4
4
|
class List < Superset::Request
|
|
5
5
|
attr_reader :email_contains, :username_equals
|
|
6
6
|
|
|
7
|
-
def initialize(
|
|
7
|
+
def initialize(email_contains: '', username_equals: '', **kwargs)
|
|
8
8
|
@email_contains = email_contains
|
|
9
9
|
@username_equals = username_equals
|
|
10
|
-
super(
|
|
10
|
+
super(**kwargs)
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
private
|
|
@@ -100,8 +100,12 @@ module Superset
|
|
|
100
100
|
new_dataset_id = existing_datasets[0]["id"] # assuming that we do not name multiple datasets with same name in a single schema
|
|
101
101
|
else
|
|
102
102
|
new_dataset_id = Superset::Dataset::Duplicate.new(source_dataset_id: dataset[:id], new_dataset_name: new_dataset_name).perform
|
|
103
|
-
# update the new dataset with the target schema
|
|
104
|
-
Superset::Dataset::UpdateSchema.new(
|
|
103
|
+
# update the new dataset with the target schema, database, catalog
|
|
104
|
+
Superset::Dataset::UpdateSchema.new(
|
|
105
|
+
source_dataset_id: new_dataset_id,
|
|
106
|
+
target_database_id: target_database_id,
|
|
107
|
+
target_schema: target_schema,
|
|
108
|
+
target_catalog: target_database_catalog).perform
|
|
105
109
|
end
|
|
106
110
|
# keep track of the previous dataset and the matching new dataset_id
|
|
107
111
|
dataset_duplication_tracker << { source_dataset_id: dataset[:id], new_dataset_id: new_dataset_id }
|
|
@@ -263,6 +267,13 @@ module Superset
|
|
|
263
267
|
@filter_dataset_ids ||= source_dashboard.filter_configuration.map { |c| c['targets'] }.flatten.compact.map { |c| c['datasetId'] }.flatten.compact.uniq
|
|
264
268
|
end
|
|
265
269
|
|
|
270
|
+
# currently does not support multiple catalogs, assumption is that 1 catalog is used per database
|
|
271
|
+
def target_database_catalog
|
|
272
|
+
catalogs = Superset::Database::GetCatalogs.new(target_database_id).catalogs
|
|
273
|
+
raise ValidationError, "Target Database #{target_database_id} has multiple catalogs" if catalogs.size > 1
|
|
274
|
+
catalogs.first
|
|
275
|
+
end
|
|
276
|
+
|
|
266
277
|
# Primary Assumption is that all charts datasets on the source dashboard are pointing to the same database schema
|
|
267
278
|
# An unpermitted filter will have a dataset that is pulling data from a datasource that is
|
|
268
279
|
# different to the dashboard charts database schema
|
|
@@ -56,6 +56,7 @@ module Superset
|
|
|
56
56
|
|
|
57
57
|
def remove_source_database_config
|
|
58
58
|
return if dashboard_config[:databases].blank?
|
|
59
|
+
|
|
59
60
|
previous_database_name = dashboard_config[:databases]&.first[:content][:database_name]
|
|
60
61
|
File.delete(dashboard_config[:databases].first[:filename])
|
|
61
62
|
|
|
@@ -78,6 +79,8 @@ module Superset
|
|
|
78
79
|
dashboard_config[:datasets].each do |dataset|
|
|
79
80
|
dataset[:content][:database_uuid] = dashboard_config[:databases].first[:content][:uuid]
|
|
80
81
|
dataset[:content][:schema] = target_database_schema
|
|
82
|
+
dataset[:content][:catalog] = nil # reset target to use default catalog if applicable
|
|
83
|
+
|
|
81
84
|
stringified_content = deep_transform_keys_to_strings(dataset[:content])
|
|
82
85
|
File.open(dataset[:filename], 'w') { |f| f.write stringified_content.to_yaml }
|
|
83
86
|
end
|
data/lib/superset/tag/list.rb
CHANGED
|
@@ -23,7 +23,7 @@ module Superset
|
|
|
23
23
|
# TODO filtering across all list classes can be refactored to support multiple options in a more flexible way
|
|
24
24
|
filter_set = []
|
|
25
25
|
filter_set << "(col:name,opr:ct,value:'#{name_contains}')" if name_contains.present?
|
|
26
|
-
filter_set << "(col:name,opr:eq,value
|
|
26
|
+
filter_set << "(col:name,opr:eq,value:'#{name_equals}')" if name_equals.present?
|
|
27
27
|
unless filter_set.empty?
|
|
28
28
|
"filters:!(" + filter_set.join(',') + "),"
|
|
29
29
|
end
|
data/lib/superset/version.rb
CHANGED
data/superset.gemspec
CHANGED
|
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
|
|
|
11
11
|
spec.summary = "A Ruby Client for Apache Superset API"
|
|
12
12
|
spec.homepage = "https://github.com/rdytech/superset-client"
|
|
13
13
|
spec.license = "MIT"
|
|
14
|
-
spec.required_ruby_version = ">= 2.
|
|
14
|
+
spec.required_ruby_version = ">= 2.7.8"
|
|
15
15
|
|
|
16
16
|
#spec.metadata["allowed_push_host"] = ""
|
|
17
17
|
|
|
@@ -37,7 +37,7 @@ Gem::Specification.new do |spec|
|
|
|
37
37
|
# Uncomment to register a new dependency of your gem
|
|
38
38
|
spec.add_dependency "dotenv", "~> 2.7"
|
|
39
39
|
spec.add_dependency "json", "~> 2.6"
|
|
40
|
-
spec.add_dependency "terminal-table", "~>
|
|
40
|
+
spec.add_dependency "terminal-table", "~> 4.0"
|
|
41
41
|
spec.add_dependency "rake", "~> 13.0"
|
|
42
42
|
spec.add_dependency "rollbar", "~> 3.4"
|
|
43
43
|
spec.add_dependency "require_all", "~> 3.0"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: superset
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- jbat
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-
|
|
11
|
+
date: 2025-10-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: dotenv
|
|
@@ -44,14 +44,14 @@ dependencies:
|
|
|
44
44
|
requirements:
|
|
45
45
|
- - "~>"
|
|
46
46
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: '
|
|
47
|
+
version: '4.0'
|
|
48
48
|
type: :runtime
|
|
49
49
|
prerelease: false
|
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
51
|
requirements:
|
|
52
52
|
- - "~>"
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
|
-
version: '
|
|
54
|
+
version: '4.0'
|
|
55
55
|
- !ruby/object:Gem::Dependency
|
|
56
56
|
name: rake
|
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -202,6 +202,7 @@ files:
|
|
|
202
202
|
- ".buildkite/pipeline.yml"
|
|
203
203
|
- ".rspec"
|
|
204
204
|
- ".rubocop.yml"
|
|
205
|
+
- ".ruby-version"
|
|
205
206
|
- CHANGELOG.md
|
|
206
207
|
- Dockerfile
|
|
207
208
|
- LICENSE
|
|
@@ -248,6 +249,7 @@ files:
|
|
|
248
249
|
- lib/superset/dashboard/warm_up_cache.rb
|
|
249
250
|
- lib/superset/database/export.rb
|
|
250
251
|
- lib/superset/database/get.rb
|
|
252
|
+
- lib/superset/database/get_catalogs.rb
|
|
251
253
|
- lib/superset/database/get_schemas.rb
|
|
252
254
|
- lib/superset/database/list.rb
|
|
253
255
|
- lib/superset/dataset/bulk_delete.rb
|
|
@@ -299,14 +301,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
299
301
|
requirements:
|
|
300
302
|
- - ">="
|
|
301
303
|
- !ruby/object:Gem::Version
|
|
302
|
-
version: 2.
|
|
304
|
+
version: 2.7.8
|
|
303
305
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
304
306
|
requirements:
|
|
305
307
|
- - ">="
|
|
306
308
|
- !ruby/object:Gem::Version
|
|
307
309
|
version: '0'
|
|
308
310
|
requirements: []
|
|
309
|
-
rubygems_version: 3.
|
|
311
|
+
rubygems_version: 3.1.6
|
|
310
312
|
signing_key:
|
|
311
313
|
specification_version: 4
|
|
312
314
|
summary: A Ruby Client for Apache Superset API
|