superset 0.2.7 → 0.3.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c6b2b4cb255df5113b61de937f565c6f004c5df347bb047f0c47e6f1c06eadd2
4
- data.tar.gz: a34377f58a2dbbfc0bcbcf7a897bee918760707c01b33c41d1119af0d940840d
3
+ metadata.gz: 7c93ad7b81ebc2a969af386ccec1b66a1188e674fe0f45a924fa6b4af015011f
4
+ data.tar.gz: 16f88b8ada5064e7d549bbc74eac806bf1626c3f4db24b68a1adf24666a61e32
5
5
  SHA512:
6
- metadata.gz: c734a5177395f6981fa7733ee0a6c44c1839ca080eb18ad9c4c6e8d12f7adb2948f7710c5d5d2c13015c855cce03169b0ddf5b93254e17929173e3a81503501b
7
- data.tar.gz: 3db00f88ca7dd063ace17729b41cc5c5c7e30b3b77ef37a15ccee83873cbe02caf1460bd30b397a4d28a7d3971179f57d9b0194a186ebf05b194e90a050ed650
6
+ metadata.gz: 24e8d3882ed187521d99851b5ba33c85f99d24b008e30801c31c526cfe372663915c2610e46157788712c7d4ea0d6915859142237cde6fe135197536acec58d8
7
+ data.tar.gz: 3398427f84e45f4eb6119345654e5b1570637ff7924d69d3495a856e5aa841b504c8c1f4773f24a813760f627128a8ccfdf1fa2e5f6b365f304ac070fc1aeb46
data/.rubocop.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  AllCops:
2
- TargetRubyVersion: 2.6
2
+ TargetRubyVersion: 3.0
3
3
 
4
4
  Style/StringLiterals:
5
5
  Enabled: true
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.7.8
1
+ 3.4.4
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  ## Change Log
2
2
 
3
+ ## 0.3.0 - 2025-11-26
4
+ * Rename reserved object_id to target_id in https://github.com/rdytech/superset-client/pull/58
5
+ * Add dashboard cascade ownship in https://github.com/rdytech/superset-client/pull/59
6
+ * allow dup dashboard dataset suffix naming override in https://github.com/rdytech/superset-client/pull/61
7
+ * Bump ruby 3 bump gems by https://github.com/rdytech/superset-client/pull/60
8
+
9
+
3
10
  ## 0.2.7 - 2025-10-16
4
11
  * add to_h method to list classes for Hash output
5
12
  * add ids method to list classes for array of ids output
data/Dockerfile CHANGED
@@ -1,5 +1,5 @@
1
- # 1: Use ruby 2.7 as base:
2
- FROM ruby:2.7
1
+ # 1: Use ruby 3.4.4 as base:
2
+ FROM ruby:3.4.4
3
3
 
4
4
  RUN apt-get update && \
5
5
  apt-get install -y --no-install-recommends \
data/README.md CHANGED
@@ -22,11 +22,14 @@ Build, bundle and open a ruby console
22
22
  docker-compose build
23
23
  docker-compose run --rm app bundle install
24
24
  docker-compose run --rm app bin/console
25
+
26
+ # note .. windows users may need to call ruby the bin/console file
27
+ docker-compose run --rm app ruby bin/console
25
28
  ```
26
29
 
27
- ## Setup Locally ( no docker )
30
+ ## Setup Locally ( no docker )
28
31
 
29
- Requires Ruby >= 2.6.0
32
+ Requires Ruby >= 3
30
33
 
31
34
  Bundle install and open a ruby console.
32
35
 
@@ -37,7 +40,7 @@ bin/console
37
40
 
38
41
  ## Including in a Ruby app
39
42
 
40
- Add to your Gemfile `gem 'superset'`
43
+ Add to your Gemfile `gem 'superset'`
41
44
  And then execute: `bundle install`
42
45
  Or install it yourself as `gem install superset`
43
46
 
@@ -36,7 +36,7 @@ then you could go ahead and run something like this.
36
36
  ```ruby
37
37
  Superset::Services::DuplicateDashboard.new(
38
38
  source_dashboard_id: 90,
39
- target_schema: 'public',
39
+ target_schema: 'client_1',
40
40
  target_database_id: 2
41
41
  ).perform
42
42
 
@@ -47,12 +47,12 @@ Superset::Services::DuplicateDashboard.new(
47
47
  # cat log/superset-client.log
48
48
  # INFO -- : >>>>>>>>>>>>>>>>> Starting DuplicateDashboard Service <<<<<<<<<<<<<<<<<<<<<<
49
49
  # INFO -- : Source Dashboard URL: https://your-superset-host/superset/dashboard/90/
50
- # INFO -- : Duplicating dashboard 90 into Target Schema: public in database 2
50
+ # INFO -- : Duplicating dashboard 90 into Target Schema: client_1 in database 2
51
51
  # INFO -- : Copy Dashboard/Charts Completed - New Dashboard ID: 401
52
52
  # INFO -- : Duplicating Source Dataset examples.video_game_sales with id 11
53
- # INFO -- : Finished. Duplicate Dataset Name video_game_sales-example_two with id 542
54
- # INFO -- : Validating Dataset ID: 542 schema update to public on Database: 2
55
- # INFO -- : Successfully updated dataset schema to public on Database: 2
53
+ # INFO -- : Finished. Duplicate Dataset Name video_game_sales-example_two-client_1 with id 542
54
+ # INFO -- : Validating Dataset ID: 542 schema update to client_1 on Database: 2
55
+ # INFO -- : Successfully updated dataset schema to client_1 on Database: 2
56
56
  # INFO -- : Updating Charts to point to New Datasets and updating Dashboard json_metadata ...
57
57
  # INFO -- : Update Chart 55752 to new dataset_id 542
58
58
  # INFO -- : Updated new Dashboard json_metadata charts with new dataset ids
@@ -71,14 +71,53 @@ If your using the embedded dashboards you can also provied attributes for
71
71
  ```ruby
72
72
  Superset::Services::DuplicateDashboard.new(
73
73
  source_dashboard_id: 37,
74
- target_schema: 'public',
74
+ target_schema: 'client_1',
75
75
  target_database_id: 2,
76
76
  allowed_domains: ["https://wylee-coyote-domain/"],
77
77
  tags: ["product:acme_fu", "client:wylee_coyote", "embedded"],
78
78
  publish: true
79
79
  ).perform
80
+
81
+
82
+
83
+ ```
84
+
85
+ ## Duplication when DB schemas are not used
86
+
87
+ If your multitenant setup is at the database level, and database structures are replicated into the same base schema, ie public,
88
+ then the target resulted datasets name must be overridden in order to get around the superset unique datasets name validation restriction. This can be done through the parameter 'target_dataset_suffix_override'.
89
+
90
+ ```ruby
91
+ Superset::Services::DuplicateDashboard.new(
92
+ source_dashboard_id: 90,
93
+ target_schema: 'public',
94
+ target_database_id: 2,
95
+ target_dataset_suffix_override: 'client_1'
96
+ ).perform
97
+
80
98
  ```
81
99
 
100
+ ## Duplication now supports catalogs
101
+
102
+ If you Database infrastructure uses catalogs, you can provide the target catalog as a paramater.
103
+ If there is only 1 catalog in the target database, then it will be used by default.
104
+
105
+ If multiple catalogs exist, and no catalog option is provided, an error will be raised.
106
+
107
+ ```ruby
108
+ Superset::Services::DuplicateDashboard.new(
109
+ source_dashboard_id: 90,
110
+ target_schema: 'public',
111
+ target_database_id: 2,
112
+ target_dataset_suffix_override: 'client_1',
113
+ target_catalog_name: 'staging_client_1',
114
+ ).perform
115
+
116
+ ```
117
+
118
+
119
+
120
+
82
121
  ### What is my Database ID ?
83
122
 
84
123
  ``` ruby
@@ -1,9 +1,9 @@
1
1
  module Superset
2
2
  class BasePutRequest < Superset::Request
3
- attr_reader :object_id, :params
3
+ attr_reader :target_id, :params
4
4
 
5
- def initialize(object_id: ,params: )
6
- @object_id = object_id
5
+ def initialize(target_id: ,params: )
6
+ @target_id = target_id
7
7
  @params = params
8
8
  end
9
9
 
@@ -19,7 +19,7 @@ module Superset
19
19
  private
20
20
 
21
21
  def validate
22
- raise "Error: object_id integer is required" unless object_id.present? && object_id.is_a?(Integer)
22
+ raise "Error: target_id integer is required" unless target_id.present? && target_id.is_a?(Integer)
23
23
  raise "Error: params hash is required" unless params.present? && params.is_a?(Hash)
24
24
  end
25
25
 
@@ -18,7 +18,7 @@ module Superset
18
18
  raise InvalidParameterError, "chart_ids array of integers expected" unless chart_ids.is_a?(Array)
19
19
  raise InvalidParameterError, "chart_ids array must contain Integer only values" unless chart_ids.all? { |item| item.is_a?(Integer) }
20
20
 
21
- logger.info("Attempting to delete charts with id: #{chart_ids.join(', ')}")
21
+ logger.info("Deleting charts with id: #{chart_ids.join(', ')}")
22
22
  response
23
23
  end
24
24
 
@@ -12,7 +12,7 @@ module Superset
12
12
  def perform
13
13
  raise InvalidParameterError, "chart_id integer is required" unless chart_id.present? && chart_id.is_a?(Integer)
14
14
 
15
- logger.info("Attempting to delete chart with id: #{chart_id}")
15
+ logger.info("Deleting chart with id: #{chart_id}")
16
16
  response
17
17
  end
18
18
 
@@ -17,28 +17,24 @@ module Superset
17
17
  self
18
18
  end
19
19
 
20
- def result
21
- [ super ]
22
- end
23
-
24
20
  def datasource_id
25
- if result.first['query_context'].present? && JSON.parse(result.first['query_context'])['datasource'].present?
26
- JSON.parse(result.first['query_context'])['datasource']['id']
27
- elsif result.first['params'].present? && JSON.parse(result.first['params'])['datasource'].present?
28
- JSON.parse(result.first['params'])['datasource'].match(/^\d+/)[0].to_i
21
+ if result['query_context'].present? && JSON.parse(result['query_context'])['datasource'].present?
22
+ JSON.parse(result['query_context'])['datasource']['id']
23
+ elsif result['params'].present? && JSON.parse(result['params'])['datasource'].present?
24
+ JSON.parse(result['params'])['datasource'].match(/^\d+/)[0].to_i
29
25
  end
30
26
  end
31
27
 
32
28
  def owner_ids
33
- result.first['owners'].map{|o| o['id']}
29
+ result['owners'].map{|o| o['id']}
34
30
  end
35
31
 
36
32
  def params
37
- JSON.parse(result.first['params']) if result.first['params'].present?
33
+ JSON.parse(result['params']) if result['params'].present?
38
34
  end
39
35
 
40
36
  def query_context
41
- JSON.parse(result.first['query_context']) if result.first['query_context'].present?
37
+ JSON.parse(result['query_context']) if result['query_context'].present?
42
38
  end
43
39
 
44
40
  private
@@ -2,7 +2,7 @@
2
2
  #
3
3
  # Usage:
4
4
  # params = { owners: [ 58, 3 ] }
5
- # Superset::Chart::Put.new(object_id: 202, params: params ).perform
5
+ # Superset::Chart::Put.new(target_id: 202, params: params ).perform
6
6
 
7
7
  module Superset
8
8
  module Chart
@@ -11,7 +11,7 @@ module Superset
11
11
  private
12
12
 
13
13
  def route
14
- "chart/#{object_id}"
14
+ "chart/#{target_id}"
15
15
  end
16
16
  end
17
17
  end
@@ -20,7 +20,7 @@ module Superset
20
20
  raise InvalidParameterError, "dashboard_ids array of integers expected" unless dashboard_ids.is_a?(Array)
21
21
  raise InvalidParameterError, "dashboard_ids array must contain Integer only values" unless dashboard_ids.all? { |item| item.is_a?(Integer) }
22
22
 
23
- logger.info("Attempting to delete dashboards with id: #{dashboard_ids.join(', ')}")
23
+ logger.info("Deleting dashboards with id: #{dashboard_ids.join(', ')}")
24
24
  response
25
25
  end
26
26
 
@@ -0,0 +1,54 @@
1
+ module Superset
2
+ module Dashboard
3
+ module CascadeOwnership
4
+ class AddNewOwner < Superset::Request
5
+ attr_reader :dashboard_id, :user_id
6
+
7
+ def initialize(dashboard_id:, user_id:)
8
+ @dashboard_id = dashboard_id
9
+ @user_id = user_id
10
+ end
11
+
12
+ def perform
13
+ raise "Error: dashboard_id integer is required" unless dashboard_id.present? && dashboard_id.is_a?(Integer)
14
+ raise "Error: user_id integer is required" unless user_id.present? && user_id.is_a?(Integer)
15
+
16
+ add_user_to_dashboard_ownership
17
+ add_user_to_charts_ownership
18
+ add_user_to_datasets_ownership
19
+ end
20
+
21
+ private
22
+
23
+ def add_user_to_dashboard_ownership
24
+ return if current_dashboard_owner_ids.include?(user_id)
25
+ Superset::Dashboard::Put.new(target_id: dashboard_id, params: { "owners": current_dashboard_owner_ids << user_id }).perform
26
+ end
27
+
28
+ def add_user_to_charts_ownership
29
+ chart_ids = Superset::Dashboard::Charts::List.new(dashboard_id).ids
30
+ chart_ids.each do |chart_id|
31
+ current_chart_owner_ids = Superset::Chart::Get.new(chart_id).result['owners'].map{|c| c['id']}
32
+ next if current_chart_owner_ids.include?(user_id)
33
+
34
+ Superset::Chart::Put.new(target_id: chart_id, params: { "owners": current_chart_owner_ids << user_id }).perform
35
+ end
36
+ end
37
+
38
+ def add_user_to_datasets_ownership
39
+ dataset_ids = Superset::Dashboard::Datasets::List.new(dashboard_id: dashboard_id).ids
40
+ dataset_ids.each do |dataset_id|
41
+ current_dataset_owner_ids = Superset::Dataset::Get.new(dataset_id).result['owners'].map{|c| c['id']}
42
+ next if current_dataset_owner_ids.include?(user_id)
43
+
44
+ Superset::Dataset::Put.new(target_id: dataset_id, params: { "owners": current_dataset_owner_ids << user_id }).perform
45
+ end
46
+ end
47
+
48
+ def current_dashboard_owner_ids
49
+ @current_dashboard_owner_ids ||= Superset::Dashboard::Get.new(dashboard_id).result['owners'].map{|i| i['id'] }
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -30,7 +30,7 @@ module Superset
30
30
  # For the current superset setup we will assume a dashboard datasets will point to EXACTLY one schema, their own.
31
31
  # if not .. we need to know about it. Potentially we could override this check if others do not consider it a problem.
32
32
  if all_dashboard_schemas.count > 1
33
- Rollbar.error("SUPERSET DASHBOARD ERROR: Dashboard id #{id} has multiple dataset schema linked: #{all_dashboard_schemas.to_s}")
33
+ Rollbar.error("SUPERSET DASHBOARD ERROR: Dashboard id #{id} has multiple dataset schema linked: #{all_dashboard_schemas.to_s}") if defined?(Rollbar)
34
34
  end
35
35
  all_dashboard_schemas
36
36
  end
@@ -48,6 +48,20 @@ module Superset
48
48
  chart_datasets + filter_datasets(filter_dataset_ids_not_used_in_charts)
49
49
  end
50
50
 
51
+ def rows
52
+ datasets_details.map do |d|
53
+ [
54
+ d[:id],
55
+ d[:datasource_name],
56
+ d[:database][:id],
57
+ d[:database][:name],
58
+ d[:database][:backend],
59
+ d[:schema],
60
+ d[:filter_only]
61
+ ]
62
+ end
63
+ end
64
+
51
65
  private
52
66
 
53
67
  def filter_dataset_ids
@@ -74,20 +88,6 @@ module Superset
74
88
  ['id', 'datasource_name', 'database_id', 'database_name', 'database_backend', 'schema', 'filter_only'].map(&:to_sym)
75
89
  end
76
90
 
77
- def rows
78
- datasets_details.map do |d|
79
- [
80
- d[:id],
81
- d[:datasource_name],
82
- d[:database][:id],
83
- d[:database][:name],
84
- d[:database][:backend],
85
- d[:schema],
86
- d[:filter_only]
87
- ]
88
- end
89
- end
90
-
91
91
  # when displaying a list of datasets, show dashboard title as well
92
92
  def title
93
93
  @title ||= [id, dashboard.title].join(' ')
@@ -16,7 +16,7 @@ module Superset
16
16
 
17
17
  confirm_zero_charts_on_dashboard if confirm_zero_charts
18
18
 
19
- logger.info("Attempting to delete dashboard with id: #{dashboard_id}")
19
+ logger.info("Deleting dashboard with id: #{dashboard_id}")
20
20
  response
21
21
  end
22
22
 
@@ -1,36 +1,19 @@
1
1
 
2
2
  # frozen_string_literal: true
3
3
 
4
+ # Example Usage: Update Dashboard Ownership to a new set of users
5
+ # this will override the existing owners and set the new owners
6
+ # params = { owners: [ 58, 3 ] }
7
+ # Superset::Dashboard::Put.new(target_id: 101, params: params ).perform
8
+
4
9
  module Superset
5
10
  module Dashboard
6
- class Put < Superset::Request
7
-
8
- attr_reader :target_dashboard_id, :params
9
-
10
- def initialize(target_dashboard_id:, params:)
11
- @target_dashboard_id = target_dashboard_id
12
- @params = params
13
- end
14
-
15
- def perform
16
- raise "Error: target_dashboard_id integer is required" unless target_dashboard_id.present? && target_dashboard_id.is_a?(Integer)
17
- raise "Error: params hash is required" unless params.present? && params.is_a?(Hash)
18
-
19
- response
20
- end
21
-
22
- def response
23
- @response ||= client.put(route, params)
24
- end
25
-
26
- def id
27
- response["result"]["id"]
28
- end
11
+ class Put < Superset::BasePutRequest
29
12
 
30
13
  private
31
14
 
32
15
  def route
33
- "dashboard/#{target_dashboard_id}"
16
+ "dashboard/#{target_id}"
34
17
  end
35
18
  end
36
19
  end
@@ -19,7 +19,7 @@ module Superset
19
19
  begin
20
20
  warm_up_dataset(dataset["datasource_name"], dataset["name"])
21
21
  rescue => e
22
- Rollbar.error("Warm up cache failed for the dashboard #{dashboard_id.to_s} and for the dataset #{dataset["datasource_name"]} - #{e}")
22
+ Rollbar.error("Warm up cache failed for the dashboard #{dashboard_id.to_s} and for the dataset #{dataset["datasource_name"]} - #{e}") if defined?(Rollbar)
23
23
  end
24
24
  end
25
25
  end
@@ -19,7 +19,7 @@ module Superset
19
19
  raise InvalidParameterError, "dataset_ids array of integers expected" unless dataset_ids.is_a?(Array)
20
20
  raise InvalidParameterError, "dataset_ids array must contain Integer only values" unless dataset_ids.all? { |item| item.is_a?(Integer) }
21
21
 
22
- logger.info("Attempting to delete datasets with id: #{dataset_ids.join(', ')}")
22
+ logger.info("Deleting datasets with id: #{dataset_ids.join(', ')}")
23
23
  response
24
24
  end
25
25
 
@@ -12,7 +12,7 @@ module Superset
12
12
  def perform
13
13
  raise InvalidParameterError, "dataset_id integer is required" unless dataset_id.present? && dataset_id.is_a?(Integer)
14
14
 
15
- logger.info("Attempting to delete dataset with id: #{dataset_id}")
15
+ logger.info("Deleting dataset with id: #{dataset_id}")
16
16
  response
17
17
  end
18
18
 
@@ -2,7 +2,7 @@
2
2
  #
3
3
  # Usage:
4
4
  # params = { owners: [ 58, 3 ] }
5
- # Superset::Dataset::Put.new(object_id: 101, params: params ).perform
5
+ # Superset::Dataset::Put.new(target_id: 101, params: params ).perform
6
6
 
7
7
  module Superset
8
8
  module Dataset
@@ -11,7 +11,7 @@ module Superset
11
11
  private
12
12
 
13
13
  def route
14
- "dataset/#{object_id}"
14
+ "dataset/#{target_id}"
15
15
  end
16
16
  end
17
17
  end
@@ -9,15 +9,17 @@ module Superset
9
9
  module Services
10
10
  class DuplicateDashboard < Superset::Request
11
11
 
12
- attr_reader :source_dashboard_id, :target_schema, :target_database_id, :allowed_domains, :tags, :publish
12
+ attr_reader :source_dashboard_id, :target_schema, :target_database_id, :target_dataset_suffix_override, :allowed_domains, :tags, :publish, :target_catalog_name
13
13
 
14
- def initialize(source_dashboard_id:, target_schema:, target_database_id: , allowed_domains: [], tags: [], publish: false)
14
+ def initialize(source_dashboard_id:, target_schema:, target_database_id: , target_dataset_suffix_override: nil, allowed_domains: [], tags: [], publish: false, target_catalog_name: nil)
15
15
  @source_dashboard_id = source_dashboard_id
16
16
  @target_schema = target_schema
17
17
  @target_database_id = target_database_id
18
+ @target_dataset_suffix_override = target_dataset_suffix_override
18
19
  @allowed_domains = allowed_domains
19
20
  @tags = tags
20
21
  @publish = publish
22
+ @target_catalog_name = target_catalog_name
21
23
  end
22
24
 
23
25
  def perform
@@ -55,6 +57,11 @@ module Superset
55
57
 
56
58
  rescue => e
57
59
  logger.error("#{e.message}")
60
+ remove_duplicated_objects
61
+ puts "------------------------------------------------------------------------------\n"
62
+ puts "DUPLICATE DASHBOARD FAILED - ERROR: #{e.message}\n"
63
+ puts "REMOVED DUPLICATED OBJECTS - Check log/superset-client.log for more details\n"
64
+ puts "------------------------------------------------------------------------------\n"
58
65
  raise e
59
66
  end
60
67
 
@@ -67,12 +74,12 @@ module Superset
67
74
  def add_tags_to_new_dashboard
68
75
  return unless tags.present?
69
76
 
70
- Superset::Tag::AddToObject.new(object_type_id: ObjectType::DASHBOARD, object_id: new_dashboard.id, tags: tags).perform
77
+ Superset::Tag::AddToObject.new(object_type_id: ObjectType::DASHBOARD, target_id: new_dashboard.id, tags: tags).perform
71
78
  logger.info " Added tags to dashboard #{new_dashboard.id}: #{tags}"
72
79
  rescue => e
73
80
  # catching tag error and display in log .. but also alowing the process to finish logs as tag error is fairly insignificant
74
- logger.error(" FAILED to add tags to new dashboard id: #{new_dashboard.id}. Error is #{e.message}")
75
- logger.error(" Missing Tags Values are #{tags}")
81
+ logger.error(" FAILED to add tags to new dashboard id: #{new_dashboard.id}. Error: #{e.message}")
82
+ raise ValidationError, "Failed to add tags to new dashboard id: #{new_dashboard.id}. #{e.message}. Missing Tags Values are #{tags}"
76
83
  end
77
84
 
78
85
  def created_embedded_config
@@ -90,35 +97,39 @@ module Superset
90
97
 
91
98
  def duplicate_source_dashboard_datasets
92
99
  source_dashboard_datasets.each do |dataset|
93
- # duplicate the dataset, renaming to use of suffix as the target_schema
94
- # reason: there is a bug(or feature) in the SS API where a dataset name must be uniq when duplicating.
95
- # (note however renaming in the GUI to a dup name works fine)
96
- new_dataset_name = "#{dataset[:datasource_name]}-#{target_schema}"
97
- existing_datasets = Superset::Dataset::List.new(title_equals: new_dataset_name, schema_equals: target_schema).result
100
+ # duplicate the dataset, renaming to use of suffix as the target_schema OR target_dataset_suffix_override value
101
+ # reason: there is a bug(or feature) in the SS where a dataset name must be uniq when duplicating
102
+ target_dataset_name = new_dataset_name(dataset[:datasource_name])
103
+ existing_datasets = Superset::Dataset::List.new(title_equals: target_dataset_name, schema_equals: target_schema).result
98
104
  if existing_datasets.any?
99
- logger.info "Dataset #{existing_datasets[0]["table_name"]} already exists. Reusing it"
100
- new_dataset_id = existing_datasets[0]["id"] # assuming that we do not name multiple datasets with same name in a single schema
105
+ logger.info " Dataset #{target_dataset_name} already exists. Reusing it"
106
+ new_dataset_id = existing_datasets[0]["id"]
101
107
  else
102
- new_dataset_id = Superset::Dataset::Duplicate.new(source_dataset_id: dataset[:id], new_dataset_name: new_dataset_name).perform
108
+ new_dataset_id = Superset::Dataset::Duplicate.new(source_dataset_id: dataset[:id], new_dataset_name: target_dataset_name).perform
103
109
  # update the new dataset with the target schema, database, catalog
104
110
  Superset::Dataset::UpdateSchema.new(
105
111
  source_dataset_id: new_dataset_id,
106
112
  target_database_id: target_database_id,
107
113
  target_schema: target_schema,
108
- target_catalog: target_database_catalog).perform
114
+ target_catalog: validated_target_database_catalog_name).perform
109
115
  end
110
116
  # keep track of the previous dataset and the matching new dataset_id
111
117
  dataset_duplication_tracker << { source_dataset_id: dataset[:id], new_dataset_id: new_dataset_id }
112
118
  end
113
119
  end
114
120
 
121
+ # if a suffix is provided, use it to suffix the dataset name
122
+ # if no suffix is provided, use the schema name as the suffix
123
+ def new_dataset_name(dataset_name)
124
+ return "#{dataset_name}-#{target_schema}" if target_dataset_suffix_override.blank?
125
+ "#{dataset_name}-#{target_dataset_suffix_override.downcase}"
126
+ end
127
+
115
128
  def update_charts_with_new_datasets
116
129
  logger.info "Updating Charts to point to New Datasets and updating Dashboard json_metadata ..."
117
130
  # note dashboard json_metadata currently still points to the old chart ids and is updated here
118
-
119
131
  new_dashboard_json_metadata_json_string = new_dashboard_json_metadata_configuration.to_json # need to convert to string for gsub
120
- # get all chart ids for the new dashboard
121
- new_charts_list = Superset::Dashboard::Charts::List.new(new_dashboard.id).result
132
+
122
133
  new_chart_ids_list = new_charts_list&.map { |r| r['id'] }&.compact
123
134
  # get all chart details for the source dashboard
124
135
  original_charts = Superset::Dashboard::Charts::List.new(source_dashboard_id).result.map { |r| [r['slice_name'], r['id']] }.to_h
@@ -148,6 +159,11 @@ module Superset
148
159
  @new_dashboard_json_metadata_configuration = JSON.parse(new_dashboard_json_metadata_json_string)
149
160
  end
150
161
 
162
+ def new_charts_list
163
+ # get all chart ids for the new dashboard
164
+ @new_charts_list ||= Superset::Dashboard::Charts::List.new(new_dashboard.id).result
165
+ end
166
+
151
167
  def duplicate_source_dashboard_filters
152
168
  return unless source_dashboard_filter_dataset_ids.length.positive?
153
169
 
@@ -164,11 +180,11 @@ module Superset
164
180
 
165
181
  def update_source_dashboard_json_metadata
166
182
  logger.info " Updated new Dashboard json_metadata charts with new dataset ids"
167
- Superset::Dashboard::Put.new(target_dashboard_id: new_dashboard.id, params: { "json_metadata" => @new_dashboard_json_metadata_configuration.to_json }).perform
183
+ Superset::Dashboard::Put.new(target_id: new_dashboard.id, params: { "json_metadata" => @new_dashboard_json_metadata_configuration.to_json }).perform
168
184
  end
169
185
 
170
186
  def publish_dashboard
171
- Superset::Dashboard::Put.new(target_dashboard_id: new_dashboard.id, params: { published: publish } ).perform
187
+ Superset::Dashboard::Put.new(target_id: new_dashboard.id, params: { published: publish } ).perform
172
188
  end
173
189
 
174
190
  def new_dashboard
@@ -209,7 +225,7 @@ module Superset
209
225
  raise ValidationError, "One or more source dashboard filters point to a different schema than the dashboard charts. Identified Unpermittied Filter Dataset Ids are #{unpermitted_filter_dataset_ids.to_s}" if unpermitted_filter_dataset_ids.any?
210
226
 
211
227
  # new dataset validations - Need to be commented for EU dashboard duplication as we are using the existing datasets for the new dashboard
212
- raise ValidationError, "DATASET NAME CONFLICT: The Target Schema #{target_schema} already has existing datasets named: #{target_schema_matching_dataset_names.join(',')}" unless target_schema_matching_dataset_names.empty?
228
+ raise ValidationError, "DATASET NAME CONFLICT: The Target Database #{target_database_id} with Schema #{target_schema} already has existing datasets named: #{target_schema_matching_dataset_names.join(',')}" unless target_schema_matching_dataset_names.empty?
213
229
  validate_source_dashboard_datasets_sql_does_not_hard_code_schema
214
230
 
215
231
  # embedded allowed_domain validations
@@ -218,8 +234,8 @@ module Superset
218
234
 
219
235
  def validate_source_dashboard_datasets_sql_does_not_hard_code_schema
220
236
  errors = source_dashboard_datasets.map do |dataset|
221
- "The Dataset ID #{dataset[:id]} SQL query is hard coded with the schema value and can not be duplicated cleanly. " +
222
- "Remove all direct embedded schema calls from the Dataset SQL query before continuing." if dataset[:sql].include?("#{dataset[:schema]}.")
237
+ "The Dataset ID #{dataset[:id]} SQL query is hard coded with the schema value: #{dataset[:schema]}. This indicates that the dataset can not be duplicated cleanly to point to the target schema. " +
238
+ "Remove all direct embedded schema calls from the Dataset SQL query before continuing." if dataset[:sql]&.include?("#{dataset[:schema]}.")
223
239
  end.compact
224
240
  raise ValidationError, errors.join("\n") unless errors.empty?
225
241
  end
@@ -245,13 +261,15 @@ module Superset
245
261
  source_dashboard_datasets.map { |dataset| dataset[:datasource_name] }.uniq
246
262
  end
247
263
 
248
- # identify any already existing datasets in the target schema that have the same name as the source dashboard datasets
264
+ # identify any already existing datasets in the target database and schema that have the same name as the source dashboard datasets + suffix
249
265
  # note this is prior to adding the (COPY) suffix
250
- # here we will need to decide if we want to use the existing dataset or not see NEP-????
266
+ # here we will need to decide if we want to use the existing dataset or not
251
267
  # for now we will exit with an error if we find any existing datasets of the same name
252
268
  def target_schema_matching_dataset_names
253
269
  @target_schema_matching_dataset_names ||= source_dashboard_dataset_names.map do |source_dataset_name|
254
- existing_names = Superset::Dataset::List.new(title_contains: source_dataset_name, schema_equals: target_schema).result.map{|t|t['table_name']}.uniq # contains match to cover with suffix as well
270
+ source_dataset_name_with_suffix = new_dataset_name(source_dataset_name)
271
+
272
+ existing_names = Superset::Dataset::List.new(title_contains: source_dataset_name_with_suffix, database_id_eq: target_database_id, schema_equals: target_schema).result.map{|t|t['table_name']}.uniq # contains match to cover with suffix as well
255
273
  unless existing_names.flatten.empty?
256
274
  logger.error " HALTING PROCESS: Schema #{target_schema} already has Dataset called #{existing_names}"
257
275
  end
@@ -267,11 +285,13 @@ module Superset
267
285
  @filter_dataset_ids ||= source_dashboard.filter_configuration.map { |c| c['targets'] }.flatten.compact.map { |c| c['datasetId'] }.flatten.compact.uniq
268
286
  end
269
287
 
270
- # currently does not support multiple catalogs, assumption is that 1 catalog is used per database
271
- def target_database_catalog
288
+ # if multiple catalogs are present, the target_catalog_name must be provided, otherwise use the first catalog
289
+ def validated_target_database_catalog_name
272
290
  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
291
+ return target_catalog_name if catalogs.include?(target_catalog_name)
292
+
293
+ raise ValidationError, "Target Database #{target_database_id} has multiple catalogs, must provide target_catalog_name" if catalogs.size > 1 && target_catalog_name.blank?
294
+ @validated_target_database_catalog_name ||= catalogs.find { |c| c['name'] == target_catalog_name } || catalogs.first
275
295
  end
276
296
 
277
297
  # Primary Assumption is that all charts datasets on the source dashboard are pointing to the same database schema
@@ -290,6 +310,21 @@ module Superset
290
310
  end
291
311
  end
292
312
 
313
+ # remove the confirmed duplicated objects if the process fails
314
+ # do not use Dashboard::BulkDeleteCascade here as it may remove datasets from the source dashboard as well
315
+ def remove_duplicated_objects
316
+ logger.info "Removing duplicated objects ..."
317
+
318
+ new_dataset_ids = dataset_duplication_tracker&.map { |dataset| dataset[:new_dataset_id] }.compact
319
+ Superset::Dataset::BulkDelete.new(dataset_ids: new_dataset_ids).perform if new_dataset_ids.any?
320
+
321
+ new_chart_ids = new_charts_list&.map { |r| r['id'] }.compact
322
+ Superset::Chart::BulkDelete.new(chart_ids: new_chart_ids).perform if new_chart_ids.any?
323
+
324
+ Superset::Dashboard::Delete.new(dashboard_id: new_dashboard.id).perform if new_dashboard.id.present?
325
+ logger.info "Removed duplicated objects successfully."
326
+ end
327
+
293
328
  def logger
294
329
  @logger ||= Superset::Logger.new
295
330
  end
@@ -4,11 +4,11 @@ module Superset
4
4
  module Tag
5
5
  class AddToObject < Superset::Request
6
6
 
7
- attr_reader :object_type_id, :object_id, :tags
7
+ attr_reader :object_type_id, :target_id, :tags
8
8
 
9
- def initialize(object_type_id:, object_id:, tags: [])
9
+ def initialize(object_type_id:, target_id:, tags: [])
10
10
  @object_type_id = object_type_id
11
- @object_id = object_id
11
+ @target_id = target_id
12
12
  @tags = tags
13
13
  end
14
14
 
@@ -31,7 +31,7 @@ module Superset
31
31
  def validate_constructor_args
32
32
  raise InvalidParameterError, "object_type_id integer is required" unless object_type_id.present? && object_type_id.is_a?(Integer)
33
33
  raise InvalidParameterError, "object_type_id is not a known value" unless ObjectType.list.include?(object_type_id)
34
- raise InvalidParameterError, "object_id integer is required" unless object_id.present? && object_id.is_a?(Integer)
34
+ raise InvalidParameterError, "target_id integer is required" unless target_id.present? && target_id.is_a?(Integer)
35
35
  raise InvalidParameterError, "tags array is required" unless tags.present? && tags.is_a?(Array)
36
36
  raise InvalidParameterError, "tags array must contain string only values" unless tags.all? { |item| item.is_a?(String) }
37
37
  end
@@ -39,7 +39,7 @@ module Superset
39
39
  private
40
40
 
41
41
  def route
42
- "tag/#{object_type_id}/#{object_id}/"
42
+ "tag/#{object_type_id}/#{target_id}/"
43
43
  end
44
44
  end
45
45
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Superset
4
- VERSION = "0.2.7"
4
+ VERSION = "0.3.1"
5
5
  end
data/lib/superset.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'require_all'
4
+ require 'terminal-table'
4
5
 
5
6
  require_rel "superset/credential"
6
7
  require_relative "superset/authenticator"
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.7.8"
14
+ spec.required_ruby_version = ">= 3"
15
15
 
16
16
  #spec.metadata["allowed_push_host"] = ""
17
17
 
@@ -34,21 +34,20 @@ Gem::Specification.new do |spec|
34
34
  "lib"
35
35
  ]
36
36
 
37
- # Uncomment to register a new dependency of your gem
38
- spec.add_dependency "dotenv", "~> 2.7"
39
- spec.add_dependency "json", "~> 2.6"
37
+ spec.add_dependency "json", ">= 2.0"
40
38
  spec.add_dependency "terminal-table", "~> 4.0"
41
- spec.add_dependency "rake", "~> 13.0"
42
- spec.add_dependency "rollbar", "~> 3.4"
43
- spec.add_dependency "require_all", "~> 3.0"
44
- spec.add_dependency "rubyzip", "~> 1.0"
39
+ spec.add_dependency "require_all", ">= 3.0"
40
+ spec.add_dependency "rubyzip", ">= 1.3"
45
41
  spec.add_dependency "faraday", "~> 1.0"
46
42
  spec.add_dependency "faraday-multipart", "~> 1.0"
47
- spec.add_dependency "enumerate_it", "~> 1.7.0"
48
-
49
- spec.add_development_dependency "rspec", "~> 3.0"
50
- spec.add_development_dependency "rubocop", "~> 1.5"
51
- spec.add_development_dependency "pry", "~> 0.14"
43
+ spec.add_dependency "enumerate_it", ">= 1.7"
44
+
45
+ spec.add_development_dependency "dotenv", ">= 2.0"
46
+ spec.add_development_dependency "rake", ">= 13.0"
47
+ spec.add_development_dependency "rspec", ">= 3.0"
48
+ spec.add_development_dependency "rubocop", ">= 1.0"
49
+ spec.add_development_dependency "pry", ">= 0.14"
50
+ spec.add_development_dependency "rollbar", ">= 3.0"
52
51
 
53
52
  # For more information and examples about making a new gem, check out our
54
53
  # guide at: https://bundler.io/guides/creating_gem.html
metadata CHANGED
@@ -1,43 +1,28 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: superset
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.7
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - jbat
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2025-10-24 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: dotenv
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '2.7'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '2.7'
27
12
  - !ruby/object:Gem::Dependency
28
13
  name: json
29
14
  requirement: !ruby/object:Gem::Requirement
30
15
  requirements:
31
- - - "~>"
16
+ - - ">="
32
17
  - !ruby/object:Gem::Version
33
- version: '2.6'
18
+ version: '2.0'
34
19
  type: :runtime
35
20
  prerelease: false
36
21
  version_requirements: !ruby/object:Gem::Requirement
37
22
  requirements:
38
- - - "~>"
23
+ - - ">="
39
24
  - !ruby/object:Gem::Version
40
- version: '2.6'
25
+ version: '2.0'
41
26
  - !ruby/object:Gem::Dependency
42
27
  name: terminal-table
43
28
  requirement: !ruby/object:Gem::Requirement
@@ -53,49 +38,49 @@ dependencies:
53
38
  - !ruby/object:Gem::Version
54
39
  version: '4.0'
55
40
  - !ruby/object:Gem::Dependency
56
- name: rake
41
+ name: require_all
57
42
  requirement: !ruby/object:Gem::Requirement
58
43
  requirements:
59
- - - "~>"
44
+ - - ">="
60
45
  - !ruby/object:Gem::Version
61
- version: '13.0'
46
+ version: '3.0'
62
47
  type: :runtime
63
48
  prerelease: false
64
49
  version_requirements: !ruby/object:Gem::Requirement
65
50
  requirements:
66
- - - "~>"
51
+ - - ">="
67
52
  - !ruby/object:Gem::Version
68
- version: '13.0'
53
+ version: '3.0'
69
54
  - !ruby/object:Gem::Dependency
70
- name: rollbar
55
+ name: rubyzip
71
56
  requirement: !ruby/object:Gem::Requirement
72
57
  requirements:
73
- - - "~>"
58
+ - - ">="
74
59
  - !ruby/object:Gem::Version
75
- version: '3.4'
60
+ version: '1.3'
76
61
  type: :runtime
77
62
  prerelease: false
78
63
  version_requirements: !ruby/object:Gem::Requirement
79
64
  requirements:
80
- - - "~>"
65
+ - - ">="
81
66
  - !ruby/object:Gem::Version
82
- version: '3.4'
67
+ version: '1.3'
83
68
  - !ruby/object:Gem::Dependency
84
- name: require_all
69
+ name: faraday
85
70
  requirement: !ruby/object:Gem::Requirement
86
71
  requirements:
87
72
  - - "~>"
88
73
  - !ruby/object:Gem::Version
89
- version: '3.0'
74
+ version: '1.0'
90
75
  type: :runtime
91
76
  prerelease: false
92
77
  version_requirements: !ruby/object:Gem::Requirement
93
78
  requirements:
94
79
  - - "~>"
95
80
  - !ruby/object:Gem::Version
96
- version: '3.0'
81
+ version: '1.0'
97
82
  - !ruby/object:Gem::Dependency
98
- name: rubyzip
83
+ name: faraday-multipart
99
84
  requirement: !ruby/object:Gem::Requirement
100
85
  requirements:
101
86
  - - "~>"
@@ -109,90 +94,103 @@ dependencies:
109
94
  - !ruby/object:Gem::Version
110
95
  version: '1.0'
111
96
  - !ruby/object:Gem::Dependency
112
- name: faraday
97
+ name: enumerate_it
113
98
  requirement: !ruby/object:Gem::Requirement
114
99
  requirements:
115
- - - "~>"
100
+ - - ">="
116
101
  - !ruby/object:Gem::Version
117
- version: '1.0'
102
+ version: '1.7'
118
103
  type: :runtime
119
104
  prerelease: false
120
105
  version_requirements: !ruby/object:Gem::Requirement
121
106
  requirements:
122
- - - "~>"
107
+ - - ">="
123
108
  - !ruby/object:Gem::Version
124
- version: '1.0'
109
+ version: '1.7'
125
110
  - !ruby/object:Gem::Dependency
126
- name: faraday-multipart
111
+ name: dotenv
127
112
  requirement: !ruby/object:Gem::Requirement
128
113
  requirements:
129
- - - "~>"
114
+ - - ">="
130
115
  - !ruby/object:Gem::Version
131
- version: '1.0'
132
- type: :runtime
116
+ version: '2.0'
117
+ type: :development
133
118
  prerelease: false
134
119
  version_requirements: !ruby/object:Gem::Requirement
135
120
  requirements:
136
- - - "~>"
121
+ - - ">="
137
122
  - !ruby/object:Gem::Version
138
- version: '1.0'
123
+ version: '2.0'
139
124
  - !ruby/object:Gem::Dependency
140
- name: enumerate_it
125
+ name: rake
141
126
  requirement: !ruby/object:Gem::Requirement
142
127
  requirements:
143
- - - "~>"
128
+ - - ">="
144
129
  - !ruby/object:Gem::Version
145
- version: 1.7.0
146
- type: :runtime
130
+ version: '13.0'
131
+ type: :development
147
132
  prerelease: false
148
133
  version_requirements: !ruby/object:Gem::Requirement
149
134
  requirements:
150
- - - "~>"
135
+ - - ">="
151
136
  - !ruby/object:Gem::Version
152
- version: 1.7.0
137
+ version: '13.0'
153
138
  - !ruby/object:Gem::Dependency
154
139
  name: rspec
155
140
  requirement: !ruby/object:Gem::Requirement
156
141
  requirements:
157
- - - "~>"
142
+ - - ">="
158
143
  - !ruby/object:Gem::Version
159
144
  version: '3.0'
160
145
  type: :development
161
146
  prerelease: false
162
147
  version_requirements: !ruby/object:Gem::Requirement
163
148
  requirements:
164
- - - "~>"
149
+ - - ">="
165
150
  - !ruby/object:Gem::Version
166
151
  version: '3.0'
167
152
  - !ruby/object:Gem::Dependency
168
153
  name: rubocop
169
154
  requirement: !ruby/object:Gem::Requirement
170
155
  requirements:
171
- - - "~>"
156
+ - - ">="
172
157
  - !ruby/object:Gem::Version
173
- version: '1.5'
158
+ version: '1.0'
174
159
  type: :development
175
160
  prerelease: false
176
161
  version_requirements: !ruby/object:Gem::Requirement
177
162
  requirements:
178
- - - "~>"
163
+ - - ">="
179
164
  - !ruby/object:Gem::Version
180
- version: '1.5'
165
+ version: '1.0'
181
166
  - !ruby/object:Gem::Dependency
182
167
  name: pry
183
168
  requirement: !ruby/object:Gem::Requirement
184
169
  requirements:
185
- - - "~>"
170
+ - - ">="
186
171
  - !ruby/object:Gem::Version
187
172
  version: '0.14'
188
173
  type: :development
189
174
  prerelease: false
190
175
  version_requirements: !ruby/object:Gem::Requirement
191
176
  requirements:
192
- - - "~>"
177
+ - - ">="
193
178
  - !ruby/object:Gem::Version
194
179
  version: '0.14'
195
- description:
180
+ - !ruby/object:Gem::Dependency
181
+ name: rollbar
182
+ requirement: !ruby/object:Gem::Requirement
183
+ requirements:
184
+ - - ">="
185
+ - !ruby/object:Gem::Version
186
+ version: '3.0'
187
+ type: :development
188
+ prerelease: false
189
+ version_requirements: !ruby/object:Gem::Requirement
190
+ requirements:
191
+ - - ">="
192
+ - !ruby/object:Gem::Version
193
+ version: '3.0'
196
194
  email:
197
195
  - jonathon.batson@gmail.com
198
196
  executables: []
@@ -233,6 +231,7 @@ files:
233
231
  - lib/superset/credential/embedded_user.rb
234
232
  - lib/superset/dashboard/bulk_delete.rb
235
233
  - lib/superset/dashboard/bulk_delete_cascade.rb
234
+ - lib/superset/dashboard/cascade_ownership/add_new_owner.rb
236
235
  - lib/superset/dashboard/charts/list.rb
237
236
  - lib/superset/dashboard/compare.rb
238
237
  - lib/superset/dashboard/copy.rb
@@ -293,7 +292,6 @@ homepage: https://github.com/rdytech/superset-client
293
292
  licenses:
294
293
  - MIT
295
294
  metadata: {}
296
- post_install_message:
297
295
  rdoc_options: []
298
296
  require_paths:
299
297
  - lib
@@ -301,15 +299,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
301
299
  requirements:
302
300
  - - ">="
303
301
  - !ruby/object:Gem::Version
304
- version: 2.7.8
302
+ version: '3'
305
303
  required_rubygems_version: !ruby/object:Gem::Requirement
306
304
  requirements:
307
305
  - - ">="
308
306
  - !ruby/object:Gem::Version
309
307
  version: '0'
310
308
  requirements: []
311
- rubygems_version: 3.1.6
312
- signing_key:
309
+ rubygems_version: 3.6.7
313
310
  specification_version: 4
314
311
  summary: A Ruby Client for Apache Superset API
315
312
  test_files: []