Skip to content

Commit 37f6069

Browse files
authored
Merge pull request #18 from rdytech/NEP-17684-duplicate-cross-filters
Nep 17684 duplicate cross filters
2 parents 7b55c75 + e26345d commit 37f6069

30 files changed

+843
-166
lines changed

Gemfile.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ PATH
1515
remote: .
1616
specs:
1717
superset (0.1.2)
18+
dotenv
19+
enumerate_it
1820
faraday (~> 1.0)
1921
faraday-multipart (~> 1.0)
2022
json (~> 2.6)
@@ -46,8 +48,11 @@ GEM
4648
concurrent-ruby (1.2.2)
4749
connection_pool (2.4.1)
4850
diff-lcs (1.5.0)
51+
dotenv (2.8.1)
4952
drb (2.2.0)
5053
ruby2_keywords
54+
enumerate_it (3.2.4)
55+
activesupport (>= 5.0.7.2)
5156
faraday (1.10.3)
5257
faraday-em_http (~> 1.0)
5358
faraday-em_synchrony (~> 1.0)

bin/console

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,20 @@
22
# frozen_string_literal: true
33

44
require "bundler/setup"
5+
require 'dotenv/load'
56
require "json"
67
require "faraday"
78
require 'faraday/multipart'
89
require "happi"
910
require "terminal-table"
1011
require "yaml"
12+
require "enumerate_it"
1113
require "superset"
1214

1315
Dir["./lib/**/*.rb"].each { |f| require f }
1416
# You can add fixtures and/or initialization code here to make experimenting
1517
# with your gem easier. You can also use a different console, if you like.
1618

1719
require "pry"
18-
load ".env" if File.exist?(".env")
1920

20-
Pry.start(__FILE__)
21+
Pry.start(__FILE__)

env.sample

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
ENV['SUPERSET_HOST'] = "https://your-superset-host.com"
1+
SUPERSET_HOST="https://your-superset-host.com"
22

33
# general api calls
4-
ENV['SUPERSET_API_USERNAME'] = "USERNAME"
5-
ENV['SUPERSET_API_PASSWORD'] = "PASSWORD"
4+
SUPERSET_API_USERNAME="USERNAME"
5+
SUPERSET_API_PASSWORD="PASSWORD"
66

77
# embedded user guest token api calls
8-
# ENV['SUPERSET_EMBEDDED_USERNAME'] = "EMBEDDED-USERNAME"
9-
# ENV['SUPERSET_EMBEDDED_PASSWORD'] = "EMBEDDED-PASSWORD"
8+
# SUPERSET_EMBEDDED_USERNAME="EMBEDDED-USERNAME"
9+
# SUPERSET_EMBEDDED_PASSWORD="EMBEDDED-PASSWORD"

lib/superset/chart/list.rb

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ module Superset
22
module Chart
33
class List < Superset::Request
44

5-
attr_reader :name_contains, :dashboard_id_eq
5+
attr_reader :name_contains, :dashboard_id_eq, :dataset_id_eq
66

7-
def initialize(page_num: 0, name_contains: '', dashboard_id_eq: '')
7+
def initialize(page_num: 0, name_contains: '', dashboard_id_eq: '', dataset_id_eq: '')
88
@name_contains = name_contains
99
@dashboard_id_eq = dashboard_id_eq
10+
@dataset_id_eq = dataset_id_eq
1011
super(page_num: page_num)
1112
end
1213

@@ -38,7 +39,8 @@ def filters
3839
# TODO filtering across all classes needs a refactor to support multiple options in a more flexible way
3940
filter_set = []
4041
filter_set << "(col:slice_name,opr:ct,value:'#{name_contains}')" if name_contains.present?
41-
filter_set << "(col:datasource_id,opr:eq,value:#{dashboard_id_eq})" if dashboard_id_eq.present?
42+
filter_set << "(col:dashboards,opr:rel_m_m,value:#{dashboard_id_eq})" if dashboard_id_eq.present?
43+
filter_set << "(col:datasource_id,opr:eq,value:#{dataset_id_eq})" if dataset_id_eq.present?
4244

4345
unless filter_set.empty?
4446
"filters:!(" + filter_set.join(',') + "),"

lib/superset/chart/update_dataset.rb

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ module Superset
44
module Chart
55
class UpdateDataset < Superset::Request
66

7-
attr_reader :chart_id, :target_dataset_id
7+
attr_reader :chart_id, :target_dataset_id, :target_dashboard_id
88

9-
def initialize(chart_id: , target_dataset_id: )
9+
def initialize(chart_id: , target_dataset_id: , target_dashboard_id: nil)
1010
@chart_id = chart_id
1111
@target_dataset_id = target_dataset_id
12+
@target_dashboard_id = target_dashboard_id
1213
end
1314

1415
def perform
@@ -60,8 +61,9 @@ def validate_proposed_changes
6061
end
6162

6263
def updated_chart_params
63-
chart_params = chart.params # init with source chart params
64+
chart_params = chart.params # init with current chart params
6465
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
6567
chart_params
6668
end
6769

@@ -70,7 +72,8 @@ def updated_chart_query_context
7072
chart_query_context = chart.query_context # init with source chart query context
7173
chart_query_context['datasource']['id'] = target_dataset_id # update to point to the new dataset
7274
chart_query_context['form_data']['datasource'] = chart_query_context['form_data']['datasource']
73-
.sub(source_dataset_id.to_s, target_dataset_id.to_s) # update to point to the new dataset
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
7477
chart_query_context
7578
end
7679
end

lib/superset/client.rb

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ class Client < Happi::Client
33
include Credential::ApiUser
44

55
attr_reader :authenticator
6-
attr_accessor :connection
76

87
def initialize
98
@authenticator = Superset::Authenticator.new(credentials)
@@ -51,4 +50,4 @@ def connection
5150
end
5251
end
5352
end
54-
end
53+
end

lib/superset/dashboard/charts/list.rb

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,6 @@ def chart_ids
1616
result.map { |c| c[:id] }
1717
end
1818

19-
private
20-
21-
def route
22-
"dashboard/#{id}/charts"
23-
end
24-
25-
def list_attributes
26-
['id', 'slice_name', 'dashsource', 'dashboards'].map(&:to_sym)
27-
end
28-
2919
def rows
3020
result.map do |c|
3121
[
@@ -37,9 +27,19 @@ def rows
3727
end
3828
end
3929

40-
# when displaying a list of datasets, show dashboard title as well
30+
private
31+
32+
def route
33+
"dashboard/#{id}/charts"
34+
end
35+
36+
def list_attributes
37+
['id', 'slice_name', 'datasource', 'dashboards'].map(&:to_sym)
38+
end
39+
40+
# when displaying a list of datasets, show dashboard id and title as well
4141
def title
42-
@title ||= Superset::Dashboard::Get.new(id).title
42+
@title ||= [id, Superset::Dashboard::Get.new(id).title].join(' ')
4343
end
4444
end
4545
end

lib/superset/dashboard/compare.rb

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

lib/superset/dashboard/copy.rb

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,29 @@ module Superset
66
module Dashboard
77
class Copy < Superset::Request
88

9-
attr_reader :source_dashboard_id, :duplicate_slices
9+
attr_reader :source_dashboard_id, :duplicate_slices, :clear_shared_label_colors
1010

11-
def initialize(source_dashboard_id: , duplicate_slices: false)
11+
def initialize(source_dashboard_id: , duplicate_slices: false, clear_shared_label_colors: false)
1212
@source_dashboard_id = source_dashboard_id
1313
@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
1415
end
1516

1617
def perform
1718
raise "Error: source_dashboard_id integer is required" unless source_dashboard_id.present? && source_dashboard_id.is_a?(Integer)
1819
raise "Error: duplicate_slices must be a boolean" unless duplicate_slices_is_boolean?
1920

21+
adjust_json_metadata
2022
response
2123
Superset::Dashboard::Get.new(id).perform # return the full new dashboard object
2224
end
2325

2426
def params
2527
{
2628
"css" => "{}",
27-
"dashboard_title" => "#{source_dashboard.title} (COPY)",
29+
"dashboard_title" => "#{source_dashboard.title}",
2830
"duplicate_slices" => duplicate_slices,
29-
"json_metadata" => source_dashboard_json_metadata_with_positions.to_json,
31+
"json_metadata" => new_dashboard_json_metadata.to_json,
3032
}
3133
end
3234

@@ -44,12 +46,14 @@ def route
4446
"dashboard/#{source_dashboard_id}/copy/"
4547
end
4648

47-
def source_dashboard_json_metadata_with_positions
48-
# when copying a DB via the API, chart positions need to be nested under json_metadata
49-
# according to the GUI copy function (as per dev tools investigation in browser)
50-
source_dashboard.json_metadata.merge(
51-
"positions" => source_dashboard.positions
52-
)
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
5357
end
5458

5559
def source_dashboard
@@ -62,6 +66,10 @@ def source_dashboard
6266
end
6367
end
6468

69+
def new_dashboard_json_metadata
70+
@new_dashboard_json_metadata ||= source_dashboard.json_metadata
71+
end
72+
6573
def duplicate_slices_is_boolean?
6674
[true, false].include?(duplicate_slices)
6775
end

lib/superset/dashboard/datasets/list.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def schemas
3535

3636
def datasets_details
3737
result.map do |details|
38-
details.slice('id', 'datasource_name', 'schema').merge('database' => details['database'].slice('id', 'name', 'backend'))
38+
details.slice('id', 'datasource_name', 'schema', 'sql').merge('database' => details['database'].slice('id', 'name', 'backend')).with_indifferent_access
3939
end
4040
end
4141

@@ -64,7 +64,7 @@ def rows
6464

6565
# when displaying a list of datasets, show dashboard title as well
6666
def title
67-
@title ||= Superset::Dashboard::Get.new(id).title
67+
@title ||= [id, Superset::Dashboard::Get.new(id).title].join(' ')
6868
end
6969
end
7070
end

0 commit comments

Comments
 (0)