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.
Files changed (77) hide show
  1. checksums.yaml +7 -0
  2. data/.buildkite/pipeline.yml +16 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +13 -0
  5. data/CHANGELOG.md +48 -0
  6. data/Dockerfile +17 -0
  7. data/LICENSE +21 -0
  8. data/README.md +205 -0
  9. data/Rakefile +12 -0
  10. data/doc/duplicate_dashboards.md +214 -0
  11. data/doc/setting_up_personal_api_credentials.md +127 -0
  12. data/docker-compose.override.yml +10 -0
  13. data/docker-compose.yml +8 -0
  14. data/env.sample +9 -0
  15. data/lib/loggers/duplicate_dashboard_logger.rb +15 -0
  16. data/lib/superset/authenticator.rb +55 -0
  17. data/lib/superset/chart/bulk_delete.rb +40 -0
  18. data/lib/superset/chart/delete.rb +30 -0
  19. data/lib/superset/chart/get.rb +56 -0
  20. data/lib/superset/chart/list.rb +59 -0
  21. data/lib/superset/chart/update_dataset.rb +90 -0
  22. data/lib/superset/client.rb +53 -0
  23. data/lib/superset/credential/api_user.rb +25 -0
  24. data/lib/superset/credential/embedded_user.rb +25 -0
  25. data/lib/superset/dashboard/bulk_delete.rb +42 -0
  26. data/lib/superset/dashboard/bulk_delete_cascade.rb +52 -0
  27. data/lib/superset/dashboard/charts/list.rb +47 -0
  28. data/lib/superset/dashboard/compare.rb +94 -0
  29. data/lib/superset/dashboard/copy.rb +78 -0
  30. data/lib/superset/dashboard/datasets/list.rb +74 -0
  31. data/lib/superset/dashboard/delete.rb +42 -0
  32. data/lib/superset/dashboard/embedded/get.rb +56 -0
  33. data/lib/superset/dashboard/embedded/put.rb +35 -0
  34. data/lib/superset/dashboard/export.rb +98 -0
  35. data/lib/superset/dashboard/get.rb +51 -0
  36. data/lib/superset/dashboard/info.rb +17 -0
  37. data/lib/superset/dashboard/list.rb +99 -0
  38. data/lib/superset/dashboard/put.rb +37 -0
  39. data/lib/superset/dashboard/warm_up_cache.rb +42 -0
  40. data/lib/superset/database/get.rb +30 -0
  41. data/lib/superset/database/get_schemas.rb +25 -0
  42. data/lib/superset/database/list.rb +51 -0
  43. data/lib/superset/dataset/bulk_delete.rb +41 -0
  44. data/lib/superset/dataset/create.rb +62 -0
  45. data/lib/superset/dataset/delete.rb +30 -0
  46. data/lib/superset/dataset/duplicate.rb +62 -0
  47. data/lib/superset/dataset/get.rb +56 -0
  48. data/lib/superset/dataset/list.rb +41 -0
  49. data/lib/superset/dataset/update_query.rb +56 -0
  50. data/lib/superset/dataset/update_schema.rb +120 -0
  51. data/lib/superset/dataset/warm_up_cache.rb +41 -0
  52. data/lib/superset/display.rb +42 -0
  53. data/lib/superset/enumerations/object_type.rb +11 -0
  54. data/lib/superset/file_utilities.rb +19 -0
  55. data/lib/superset/guest_token.rb +69 -0
  56. data/lib/superset/logger.rb +20 -0
  57. data/lib/superset/request.rb +62 -0
  58. data/lib/superset/route_info.rb +34 -0
  59. data/lib/superset/security/permissions_resources/list.rb +22 -0
  60. data/lib/superset/security/role/create.rb +25 -0
  61. data/lib/superset/security/role/get.rb +32 -0
  62. data/lib/superset/security/role/list.rb +45 -0
  63. data/lib/superset/security/role/permission/create.rb +35 -0
  64. data/lib/superset/security/role/permission/get.rb +37 -0
  65. data/lib/superset/security/user/create.rb +49 -0
  66. data/lib/superset/security/user/get.rb +27 -0
  67. data/lib/superset/security/user/list.rb +42 -0
  68. data/lib/superset/services/duplicate_dashboard.rb +298 -0
  69. data/lib/superset/sqllab/execute.rb +52 -0
  70. data/lib/superset/tag/add_to_object.rb +46 -0
  71. data/lib/superset/tag/get.rb +30 -0
  72. data/lib/superset/tag/list.rb +37 -0
  73. data/lib/superset/version.rb +5 -0
  74. data/lib/superset.rb +17 -0
  75. data/log/README.md +4 -0
  76. data/superset.gemspec +55 -0
  77. metadata +300 -0
@@ -0,0 +1,127 @@
1
+ # API Credentials
2
+
3
+ Superset API Credentials are essentially the username, password and host of the Superset host users account.
4
+
5
+ If you know these already, plug them and your host value into the `.env` and it should all just work. A sample env.sample is provided as a template.
6
+
7
+ Create your own .env file with
8
+
9
+ ```
10
+ cp env.sample .env
11
+ ```
12
+
13
+ Adjust .env as required.
14
+ ```
15
+ SUPERSET_HOST="https://your-superset-host.com"
16
+ SUPERSET_API_USERNAME="your-username"
17
+ SUPERSET_API_PASSWORD="your-password"
18
+ ```
19
+
20
+ If you have multiple superset hosts across various environments you also have the option
21
+ to create individual env files per environment. More details below.
22
+
23
+ Starting a ruby console with `bin/console` will auto load the env vars.
24
+
25
+ ## What is my user name?
26
+
27
+ If your Superset instance is setup to authenicate via SSO then your authenticating agent will most likely have provided a username for you in the form of a UUID value.
28
+
29
+ This is easily retrieved on you User Profile page in Superset.
30
+
31
+ Optionally use jinja template macro in sql lab.
32
+
33
+ ```
34
+ select '{{ current_username() }}' as current_username;
35
+ ```
36
+
37
+ ## Creating / Updating your password via Swagger interface
38
+
39
+ A common setup is to use SSO to enable user access in Superset. This would mean your authenticating agent is your SSO provider service ( ie Azure ) and most likely you do not have / need a password configured for your Superset user for GUI access.
40
+
41
+ If this is the case, you will need to add your own password via hitting the superset API using the swagger interface.
42
+
43
+ Firstly you will need your superset user id, which is the superset users table PK that is assigned to you.
44
+
45
+ It appears this user id value is not exposed on the Users profile page in Superset. Depending on your level of access within your Superset instance you could:
46
+ - access the Users List page, find your user, and mouse over the edit button to reveal the Url and user id param value.
47
+ - got to sql lab and use a jinja template predefined macro to retrieve your users id.
48
+ ```
49
+ select '{{ current_user_id() }}' as current_user_id;
50
+ ```
51
+ - ask your superset admin to tell you what your personal superset user id is.
52
+
53
+ Once you have your user id value, open the Swagger API page on you superset host.
54
+ `https://{your-superset-host}/swagger/v1`
55
+
56
+ Scroll down to the 'Security Users' area and find the PUT request that will update your users record.
57
+
58
+ PUT `/api/v1/security/users/{pk}`
59
+
60
+ Click 'Try it Out' and add your users ID in the PK input box.
61
+
62
+ Edit the params to only consist of only the password field and the value of your new password.
63
+
64
+ ```
65
+ {
66
+ "password": "{some-long-complex-random-password-value}"
67
+ }
68
+ ```
69
+
70
+ And click Execute.
71
+
72
+ Within your `.env` now add your username and password.
73
+
74
+ # Accessing API across Multiple Environments
75
+
76
+ Given some local development requirements where you have to access multiple superset hosts across various environments with different credentials you can setup the env creds as follows.
77
+
78
+ Just set the overall superset environment in `.env`
79
+
80
+ ```
81
+ # .env file holding one setting for the overall superset environment
82
+ SUPERSET_ENVIRONMENT='staging'
83
+ ```
84
+
85
+ Then create a new file called `.env-staging` that holds your superset staging host and credentials.
86
+
87
+ ```
88
+ # specific settings for the superset staging host
89
+ SUPERSET_HOST="https://your-staging-superset-host.com"
90
+ SUPERSET_API_USERNAME="staging-user-here"
91
+ SUPERSET_API_PASSWORD="set-password-here"
92
+ ```
93
+
94
+ Do the same for production env.
95
+ Create a new file called `.env-production` that holds your superset production host and credentials.
96
+
97
+ ```
98
+ # specific settings for the superset production host
99
+ SUPERSET_HOST="https://your-production-superset-host.com"
100
+ SUPERSET_API_USERNAME="production-user-here"
101
+ SUPERSET_API_PASSWORD="set-password-here"
102
+ ```
103
+
104
+ The command `bin/console` will then load your env file depending on the value in ENV['SUPERSET_ENVIRONMENT'] from the primary `.env`.
105
+
106
+ When you need to switch envs, exit the console, edit the .env to your desired value, eg `production`, then open a console again.
107
+
108
+ Bonus is the Pry prompt will now also include the `SUPERSET_ENVIRONMENT` value.
109
+
110
+ ```
111
+ bin/console
112
+ ENV configuration loaded from from .env-staging
113
+ [1] (ENV:STAGING)> Superset::Dashboard::List.new(title_contains: 'video').list
114
+
115
+ Happi: GET https://your-staging-superset-host.com/api/v1/dashboard/?q=(filters:!((col:dashboard_title,opr:ct,value:'video')),page:0,page_size:100), {}
116
+ +----+------------------+-----------+------------------------------------------------------------------+
117
+ | Superset::Dashboard::List |
118
+ +----+------------------+-----------+------------------------------------------------------------------+
119
+ | Id | Dashboard title | Status | Url |
120
+ +----+------------------+-----------+------------------------------------------------------------------+
121
+ | 6 | Video Game Sales | published | https://your-staging-superset-host.com/superset/dashboard/6/ |
122
+ +----+------------------+-----------+------------------------------------------------------------------+
123
+ ```
124
+
125
+
126
+
127
+
@@ -0,0 +1,10 @@
1
+ version: '3'
2
+
3
+ volumes:
4
+ bundle-data:
5
+ driver: local
6
+
7
+ services:
8
+ app:
9
+ volumes:
10
+ - bundle-data:/usr/local/bundle
@@ -0,0 +1,8 @@
1
+ version: '3'
2
+
3
+ services:
4
+ app:
5
+ build: .
6
+ command: setup
7
+ volumes:
8
+ - .:/app
data/env.sample ADDED
@@ -0,0 +1,9 @@
1
+ SUPERSET_HOST="https://your-superset-host.com"
2
+
3
+ # general api calls
4
+ SUPERSET_API_USERNAME="USERNAME"
5
+ SUPERSET_API_PASSWORD="PASSWORD"
6
+
7
+ # embedded user guest token api calls
8
+ # SUPERSET_EMBEDDED_USERNAME="EMBEDDED-USERNAME"
9
+ # SUPERSET_EMBEDDED_PASSWORD="EMBEDDED-PASSWORD"
@@ -0,0 +1,15 @@
1
+ module Superset
2
+ module DuplicateDashboardLogger
3
+
4
+
5
+ def logger
6
+ @logger ||= begin
7
+ if defined?(Rails)
8
+ Rails.try(:logger) || Logger.new(STDOUT)
9
+ else
10
+ Logger.new(STDOUT)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,55 @@
1
+ module Superset
2
+ class Authenticator
3
+ class CredentialMissingError < StandardError; end
4
+
5
+ attr_reader :credentials
6
+
7
+ def initialize(credentials)
8
+ @credentials = credentials
9
+ end
10
+
11
+ def self.call(credentials)
12
+ self.new(credentials).access_token
13
+ end
14
+
15
+ def access_token
16
+ response_body['access_token']
17
+ end
18
+
19
+ def refresh_token
20
+ response_body['refresh_token']
21
+ end
22
+
23
+ def validate_credential_existance
24
+ raise CredentialMissingError, 'password not set' unless credentials[:password].present?
25
+ raise CredentialMissingError, 'username not set' unless credentials[:username].present?
26
+ end
27
+
28
+ def superset_host
29
+ raise CredentialMissingError, "SUPERSET_HOST not found" unless ENV['SUPERSET_HOST'].present?
30
+
31
+ ENV['SUPERSET_HOST']
32
+ end
33
+
34
+ private
35
+
36
+ def response
37
+ validate_credential_existance
38
+ @response ||= connection.post(route,
39
+ credentials.to_json,
40
+ 'Content-Type' => 'application/json')
41
+ end
42
+
43
+ def route
44
+ 'api/v1/security/login'
45
+ end
46
+
47
+ def response_body
48
+ JSON.parse(response.env['body'] || response.env['response_body'])
49
+ end
50
+
51
+ def connection
52
+ Faraday.new(superset_host)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+ # TODO: the gui delete has a confirmation step, this API call does not.
3
+ # Potentially we could add a confirm_delete parameter to the constructor that would ensure that all charts either
4
+ # 1 belong to only an expected dashboard before deleting
5
+ # 2 or do not belong to any dashboards
6
+ # ( not sure if this needed at this point )
7
+
8
+ module Superset
9
+ module Chart
10
+ class BulkDelete < Superset::Request
11
+ attr_reader :chart_ids
12
+
13
+ def initialize(chart_ids: [])
14
+ @chart_ids = chart_ids
15
+ end
16
+
17
+ def perform
18
+ raise InvalidParameterError, "chart_ids array of integers expected" unless chart_ids.is_a?(Array)
19
+ raise InvalidParameterError, "chart_ids array must contain Integer only values" unless chart_ids.all? { |item| item.is_a?(Integer) }
20
+
21
+ logger.info("Attempting to delete charts with id: #{chart_ids.join(', ')}")
22
+ response
23
+ end
24
+
25
+ def response
26
+ @response ||= client.delete(route, params)
27
+ end
28
+
29
+ private
30
+
31
+ def params
32
+ { q: "!(#{chart_ids.join(',')})" }
33
+ end
34
+
35
+ def route
36
+ "chart/"
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Superset
4
+ module Chart
5
+ class Delete < Superset::Request
6
+ attr_reader :chart_id
7
+
8
+ def initialize(chart_id: )
9
+ @chart_id = chart_id
10
+ end
11
+
12
+ def perform
13
+ raise InvalidParameterError, "chart_id integer is required" unless chart_id.present? && chart_id.is_a?(Integer)
14
+
15
+ logger.info("Attempting to delete chart with id: #{chart_id}")
16
+ response
17
+ end
18
+
19
+ def response
20
+ @response ||= client.delete(route)
21
+ end
22
+
23
+ private
24
+
25
+ def route
26
+ "chart/#{chart_id}"
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,56 @@
1
+ module Superset
2
+ module Chart
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 result
21
+ [ super ]
22
+ end
23
+
24
+ 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
29
+ end
30
+ end
31
+
32
+ def owner_ids
33
+ result.first['owners'].map{|o| o['id']}
34
+ end
35
+
36
+ def params
37
+ JSON.parse(result.first['params']) if result.first['params'].present?
38
+ end
39
+
40
+ def query_context
41
+ JSON.parse(result.first['query_context']) if result.first['query_context'].present?
42
+ end
43
+
44
+ private
45
+
46
+ def route
47
+ "chart/#{id}"
48
+ end
49
+
50
+ def list_attributes
51
+ %w(id slice_name owners)
52
+ end
53
+
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,59 @@
1
+ module Superset
2
+ module Chart
3
+ class List < Superset::Request
4
+
5
+ attr_reader :name_contains, :dashboard_id_eq, :dataset_id_eq
6
+
7
+ def initialize(page_num: 0, name_contains: '', dashboard_id_eq: '', dataset_id_eq: '')
8
+ @name_contains = name_contains
9
+ @dashboard_id_eq = dashboard_id_eq
10
+ @dataset_id_eq = dataset_id_eq
11
+ super(page_num: page_num)
12
+ end
13
+
14
+ def self.call
15
+ self.new.list
16
+ end
17
+
18
+ def list_with_dashboards
19
+ puts Terminal::Table.new(
20
+ title: title,
21
+ headings: list_dashboard_attributes.map(&:to_s).map(&:humanize),
22
+ rows: rows_with_dashboards
23
+ )
24
+ end
25
+
26
+ def rows_with_dashboards
27
+ result.map do |d|
28
+ list_dashboard_attributes.map { |la| d[la].to_s }
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def route
35
+ "chart/?q=(#{query_params})"
36
+ end
37
+
38
+ def filters
39
+ # TODO filtering across all classes needs a refactor to support multiple options in a more flexible way
40
+ filter_set = []
41
+ filter_set << "(col:slice_name,opr:ct,value:'#{name_contains}')" if name_contains.present?
42
+ filter_set << "(col:dashboards,opr:rel_m_m,value:#{dashboard_id_eq})" if dashboard_id_eq.present? # rel many to many
43
+ filter_set << "(col:datasource_id,opr:eq,value:#{dataset_id_eq})" if dataset_id_eq.present?
44
+
45
+ unless filter_set.empty?
46
+ "filters:!(" + filter_set.join(',') + "),"
47
+ end
48
+ end
49
+
50
+ def list_attributes
51
+ ['id', 'slice_name', 'datasource_id', 'datasource_name_text', 'created_by_name']
52
+ end
53
+
54
+ def list_dashboard_attributes
55
+ ['id', 'slice_name', 'dashboards']
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,90 @@
1
+ # In the context of a chart, dataset and datasource are the same thing
2
+
3
+ module Superset
4
+ module Chart
5
+ class UpdateDataset < Superset::Request
6
+
7
+ attr_reader :chart_id, :target_dataset_id, :target_dashboard_id
8
+
9
+ def initialize(chart_id: , target_dataset_id: , target_dashboard_id: nil)
10
+ @chart_id = chart_id
11
+ @target_dataset_id = target_dataset_id
12
+ @target_dashboard_id = target_dashboard_id
13
+ end
14
+
15
+ def perform
16
+ validate_proposed_changes
17
+
18
+ response
19
+
20
+ if result['datasource_id'] == target_dataset_id
21
+ "Successfully updated chart #{chart_id} to the target dataset #{target_dataset_id}"
22
+ else
23
+ "Error: Failed to update chart #{chart_id} to #{target_dataset_id}"
24
+ end
25
+
26
+ end
27
+
28
+ def response
29
+ @response ||= client.put(route, params_updated)
30
+ end
31
+
32
+ def params_updated
33
+ @params_updated ||= begin
34
+ new_params = {}
35
+ # chart updates to point to the new target dataset
36
+ new_params.merge!("datasource_id": target_dataset_id) # point to the new dataset id
37
+ new_params.merge!("datasource_type": 'table') # type of dataset ?? not sure of other options
38
+ new_params.merge!("owners": chart.owner_ids ) # expects an array of user ids
39
+
40
+ new_params.merge!("params": updated_chart_params.to_json) # updated to point to the new params
41
+ query_context = updated_chart_query_context
42
+ if query_context
43
+ new_params.merge!("query_context": query_context.to_json) # update to point to the new query context
44
+ new_params.merge!("query_context_generation": true) # new param set to true to regenerate the query context
45
+ end
46
+
47
+ new_params
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def chart
54
+ @chart ||= Superset::Chart::Get.new(chart_id).perform
55
+ end
56
+
57
+ def validate_proposed_changes
58
+ raise "Error: chart_id integer is required" unless chart_id.present? && chart_id.is_a?(Integer)
59
+ raise "Error: target_dataset_id integer is required" unless target_dataset_id.present? && target_dataset_id.is_a?(Integer)
60
+ # validate schema ???
61
+ end
62
+
63
+ def updated_chart_params
64
+ chart_params = chart.params # init with current chart params
65
+ chart_params['datasource'] = chart_params['datasource'].sub(source_dataset_id.to_s, target_dataset_id.to_s) # update to point to the new dataset
66
+ chart_params['dashboards'] = [target_dashboard_id] if target_dashboard_id.present? # update to point to the new dashboard
67
+ chart_params
68
+ end
69
+
70
+ def updated_chart_query_context
71
+ if chart.query_context.present? # not all charts have a query context
72
+ chart_query_context = chart.query_context # init with source chart query context
73
+ chart_query_context['datasource']['id'] = target_dataset_id # update to point to the new dataset
74
+ chart_query_context['form_data']['datasource'] = chart_query_context['form_data']['datasource']
75
+ .sub(source_dataset_id.to_s, target_dataset_id.to_s) # update to point to the new dataset
76
+ chart_query_context['form_data']['dashboards'] = [target_dashboard_id] if target_dashboard_id.present? # update to point to the new dashboard
77
+ chart_query_context
78
+ end
79
+ end
80
+
81
+ def source_dataset_id
82
+ chart.datasource_id
83
+ end
84
+
85
+ def route
86
+ "chart/#{chart_id}"
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,53 @@
1
+ module Superset
2
+ class Client < Happi::Client
3
+ include Credential::ApiUser
4
+
5
+ attr_reader :authenticator
6
+
7
+ def initialize
8
+ @authenticator = Superset::Authenticator.new(credentials)
9
+ super(log_level: :debug, host: superset_host)
10
+ end
11
+
12
+ def access_token
13
+ @access_token ||= authenticator.access_token
14
+ end
15
+
16
+ def superset_host
17
+ @superset_host ||= authenticator.superset_host
18
+ end
19
+
20
+ # TODO: Happi has not got a put method yet
21
+ def put(resource, params = {})
22
+ call(:put, url(resource), param_check(params))
23
+ .body.with_indifferent_access
24
+ end
25
+
26
+ # TODO: Happi is not surfacing the errors correctly overriding raise_error for now
27
+ def raise_error(response)
28
+ message =
29
+ if response.body['errors']
30
+ response.body['errors']
31
+ else
32
+ response.body
33
+ end
34
+
35
+ puts "API Error: #{message}" # display the error message for console debugging
36
+ # binding.pry # helpfull to debug the response
37
+
38
+ raise errors[response.status].new(message, response) # message is not being surfaced from Happi correctly, :(
39
+ end
40
+
41
+ private
42
+
43
+ def connection
44
+ @connection ||= Faraday.new(superset_host) do |f|
45
+ f.authorization :Bearer, access_token
46
+ f.use FaradayMiddleware::ParseJson, content_type: 'application/json'
47
+ f.request :json
48
+
49
+ f.adapter :net_http
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,25 @@
1
+ module Superset
2
+ module Credential
3
+ module ApiUser
4
+
5
+ def credentials
6
+ {
7
+ "username": api_username,
8
+ "password": api_password,
9
+ "provider": "db",
10
+ "refresh": false
11
+ }
12
+ end
13
+
14
+ private
15
+
16
+ def api_username
17
+ ENV['SUPERSET_API_USERNAME']
18
+ end
19
+
20
+ def api_password
21
+ ENV['SUPERSET_API_PASSWORD']
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ module Superset
2
+ module Credential
3
+ module EmbeddedUser
4
+
5
+ def credentials
6
+ {
7
+ "username": embedded_username,
8
+ "password": embedded_password,
9
+ "provider": "db",
10
+ "refresh": false
11
+ }
12
+ end
13
+
14
+ private
15
+
16
+ def embedded_username
17
+ ENV['SUPERSET_EMBEDDED_USERNAME']
18
+ end
19
+
20
+ def embedded_password
21
+ ENV['SUPERSET_EMBEDDED_PASSWORD']
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ # TODO: the gui delete has a confirmation step, this API call does not.
4
+ # Potentially we could add a confirm_delete parameter to the constructor that would ensure that all dashboards either
5
+ # 1 have an expected set of charts or filters before deleting
6
+ # 2 or do not have any charts or filters linked to them
7
+ # ( not sure if this needed at this point )
8
+
9
+ # NOTE: deletes the Dashboard Only. Use Dashboard::BulkDeleteCascade to delete all related objects
10
+ module Superset
11
+ module Dashboard
12
+ class BulkDelete < Superset::Request
13
+ attr_reader :dashboard_ids
14
+
15
+ def initialize(dashboard_ids: [])
16
+ @dashboard_ids = dashboard_ids
17
+ end
18
+
19
+ def perform
20
+ raise InvalidParameterError, "dashboard_ids array of integers expected" unless dashboard_ids.is_a?(Array)
21
+ raise InvalidParameterError, "dashboard_ids array must contain Integer only values" unless dashboard_ids.all? { |item| item.is_a?(Integer) }
22
+
23
+ logger.info("Attempting to delete dashboards with id: #{dashboard_ids.join(', ')}")
24
+ response
25
+ end
26
+
27
+ def response
28
+ @response ||= client.delete(route, params)
29
+ end
30
+
31
+ private
32
+
33
+ def params
34
+ { q: "!(#{dashboard_ids.join(',')})" }
35
+ end
36
+
37
+ def route
38
+ "dashboard/"
39
+ end
40
+ end
41
+ end
42
+ end