superset 0.1.6
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 +7 -0
- data/.buildkite/pipeline.yml +16 -0
- data/.rspec +3 -0
- data/.rubocop.yml +13 -0
- data/CHANGELOG.md +48 -0
- data/Dockerfile +17 -0
- data/LICENSE +21 -0
- data/README.md +205 -0
- data/Rakefile +12 -0
- data/doc/duplicate_dashboards.md +214 -0
- data/doc/setting_up_personal_api_credentials.md +127 -0
- data/docker-compose.override.yml +10 -0
- data/docker-compose.yml +8 -0
- data/env.sample +9 -0
- data/lib/loggers/duplicate_dashboard_logger.rb +15 -0
- data/lib/superset/authenticator.rb +55 -0
- data/lib/superset/chart/bulk_delete.rb +40 -0
- data/lib/superset/chart/delete.rb +30 -0
- data/lib/superset/chart/get.rb +56 -0
- data/lib/superset/chart/list.rb +59 -0
- data/lib/superset/chart/update_dataset.rb +90 -0
- data/lib/superset/client.rb +53 -0
- data/lib/superset/credential/api_user.rb +25 -0
- data/lib/superset/credential/embedded_user.rb +25 -0
- data/lib/superset/dashboard/bulk_delete.rb +42 -0
- data/lib/superset/dashboard/bulk_delete_cascade.rb +52 -0
- data/lib/superset/dashboard/charts/list.rb +47 -0
- data/lib/superset/dashboard/compare.rb +94 -0
- data/lib/superset/dashboard/copy.rb +78 -0
- data/lib/superset/dashboard/datasets/list.rb +74 -0
- data/lib/superset/dashboard/delete.rb +42 -0
- data/lib/superset/dashboard/embedded/get.rb +56 -0
- data/lib/superset/dashboard/embedded/put.rb +35 -0
- data/lib/superset/dashboard/export.rb +98 -0
- data/lib/superset/dashboard/get.rb +51 -0
- data/lib/superset/dashboard/info.rb +17 -0
- data/lib/superset/dashboard/list.rb +99 -0
- data/lib/superset/dashboard/put.rb +37 -0
- data/lib/superset/dashboard/warm_up_cache.rb +42 -0
- data/lib/superset/database/get.rb +30 -0
- data/lib/superset/database/get_schemas.rb +25 -0
- data/lib/superset/database/list.rb +51 -0
- data/lib/superset/dataset/bulk_delete.rb +41 -0
- data/lib/superset/dataset/create.rb +62 -0
- data/lib/superset/dataset/delete.rb +30 -0
- data/lib/superset/dataset/duplicate.rb +62 -0
- data/lib/superset/dataset/get.rb +56 -0
- data/lib/superset/dataset/list.rb +41 -0
- data/lib/superset/dataset/update_query.rb +56 -0
- data/lib/superset/dataset/update_schema.rb +120 -0
- data/lib/superset/dataset/warm_up_cache.rb +41 -0
- data/lib/superset/display.rb +42 -0
- data/lib/superset/enumerations/object_type.rb +11 -0
- data/lib/superset/file_utilities.rb +19 -0
- data/lib/superset/guest_token.rb +69 -0
- data/lib/superset/logger.rb +20 -0
- data/lib/superset/request.rb +62 -0
- data/lib/superset/route_info.rb +34 -0
- data/lib/superset/security/permissions_resources/list.rb +22 -0
- data/lib/superset/security/role/create.rb +25 -0
- data/lib/superset/security/role/get.rb +32 -0
- data/lib/superset/security/role/list.rb +45 -0
- data/lib/superset/security/role/permission/create.rb +35 -0
- data/lib/superset/security/role/permission/get.rb +37 -0
- data/lib/superset/security/user/create.rb +49 -0
- data/lib/superset/security/user/get.rb +27 -0
- data/lib/superset/security/user/list.rb +42 -0
- data/lib/superset/services/duplicate_dashboard.rb +298 -0
- data/lib/superset/sqllab/execute.rb +52 -0
- data/lib/superset/tag/add_to_object.rb +46 -0
- data/lib/superset/tag/get.rb +30 -0
- data/lib/superset/tag/list.rb +37 -0
- data/lib/superset/version.rb +5 -0
- data/lib/superset.rb +17 -0
- data/log/README.md +4 -0
- data/superset.gemspec +55 -0
- metadata +300 -0
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# WARNING: DESTRUCTIVE OPERATION .. use with caution
|
4
|
+
# This class is used to delete multiple dashboards and all related charts and datasets.
|
5
|
+
# There are NO CHECKS currently to confirm if a dataset is used on other dashboards.
|
6
|
+
|
7
|
+
module Superset
|
8
|
+
module Dashboard
|
9
|
+
class BulkDeleteCascade
|
10
|
+
class InvalidParameterError < StandardError; end
|
11
|
+
|
12
|
+
attr_reader :dashboard_ids
|
13
|
+
|
14
|
+
def initialize(dashboard_ids: [])
|
15
|
+
@dashboard_ids = dashboard_ids
|
16
|
+
end
|
17
|
+
|
18
|
+
def perform
|
19
|
+
raise InvalidParameterError, "dashboard_ids array of integers expected" unless dashboard_ids.is_a?(Array)
|
20
|
+
raise InvalidParameterError, "dashboard_ids array must contain Integer only values" unless dashboard_ids.all? { |item| item.is_a?(Integer) }
|
21
|
+
|
22
|
+
dashboard_ids.sort.each do |dashboard_id|
|
23
|
+
logger.info("Dashboard Id: #{dashboard_id.to_s} Attempting CASCADE delete of dashboard, charts, datasets")
|
24
|
+
delete_datasets(dashboard_id)
|
25
|
+
delete_charts(dashboard_id)
|
26
|
+
delete_dashboard(dashboard_id)
|
27
|
+
end
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def delete_datasets(dashboard_id)
|
34
|
+
datasets_to_delete = Superset::Dashboard::Datasets::List.new(dashboard_id).datasets_details.map{|d| d[:id] }
|
35
|
+
Superset::Dataset::BulkDelete.new(dataset_ids: datasets_to_delete).perform if datasets_to_delete.any?
|
36
|
+
end
|
37
|
+
|
38
|
+
def delete_charts(dashboard_id)
|
39
|
+
charts_to_delete = Superset::Dashboard::Charts::List.new(dashboard_id).chart_ids
|
40
|
+
Superset::Chart::BulkDelete.new(chart_ids: charts_to_delete).perform if charts_to_delete.any?
|
41
|
+
end
|
42
|
+
|
43
|
+
def delete_dashboard(dashboard_id)
|
44
|
+
Superset::Dashboard::Delete.new(dashboard_id: dashboard_id, confirm_zero_charts: true).perform
|
45
|
+
end
|
46
|
+
|
47
|
+
def logger
|
48
|
+
@logger ||= Superset::Logger.new
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Superset
|
2
|
+
module Dashboard
|
3
|
+
module Charts
|
4
|
+
class List < Superset::Request
|
5
|
+
attr_reader :id # dashboard id
|
6
|
+
|
7
|
+
def self.call(id)
|
8
|
+
self.new(id).list
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(id)
|
12
|
+
@id = id
|
13
|
+
end
|
14
|
+
|
15
|
+
def chart_ids
|
16
|
+
result.map { |c| c[:id] }
|
17
|
+
end
|
18
|
+
|
19
|
+
def rows
|
20
|
+
result.map do |c|
|
21
|
+
[
|
22
|
+
c[:id],
|
23
|
+
c[:slice_name],
|
24
|
+
c[:form_data][:datasource],
|
25
|
+
# c[:form_data][:dashboards] # NOTE: form_data dashboards is not accurate .. looks to be bugs related to copying charts
|
26
|
+
]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def route
|
33
|
+
"dashboard/#{id}/charts"
|
34
|
+
end
|
35
|
+
|
36
|
+
def list_attributes
|
37
|
+
['id', 'slice_name', 'datasource'].map(&:to_sym)
|
38
|
+
end
|
39
|
+
|
40
|
+
# when displaying a list of datasets, show dashboard id and title as well
|
41
|
+
def title
|
42
|
+
@title ||= [id, Superset::Dashboard::Get.new(id).title].join(' ')
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# A validation checker for comparing dashboards.
|
2
|
+
# This class is used to compare two dashboards by their datasets, charts, native filters and cross filters.
|
3
|
+
# Output is displayed in a table format to the ruby console
|
4
|
+
#
|
5
|
+
# Usage: Superset::Dashboard::Compare.new(first_dashboard_id: 322, second_dashboard_id: 347).perform
|
6
|
+
#
|
7
|
+
module Superset
|
8
|
+
module Dashboard
|
9
|
+
class Compare
|
10
|
+
|
11
|
+
attr_reader :first_dashboard_id, :second_dashboard_id
|
12
|
+
|
13
|
+
def initialize(first_dashboard_id: , second_dashboard_id: )
|
14
|
+
@first_dashboard_id = first_dashboard_id
|
15
|
+
@second_dashboard_id = second_dashboard_id
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
def perform
|
20
|
+
raise "Error: first_dashboard_id integer is required" unless first_dashboard_id.present? && first_dashboard_id.is_a?(Integer)
|
21
|
+
raise "Error: second_dashboard_id integer is required" unless second_dashboard_id.present? && second_dashboard_id.is_a?(Integer)
|
22
|
+
|
23
|
+
list_datasets
|
24
|
+
list_charts
|
25
|
+
list_native_filters
|
26
|
+
list_cross_filters
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
def first_dashboard
|
31
|
+
@first_dashboard ||= Get.new(first_dashboard_id).result
|
32
|
+
end
|
33
|
+
|
34
|
+
def second_dashboard
|
35
|
+
@second_dashboard ||= Get.new(second_dashboard_id).result
|
36
|
+
end
|
37
|
+
|
38
|
+
def list_datasets
|
39
|
+
puts "\n ====== DASHBOARD DATASETS ====== "
|
40
|
+
Superset::Dashboard::Datasets::List.new(first_dashboard_id).list
|
41
|
+
Superset::Dashboard::Datasets::List.new(second_dashboard_id).list
|
42
|
+
end
|
43
|
+
|
44
|
+
def list_charts
|
45
|
+
puts "\n ====== DASHBOARD CHARTS ====== "
|
46
|
+
Superset::Dashboard::Charts::List.new(first_dashboard_id).list
|
47
|
+
puts ''
|
48
|
+
Superset::Dashboard::Charts::List.new(second_dashboard_id).list
|
49
|
+
end
|
50
|
+
|
51
|
+
def list_native_filters
|
52
|
+
puts "\n ====== DASHBOARD NATIVE FILTERS ====== "
|
53
|
+
list_native_filters_for(first_dashboard)
|
54
|
+
puts ''
|
55
|
+
list_native_filters_for(second_dashboard)
|
56
|
+
end
|
57
|
+
|
58
|
+
def list_cross_filters
|
59
|
+
puts "\n ====== DASHBOARD CROSS FILTERS ====== "
|
60
|
+
list_cross_filters_for(first_dashboard)
|
61
|
+
puts ''
|
62
|
+
list_cross_filters_for(second_dashboard)
|
63
|
+
end
|
64
|
+
|
65
|
+
def native_filter_configuration(dashboard_result)
|
66
|
+
rows = []
|
67
|
+
JSON.parse(dashboard_result['json_metadata'])['native_filter_configuration'].each do |filter|
|
68
|
+
filter['targets'].each {|t| rows << [ t['column']['name'], t['datasetId'] ] }
|
69
|
+
end
|
70
|
+
rows
|
71
|
+
end
|
72
|
+
|
73
|
+
def list_native_filters_for(dashboard_result)
|
74
|
+
puts Terminal::Table.new(
|
75
|
+
title: [dashboard_result['id'], dashboard_result['dashboard_title']].join(' - '),
|
76
|
+
headings: ['Filter Name', 'Dataset Id'],
|
77
|
+
rows: native_filter_configuration(dashboard_result)
|
78
|
+
)
|
79
|
+
end
|
80
|
+
|
81
|
+
def cross_filter_configuration(dashboard_result)
|
82
|
+
JSON.parse(dashboard_result['json_metadata'])['chart_configuration'].map {|k, v| [ v['id'], v['crossFilters'].to_s ] }
|
83
|
+
end
|
84
|
+
|
85
|
+
def list_cross_filters_for(dashboard_result)
|
86
|
+
puts Terminal::Table.new(
|
87
|
+
title: [dashboard_result['id'], dashboard_result['dashboard_title']].join(' - '),
|
88
|
+
headings: ['Chart Id', 'Cross Filter Config'],
|
89
|
+
rows: cross_filter_configuration(dashboard_result)
|
90
|
+
)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
|
2
|
+
# TODO: some work to happen around TAGS still .. ie 'template' tag would indicate it tested and available to be copied.
|
3
|
+
# TODO: also need to ensoure that the embedded details are not duplicated across to the new dashboard
|
4
|
+
|
5
|
+
module Superset
|
6
|
+
module Dashboard
|
7
|
+
class Copy < Superset::Request
|
8
|
+
|
9
|
+
attr_reader :source_dashboard_id, :duplicate_slices, :clear_shared_label_colors
|
10
|
+
|
11
|
+
def initialize(source_dashboard_id: , duplicate_slices: false, clear_shared_label_colors: false)
|
12
|
+
@source_dashboard_id = source_dashboard_id
|
13
|
+
@duplicate_slices = duplicate_slices # boolean indicates whether to duplicate charts OR keep the new dashboard pointing to the same charts as the original
|
14
|
+
@clear_shared_label_colors = clear_shared_label_colors
|
15
|
+
end
|
16
|
+
|
17
|
+
def perform
|
18
|
+
raise "Error: source_dashboard_id integer is required" unless source_dashboard_id.present? && source_dashboard_id.is_a?(Integer)
|
19
|
+
raise "Error: duplicate_slices must be a boolean" unless duplicate_slices_is_boolean?
|
20
|
+
|
21
|
+
adjust_json_metadata
|
22
|
+
response
|
23
|
+
Superset::Dashboard::Get.new(id).perform # return the full new dashboard object
|
24
|
+
end
|
25
|
+
|
26
|
+
def params
|
27
|
+
{
|
28
|
+
"css" => "{}",
|
29
|
+
"dashboard_title" => "#{source_dashboard.title}",
|
30
|
+
"duplicate_slices" => duplicate_slices,
|
31
|
+
"json_metadata" => new_dashboard_json_metadata.to_json,
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
def response
|
36
|
+
@response ||= client.post(route, params)
|
37
|
+
end
|
38
|
+
|
39
|
+
def id
|
40
|
+
response["result"]["id"]
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def route
|
46
|
+
"dashboard/#{source_dashboard_id}/copy/"
|
47
|
+
end
|
48
|
+
|
49
|
+
def adjust_json_metadata
|
50
|
+
# when copying a DB via the API, chart positions need to be nested under json_metadata according to the GUI copy function (as per dev tools investigation in browser)
|
51
|
+
new_dashboard_json_metadata.merge!( "positions" => source_dashboard.positions )
|
52
|
+
|
53
|
+
if clear_shared_label_colors
|
54
|
+
# if coping a dashboard to a new db schema .. shared label colors will not be relevant/match as they are specific to the previous schemas dataset values
|
55
|
+
new_dashboard_json_metadata.merge!( "shared_label_colors" => {} )
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def source_dashboard
|
60
|
+
@source_dashboard ||= begin
|
61
|
+
dash = Get.new(source_dashboard_id)
|
62
|
+
dash.response
|
63
|
+
dash
|
64
|
+
rescue => e
|
65
|
+
raise "Error retrieving source dashboard #{e.message}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def new_dashboard_json_metadata
|
70
|
+
@new_dashboard_json_metadata ||= source_dashboard.json_metadata
|
71
|
+
end
|
72
|
+
|
73
|
+
def duplicate_slices_is_boolean?
|
74
|
+
[true, false].include?(duplicate_slices)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# WARNING: Does not take into account datasets with queries that have embedded schema references.
|
2
|
+
# ie " select * from schema1.table join schema2.table" for a dataset query will ONLY return the datasets config schema setting not the sql query schema references which refers to 2 distinct schemas.
|
3
|
+
#
|
4
|
+
# WARNING: Does not return Filter Datasets for the dashboard
|
5
|
+
|
6
|
+
module Superset
|
7
|
+
module Dashboard
|
8
|
+
module Datasets
|
9
|
+
class List < Superset::Request
|
10
|
+
attr_reader :id # dashboard id
|
11
|
+
|
12
|
+
def self.call(id)
|
13
|
+
self.new(id).list
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(id)
|
17
|
+
@id = id
|
18
|
+
end
|
19
|
+
|
20
|
+
def perform
|
21
|
+
response
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def schemas
|
26
|
+
@schemas ||= begin
|
27
|
+
all_dashboard_schemas = result.map {|d| d[:schema] }.uniq
|
28
|
+
|
29
|
+
# For the current superset setup we will assume a dashboard datasets will point to EXACTLY one schema, their own.
|
30
|
+
# if not .. we need to know about it. Potentially we could override this check if others do not consider it a problem.
|
31
|
+
if all_dashboard_schemas.count > 1
|
32
|
+
Rollbar.error("SUPERSET DASHBOARD ERROR: Dashboard id #{id} has multiple dataset schema linked: #{all_dashboard_schemas.to_s}")
|
33
|
+
end
|
34
|
+
all_dashboard_schemas
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def datasets_details
|
39
|
+
result.map do |details|
|
40
|
+
details.slice('id', 'datasource_name', 'schema', 'sql').merge('database' => details['database'].slice('id', 'name', 'backend')).with_indifferent_access
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def route
|
47
|
+
"dashboard/#{id}/datasets"
|
48
|
+
end
|
49
|
+
|
50
|
+
def list_attributes
|
51
|
+
['id', 'datasource_name', 'database_id', 'database_name', 'database_backend', 'schema'].map(&:to_sym)
|
52
|
+
end
|
53
|
+
|
54
|
+
def rows
|
55
|
+
result.map do |d|
|
56
|
+
[
|
57
|
+
d[:id],
|
58
|
+
d[:datasource_name],
|
59
|
+
d[:database][:id],
|
60
|
+
d[:database][:name],
|
61
|
+
d[:database][:backend],
|
62
|
+
d[:schema]
|
63
|
+
]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# when displaying a list of datasets, show dashboard title as well
|
68
|
+
def title
|
69
|
+
@title ||= [id, Superset::Dashboard::Get.new(id).title].join(' ')
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Superset
|
4
|
+
module Dashboard
|
5
|
+
class Delete < Superset::Request
|
6
|
+
|
7
|
+
attr_reader :dashboard_id, :confirm_zero_charts
|
8
|
+
|
9
|
+
def initialize(dashboard_id: , confirm_zero_charts: true)
|
10
|
+
@dashboard_id = dashboard_id
|
11
|
+
@confirm_zero_charts = confirm_zero_charts
|
12
|
+
end
|
13
|
+
|
14
|
+
def perform
|
15
|
+
raise InvalidParameterError, "dashboard_id integer is required" unless dashboard_id.present? && dashboard_id.is_a?(Integer)
|
16
|
+
|
17
|
+
confirm_zero_charts_on_dashboard if confirm_zero_charts
|
18
|
+
|
19
|
+
logger.info("Attempting to delete dashboard with id: #{dashboard_id}")
|
20
|
+
response
|
21
|
+
end
|
22
|
+
|
23
|
+
def response
|
24
|
+
@response ||= client.delete(route)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def confirm_zero_charts_on_dashboard
|
30
|
+
raise "Error: Dashboard includes #{dashboard_charts.count} charts. Please delete all charts before deleting the dashboard or override and set confirm_zero_charts: false" if dashboard_charts.count.positive?
|
31
|
+
end
|
32
|
+
|
33
|
+
def dashboard_charts
|
34
|
+
@dashboard_charts ||= Superset::Dashboard::Charts::List.new(dashboard_id).rows
|
35
|
+
end
|
36
|
+
|
37
|
+
def route
|
38
|
+
"dashboard/#{dashboard_id}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Superset
|
2
|
+
module Dashboard
|
3
|
+
module Embedded
|
4
|
+
class Get < Superset::Request
|
5
|
+
attr_reader :id # dashboard id
|
6
|
+
|
7
|
+
def self.call(id)
|
8
|
+
self.new(id).list
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(id)
|
12
|
+
@id = id
|
13
|
+
end
|
14
|
+
|
15
|
+
def response
|
16
|
+
@response ||= client.get(route)
|
17
|
+
rescue Happi::Error::NotFound => e
|
18
|
+
logger.info("Dashboard #{id} has no Embedded settings. (skipping)") # some dashboards don't have embedded settings, fine to ignore.
|
19
|
+
@response = { result: [] }.with_indifferent_access
|
20
|
+
@response
|
21
|
+
end
|
22
|
+
|
23
|
+
def result
|
24
|
+
response[:result].empty? ? [] : [ super ] # wrap single result in an array so it can be used in the tt list and table
|
25
|
+
end
|
26
|
+
|
27
|
+
def allowed_domains
|
28
|
+
result.first['allowed_domains'] unless response[:result].empty?
|
29
|
+
end
|
30
|
+
|
31
|
+
def uuid
|
32
|
+
result.first['uuid'] unless response[:result].empty?
|
33
|
+
end
|
34
|
+
|
35
|
+
def list
|
36
|
+
super unless response[:result].empty?
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def route
|
42
|
+
"dashboard/#{id}/embedded"
|
43
|
+
end
|
44
|
+
|
45
|
+
def list_attributes
|
46
|
+
[:dashboard_id, :uuid, :allowed_domains, :changed_on]
|
47
|
+
end
|
48
|
+
|
49
|
+
# when displaying embedded details, show dashboard title as well
|
50
|
+
def title
|
51
|
+
Superset::Dashboard::Get.new(id).title
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Superset
|
2
|
+
module Dashboard
|
3
|
+
module Embedded
|
4
|
+
class Put < Superset::Request
|
5
|
+
attr_reader :dashboard_id, :allowed_domains
|
6
|
+
|
7
|
+
def initialize(dashboard_id: , allowed_domains: )
|
8
|
+
@dashboard_id = dashboard_id
|
9
|
+
@allowed_domains = allowed_domains
|
10
|
+
end
|
11
|
+
|
12
|
+
def response
|
13
|
+
raise InvalidParameterError, 'dashboard_id integer is required' if dashboard_id.nil? || dashboard_id.class != Integer
|
14
|
+
raise InvalidParameterError, 'allowed_domains array is required' if allowed_domains.nil? || allowed_domains.class != Array
|
15
|
+
|
16
|
+
@response ||= client.put(route, params)
|
17
|
+
end
|
18
|
+
|
19
|
+
def params
|
20
|
+
{ "allowed_domains": allowed_domains }
|
21
|
+
end
|
22
|
+
|
23
|
+
def uuid
|
24
|
+
result['uuid'] unless response[:result].empty?
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def route
|
30
|
+
"dashboard/#{dashboard_id}/embedded"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# Will export the zip file to /tmp/superset_dashboards with zip filename adjusted to include the dashboard_id
|
2
|
+
# Example zipfile: dashboard_#{dashboard_id}_export_#{datestamp}.zip
|
3
|
+
# Will then unzip and copy the files into the destination_path with the dashboard_id as a subfolder
|
4
|
+
#
|
5
|
+
# Usage
|
6
|
+
# Superset::Dashboard::Export.new(dashboard_id: 15, destination_path: '/tmp/superset_dashboard_backups/').perform
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'superset/file_utilities'
|
10
|
+
|
11
|
+
module Superset
|
12
|
+
module Dashboard
|
13
|
+
class Export < Request
|
14
|
+
include FileUtilities
|
15
|
+
|
16
|
+
TMP_SUPERSET_DASHBOARD_PATH = '/tmp/superset_dashboards'
|
17
|
+
|
18
|
+
attr_reader :dashboard_id, :destination_path
|
19
|
+
|
20
|
+
def initialize(dashboard_id: , destination_path: )
|
21
|
+
@dashboard_id = dashboard_id
|
22
|
+
@destination_path = destination_path.chomp('/')
|
23
|
+
end
|
24
|
+
|
25
|
+
def perform
|
26
|
+
create_tmp_dir
|
27
|
+
save_exported_zip_file
|
28
|
+
unzip_files
|
29
|
+
copy_export_files_to_destination_path if destination_path
|
30
|
+
end
|
31
|
+
|
32
|
+
def response
|
33
|
+
@response ||= client.call(
|
34
|
+
:get,
|
35
|
+
client.url(route),
|
36
|
+
client.param_check(params)
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def params
|
43
|
+
{ "q": "!(#{dashboard_id})" } # pulled off chrome dev tools doing a GUI export. Swagger interface not helpfull with this endpoint.
|
44
|
+
end
|
45
|
+
|
46
|
+
def save_exported_zip_file
|
47
|
+
File.open(zip_file_name, 'wb') { |fp| fp.write(response.body) }
|
48
|
+
end
|
49
|
+
|
50
|
+
def unzip_files
|
51
|
+
@extracted_files = unzip_file(zip_file_name, tmp_uniq_dashboard_path)
|
52
|
+
end
|
53
|
+
|
54
|
+
def download_folder
|
55
|
+
File.dirname(extracted_files[0])
|
56
|
+
end
|
57
|
+
|
58
|
+
def copy_export_files_to_destination_path
|
59
|
+
path_with_dash_id = File.join(destination_path, dashboard_id.to_s)
|
60
|
+
FileUtils.mkdir_p(path_with_dash_id) unless File.directory?(path_with_dash_id)
|
61
|
+
|
62
|
+
Dir.glob("#{download_folder}/*").each do |item|
|
63
|
+
FileUtils.cp_r(item, path_with_dash_id)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def zip_file_name
|
68
|
+
@zip_file_name ||= "#{tmp_uniq_dashboard_path}/dashboard_#{dashboard_id}_export_#{datestamp}.zip"
|
69
|
+
end
|
70
|
+
|
71
|
+
def create_tmp_dir
|
72
|
+
FileUtils.mkdir_p(tmp_uniq_dashboard_path) unless File.directory?(tmp_uniq_dashboard_path)
|
73
|
+
end
|
74
|
+
|
75
|
+
# uniq random tmp folder name for each export
|
76
|
+
# this will allow us to do a wildcard glop on the folder to get the files
|
77
|
+
def tmp_uniq_dashboard_path
|
78
|
+
@tmp_uniq_dashboard_path ||= File.join(TMP_SUPERSET_DASHBOARD_PATH, uuid)
|
79
|
+
end
|
80
|
+
|
81
|
+
def uuid
|
82
|
+
SecureRandom.uuid
|
83
|
+
end
|
84
|
+
|
85
|
+
def extracted_files
|
86
|
+
@extracted_files ||= []
|
87
|
+
end
|
88
|
+
|
89
|
+
def route
|
90
|
+
"dashboard/export/"
|
91
|
+
end
|
92
|
+
|
93
|
+
def datestamp
|
94
|
+
@datestamp ||= Time.now.strftime('%Y%m%d')
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Superset
|
2
|
+
module Dashboard
|
3
|
+
class Get < Superset::Request
|
4
|
+
|
5
|
+
attr_reader :id
|
6
|
+
|
7
|
+
def initialize(id)
|
8
|
+
@id = id
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.call(id)
|
12
|
+
self.new(id).list
|
13
|
+
end
|
14
|
+
|
15
|
+
def perform
|
16
|
+
response
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def title
|
21
|
+
"#{result['dashboard_title']}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def json_metadata
|
25
|
+
JSON.parse(result['json_metadata'])
|
26
|
+
end
|
27
|
+
|
28
|
+
def positions
|
29
|
+
JSON.parse(result['position_json'])
|
30
|
+
end
|
31
|
+
|
32
|
+
def url
|
33
|
+
"#{superset_host}#{result['url']}"
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def route
|
39
|
+
"dashboard/#{id}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def rows
|
43
|
+
result['charts'].map {|c| [c]}
|
44
|
+
end
|
45
|
+
|
46
|
+
def headings
|
47
|
+
['Charts']
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|