power-bi 2.3.0 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c120eeef2ac35b02afcfff32690f003efad7fbfd59c7f2bd714a6d0659b1ed44
4
- data.tar.gz: ee077e4fa8f6faae52ad5c1107fd96f9cb8bae5e7c8f0ef47a83009e7c80ce0d
3
+ metadata.gz: 98d26348ce002789d1f065e91735ae2d441a64bb32c8c935993961036f2d2379
4
+ data.tar.gz: e3527606c13a8f62a4918b98e0dfc5ed441f691b836b4f670251e80442fcf104
5
5
  SHA512:
6
- metadata.gz: c9c742362808fb3fa08ad663ebbdaf34d916f932fa5bed8f66d657caf769c230a7694d82cd05555a642d41098ccb9fcf6eed9b6f89233ca344ff3f1c3d02b6fb
7
- data.tar.gz: dc28a5e89d26365b7b48d5f6538bd1e8c12a9fe45c81b4f216ae63074ced3b728f31395c98751ee571a9e7234315693af5f6a7cffe4d86c398f6ec6683cfeafe
6
+ metadata.gz: 5e1674d67c5cf55473e0325601b65b532dc374cd9e5a08a2345a282c4b5dd0a984392d1c0196857a649a00ceaf45362d21980e85c9050868b5538b4377f20dd6
7
+ data.tar.gz: 1c7517a30c1d32ddcc7e6d6883ef6a5b91fcac12500c76b89bdbebb473c405dde6b58f45a2b60dc3c60b7dfac440973ea81daa6a49f308c9d22b23670484293a
data/README.md CHANGED
@@ -62,7 +62,9 @@ Once authenticated, you can set profiles like this:
62
62
  pbi.profile = profile
63
63
  ```
64
64
 
65
- Every action executed after setting the profile, will be executed _through the eyes of the profile_. This way, you can create an isolated multi-tenant setup. Using profiles, simplifies the internal organization of Power BI and allows faster interaction with Power BI. This also lifts the 1000-workspaces limit that is imposed on Master Userss and Service Principals
65
+ Every action executed after setting the profile, will be executed _through the eyes of the profile_. This way, you can create an isolated multi-tenant setup. Using profiles, simplifies the internal organization of Power BI and allows faster interaction with Power BI. This also lifts the 1000-workspaces limit that is imposed on Master Users and Service Principals
66
+
67
+ Note: when working with Service principal profiles (SPP), you need to add the SPP to the gateway datasource before binding the gateway datasource to the dataset.
66
68
 
67
69
  # Supported endpoints
68
70
 
@@ -122,6 +124,11 @@ Note 2: to limit the number of API calls, it is best to directly use the _getter
122
124
  * Create a new gateway datasource: `gateway.gateway_datasource.create(name, credentials, db_server, db_name)`
123
125
  * Delete a new gateway datasource: `gateway_datasource.delete`
124
126
 
127
+ ## Gateway datasource users
128
+
129
+ * List datasource users in a gateway datasource: `gateway_datasource.gateway_datasource_users`
130
+ * Add a Service principal profile to a gateway datasource: `gateway_datasource.add_service_principal_profile_user(profile_id, principal_object_id)`
131
+
125
132
  ## Capacities
126
133
 
127
134
  Note: Capacities are Azure creatures, you can't create them in Power BI.
@@ -0,0 +1,80 @@
1
+ module PowerBI
2
+ class Admin
3
+
4
+ def initialize(tenant)
5
+ @tenant = tenant
6
+ end
7
+
8
+ def get_user_artifact_access(user_id, artifact_types: nil)
9
+ if artifact_types
10
+ params = {artifactTypes: artifact_types.join(',')}
11
+ else
12
+ params = {}
13
+ end
14
+
15
+ url = "/admin/users/#{user_id}/artifactAccess"
16
+
17
+ resp = @tenant.get(url, params)
18
+ data = resp[:ArtifactAccessEntities]
19
+
20
+ continuation_token = resp[:continuationToken]
21
+
22
+ while continuation_token
23
+ params = {continuationToken: "'#{continuation_token}'"}
24
+ resp = @tenant.get(url, params)
25
+ data += resp[:ArtifactAccessEntities]
26
+ continuation_token = resp[:continuationToken] ? URI::decode_uri_component(resp[:continuationToken]) : nil
27
+ end
28
+
29
+ data
30
+ end
31
+
32
+ def get_workspaces(filter: nil, expand: nil)
33
+ params = {}
34
+ params[:$filter] = filter if filter
35
+ params[:$expand] = expand if expand
36
+
37
+ url = '/admin/groups'
38
+
39
+ nr_records = 5000
40
+ count = 0
41
+
42
+ data = []
43
+
44
+ loop do
45
+ params[:$top] = nr_records
46
+ params[:$skip] = count
47
+ resp = @tenant.get(url, params)
48
+ data += resp[:value]
49
+ batch_count = resp[:value].size
50
+ count += batch_count
51
+ break if batch_count < nr_records
52
+ end
53
+
54
+ data
55
+ end
56
+
57
+ def force_delete_workspace_by_workspace_name(user_email, workspace_name)
58
+ workspace = get_workspaces(filter: "name eq '#{workspace_name}' and state eq 'Active'").first
59
+ add_user(user_email, workspace[:id], access_right: "Admin")
60
+ @tenant.workspace(workspace[:id]).delete
61
+ end
62
+
63
+ def force_delete_workspace_by_workspace_id(user_email, workspace_id)
64
+ add_user(user_email, workspace_id, access_right: "Admin")
65
+ @tenant.workspace(workspace_id).delete
66
+ end
67
+
68
+ private
69
+
70
+ def add_user(user_email, workspace_id, access_right: "Admin")
71
+ @tenant.post("/admin/groups/#{workspace_id}/users") do |req|
72
+ req.body = {
73
+ emailAddress: user_email,
74
+ groupUserAccessRight: access_right,
75
+ }.to_json
76
+ end
77
+ end
78
+
79
+ end
80
+ end
@@ -1,6 +1,6 @@
1
1
  module PowerBI
2
2
  class Dataset < Object
3
- attr_reader :workspace, :datasources, :parameters, :refresh_history
3
+ attr_reader :workspace, :datasources, :parameters
4
4
 
5
5
  def initialize(tenant, parent, id = nil)
6
6
  super(tenant, id)
@@ -38,6 +38,11 @@ module PowerBI
38
38
  true
39
39
  end
40
40
 
41
+ def refresh_history(entries_to_load = 1)
42
+ @refresh_history.entries_to_load = entries_to_load
43
+ @refresh_history
44
+ end
45
+
41
46
  def last_refresh
42
47
  @refresh_history.first
43
48
  end
@@ -8,7 +8,7 @@ module PowerBI
8
8
  end
9
9
 
10
10
  def get_data(id)
11
- @tenant.get("/gateways/#{id}")
11
+ @tenant.get("/gateways/#{id}", use_profile: false)
12
12
  end
13
13
 
14
14
  def data_to_attributes(data)
@@ -32,7 +32,7 @@ module PowerBI
32
32
  end
33
33
 
34
34
  def get_data
35
- @tenant.get("/gateways")[:value]
35
+ @tenant.get("/gateways", use_profile: false)[:value]
36
36
  end
37
37
  end
38
38
  end
@@ -1,14 +1,15 @@
1
1
  module PowerBI
2
2
  class GatewayDatasource < Object
3
- attr_reader :gateway
3
+ attr_reader :gateway, :gateway_datasource_users
4
4
 
5
5
  def initialize(tenant, parent, id = nil)
6
6
  super(tenant, id)
7
7
  @gateway = parent
8
+ @gateway_datasource_users = GatewayDatasourceUserArray.new(@tenant, self)
8
9
  end
9
10
 
10
11
  def get_data(id)
11
- @tenant.get("/gateways/#{@gateway.id}/datasources/#{id}")
12
+ @tenant.get("/gateways/#{@gateway.id}/datasources/#{id}", use_profile: false)
12
13
  end
13
14
 
14
15
  def data_to_attributes(data)
@@ -24,7 +25,7 @@ module PowerBI
24
25
  end
25
26
 
26
27
  def update_credentials(encrypted_credentials)
27
- @tenant.patch("/gateways/#{gateway.id}/datasources/#{id}") do |req|
28
+ @tenant.patch("/gateways/#{gateway.id}/datasources/#{id}", use_profile: false) do |req|
28
29
  req.body = {
29
30
  credentialDetails: {
30
31
  credentialType: "Basic",
@@ -41,7 +42,7 @@ module PowerBI
41
42
  end
42
43
 
43
44
  def delete
44
- @tenant.delete("/gateways/#{gateway.id}/datasources/#{id}")
45
+ @tenant.delete("/gateways/#{gateway.id}/datasources/#{id}", use_profile: false)
45
46
  @gateway.gateway_datasources.reload
46
47
  true
47
48
  end
@@ -61,7 +62,7 @@ module PowerBI
61
62
 
62
63
  # only MySQL type is currently supported
63
64
  def create(name, encrypted_credentials, db_server, db_name)
64
- data = @tenant.post("/gateways/#{@gateway.id}/datasources",) do |req|
65
+ data = @tenant.post("/gateways/#{@gateway.id}/datasources", use_profile: false) do |req|
65
66
  req.body = {
66
67
  connectionDetails: {server: db_server, database: db_name}.to_json,
67
68
  credentialDetails: {
@@ -82,7 +83,7 @@ module PowerBI
82
83
  end
83
84
 
84
85
  def get_data
85
- @tenant.get("/gateways/#{@gateway.id}/datasources")[:value]
86
+ @tenant.get("/gateways/#{@gateway.id}/datasources", use_profile: false)[:value]
86
87
  end
87
88
  end
88
89
  end
@@ -0,0 +1,51 @@
1
+ module PowerBI
2
+ class GatewayDatasourceUser < Object
3
+ attr_reader :gateway_datasource
4
+
5
+ def initialize(tenant, parent, id = nil)
6
+ super(tenant, id)
7
+ @gateway_datasource = parent
8
+ end
9
+
10
+ def data_to_attributes(data)
11
+ {
12
+ datasource_access_right: data[:datasourceAccessRight],
13
+ display_name: data[:displayName],
14
+ email_address: data[:emailAddress],
15
+ identifier: data[:identifier],
16
+ principal_type: data[:principalType],
17
+ profile: data[:profile],
18
+ }
19
+ end
20
+
21
+ end
22
+
23
+ class GatewayDatasourceUserArray < Array
24
+
25
+ def initialize(tenant, gateway_datasource)
26
+ super(tenant, gateway_datasource)
27
+ @gateway_datasource = gateway_datasource
28
+ end
29
+
30
+ def self.get_class
31
+ GatewayDatasourceUser
32
+ end
33
+
34
+ # service principal object ID: https://learn.microsoft.com/en-us/power-bi/developer/embedded/embedded-troubleshoot#what-is-the-difference-between-application-object-id-and-principal-object-id
35
+ def add_service_principal_profile_user(profile_id, principal_object_id, datasource_access_right: "Read")
36
+ @tenant.post("/gateways/#{@gateway_datasource.gateway.id}/datasources/#{@gateway_datasource.id}/users", use_profile: false) do |req|
37
+ req.body = {
38
+ datasourceAccessRight: datasource_access_right,
39
+ identifier: principal_object_id,
40
+ principalType: "App",
41
+ profile: {id: profile_id},
42
+ }.to_json
43
+ end
44
+ self.reload
45
+ end
46
+
47
+ def get_data
48
+ @tenant.get("/gateways/#{@gateway_datasource.gateway.id}/datasources/#{@gateway_datasource.id}/users", use_profile: false)[:value]
49
+ end
50
+ end
51
+ end
@@ -6,7 +6,7 @@ module PowerBI
6
6
  end
7
7
 
8
8
  def get_data(id)
9
- @tenant.get("/profiles/#{id}")
9
+ @tenant.get("/profiles/#{id}", use_profile: false)
10
10
  end
11
11
 
12
12
  def data_to_attributes(data)
@@ -17,7 +17,7 @@ module PowerBI
17
17
  end
18
18
 
19
19
  def delete
20
- @tenant.delete("/profiles/#{@id}")
20
+ @tenant.delete("/profiles/#{@id}", use_profile: false)
21
21
  @tenant.profiles.reload
22
22
  true
23
23
  end
@@ -30,7 +30,7 @@ module PowerBI
30
30
  end
31
31
 
32
32
  def create(name)
33
- data = @tenant.post("/profiles") do |req|
33
+ data = @tenant.post("/profiles", use_profile: false) do |req|
34
34
  req.body = {displayName: name}.to_json
35
35
  end
36
36
  self.reload
@@ -38,7 +38,7 @@ module PowerBI
38
38
  end
39
39
 
40
40
  def get_data
41
- @tenant.get("/profiles")[:value]
41
+ @tenant.get("/profiles", use_profile: false)[:value]
42
42
  end
43
43
  end
44
44
  end
@@ -22,18 +22,27 @@ module PowerBI
22
22
  end
23
23
 
24
24
  class RefreshArray < Array
25
+ attr_reader :entries_to_load
25
26
 
26
27
  def initialize(tenant, dataset)
27
28
  super(tenant, dataset)
28
29
  @dataset = dataset
30
+ @entries_to_load = 1
29
31
  end
30
32
 
31
33
  def self.get_class
32
34
  Refresh
33
35
  end
34
36
 
37
+ def entries_to_load=(entries_to_load)
38
+ if entries_to_load > @entries_to_load
39
+ @entries_to_load = entries_to_load
40
+ reload
41
+ end
42
+ end
43
+
35
44
  def get_data
36
- @tenant.get("/groups/#{@dataset.workspace.id}/datasets/#{@dataset.id}/refreshes", {'$top': '1'})[:value]
45
+ @tenant.get("/groups/#{@dataset.workspace.id}/datasets/#{@dataset.id}/refreshes", {'$top': @entries_to_load.to_s})[:value]
37
46
  end
38
47
  end
39
48
  end
@@ -1,6 +1,6 @@
1
1
  module PowerBI
2
2
  class Tenant
3
- attr_reader :workspaces, :gateways, :capacities, :profiles, :profile
3
+ attr_reader :workspaces, :gateways, :capacities, :profiles, :profile_id, :admin
4
4
 
5
5
  def initialize(token_generator, retries: 5, logger: nil)
6
6
  @token_generator = token_generator
@@ -9,7 +9,8 @@ module PowerBI
9
9
  @capacities = CapacityArray.new(self)
10
10
  @profiles = ProfileArray.new(self)
11
11
  @logger = logger
12
- @profile = nil
12
+ @profile_id = nil
13
+ @admin = Admin.new(self)
13
14
 
14
15
  ## WHY RETRIES? ##
15
16
  # It is noticed that once in a while (~0.1% API calls), the Power BI server returns a 500 (internal server error) without apparent reason, just retrying works :-)
@@ -44,12 +45,16 @@ module PowerBI
44
45
  Capacity.new(self, nil, id)
45
46
  end
46
47
 
48
+ def profile(id)
49
+ Profile.new(self, nil, id)
50
+ end
51
+
47
52
  def profile=(profile)
48
- @profile = profile
53
+ @profile_id = profile.is_a?(String) ? profile : profile&.id
49
54
  @workspaces.reload # we need to reload the workspaces because we look through the eyes of the profile
50
55
  end
51
56
 
52
- def get(url, params = {})
57
+ def get(url, params = {}, use_profile: true)
53
58
  t0 = Time.now
54
59
  conn = Faraday.new do |f|
55
60
  f.request :retry, @retry_options
@@ -58,8 +63,8 @@ module PowerBI
58
63
  req.params = params
59
64
  req.headers['Accept'] = 'application/json'
60
65
  req.headers['authorization'] = "Bearer #{token}"
61
- if @profile
62
- req.headers['X-PowerBI-Profile-Id'] = @profile.id
66
+ if use_profile
67
+ add_spp_header(req)
63
68
  end
64
69
  yield req if block_given?
65
70
  end
@@ -75,7 +80,7 @@ module PowerBI
75
80
  end
76
81
  end
77
82
 
78
- def get_raw(url, params = {})
83
+ def get_raw(url, params = {}, use_profile: true)
79
84
  t0 = Time.now
80
85
  conn = Faraday.new do |f|
81
86
  f.request :retry, @retry_options
@@ -83,8 +88,8 @@ module PowerBI
83
88
  response = conn.get(PowerBI::BASE_URL + url) do |req|
84
89
  req.params = params
85
90
  req.headers['authorization'] = "Bearer #{token}"
86
- if @profile
87
- req.headers['X-PowerBI-Profile-Id'] = @profile.id
91
+ if use_profile
92
+ add_spp_header(req)
88
93
  end
89
94
  yield req if block_given?
90
95
  end
@@ -95,7 +100,7 @@ module PowerBI
95
100
  response.body
96
101
  end
97
102
 
98
- def post(url, params = {})
103
+ def post(url, params = {}, use_profile: true)
99
104
  t0 = Time.now
100
105
  conn = Faraday.new do |f|
101
106
  f.request :retry, @retry_options
@@ -105,8 +110,8 @@ module PowerBI
105
110
  req.headers['Accept'] = 'application/json'
106
111
  req.headers['Content-Type'] = 'application/json'
107
112
  req.headers['authorization'] = "Bearer #{token}"
108
- if @profile
109
- req.headers['X-PowerBI-Profile-Id'] = @profile.id
113
+ if use_profile
114
+ add_spp_header(req)
110
115
  end
111
116
  yield req if block_given?
112
117
  end
@@ -119,7 +124,7 @@ module PowerBI
119
124
  end
120
125
  end
121
126
 
122
- def patch(url, params = {})
127
+ def patch(url, params = {}, use_profile: true)
123
128
  t0 = Time.now
124
129
  conn = Faraday.new do |f|
125
130
  f.request :retry, @retry_options
@@ -129,8 +134,8 @@ module PowerBI
129
134
  req.headers['Accept'] = 'application/json'
130
135
  req.headers['Content-Type'] = 'application/json'
131
136
  req.headers['authorization'] = "Bearer #{token}"
132
- if @profile
133
- req.headers['X-PowerBI-Profile-Id'] = @profile.id
137
+ if use_profile
138
+ add_spp_header(req)
134
139
  end
135
140
  yield req if block_given?
136
141
  end
@@ -143,7 +148,7 @@ module PowerBI
143
148
  end
144
149
  end
145
150
 
146
- def delete(url, params = {})
151
+ def delete(url, params = {}, use_profile: true)
147
152
  t0 = Time.now
148
153
  conn = Faraday.new do |f|
149
154
  f.request :retry, @retry_options
@@ -152,8 +157,8 @@ module PowerBI
152
157
  req.params = params
153
158
  req.headers['Accept'] = 'application/json'
154
159
  req.headers['authorization'] = "Bearer #{token}"
155
- if @profile
156
- req.headers['X-PowerBI-Profile-Id'] = @profile.id
160
+ if use_profile
161
+ add_spp_header(req)
157
162
  end
158
163
  yield req if block_given?
159
164
  end
@@ -169,7 +174,7 @@ module PowerBI
169
174
  end
170
175
  end
171
176
 
172
- def post_file(url, file, params = {})
177
+ def post_file(url, file, params = {}, use_profile: true)
173
178
  t0 = Time.now
174
179
  conn = Faraday.new do |f|
175
180
  f.request :multipart
@@ -180,8 +185,8 @@ module PowerBI
180
185
  req.headers['Accept'] = 'application/json'
181
186
  req.headers['Content-Type'] = 'multipart/form-data'
182
187
  req.headers['authorization'] = "Bearer #{token}"
183
- if @profile
184
- req.headers['X-PowerBI-Profile-Id'] = @profile.id
188
+ if use_profile
189
+ add_spp_header(req)
185
190
  end
186
191
  req.body = {value: Faraday::UploadIO.new(file, 'application/octet-stream')}
187
192
  req.options.timeout = 120 # default is 60 seconds Net::ReadTimeout
@@ -195,6 +200,12 @@ module PowerBI
195
200
 
196
201
  private
197
202
 
203
+ def add_spp_header(req)
204
+ if @profile_id
205
+ req.headers['X-PowerBI-Profile-Id'] = @profile_id
206
+ end
207
+ end
208
+
198
209
  def token
199
210
  @token_generator.call
200
211
  end
data/lib/power-bi.rb CHANGED
@@ -22,7 +22,9 @@ require_relative "power-bi/parameter"
22
22
  require_relative "power-bi/refresh"
23
23
  require_relative "power-bi/gateway"
24
24
  require_relative "power-bi/gateway_datasource"
25
+ require_relative "power-bi/gateway_datasource_user"
25
26
  require_relative "power-bi/page"
26
27
  require_relative "power-bi/user"
27
28
  require_relative "power-bi/capacity"
28
- require_relative "power-bi/profile"
29
+ require_relative "power-bi/profile"
30
+ require_relative "power-bi/admin"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: power-bi
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.0
4
+ version: 2.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lode Cools
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-08-04 00:00:00.000000000 Z
11
+ date: 2024-05-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -89,12 +89,14 @@ files:
89
89
  - LICENSE
90
90
  - README.md
91
91
  - lib/power-bi.rb
92
+ - lib/power-bi/admin.rb
92
93
  - lib/power-bi/array.rb
93
94
  - lib/power-bi/capacity.rb
94
95
  - lib/power-bi/dataset.rb
95
96
  - lib/power-bi/datasource.rb
96
97
  - lib/power-bi/gateway.rb
97
98
  - lib/power-bi/gateway_datasource.rb
99
+ - lib/power-bi/gateway_datasource_user.rb
98
100
  - lib/power-bi/object.rb
99
101
  - lib/power-bi/page.rb
100
102
  - lib/power-bi/parameter.rb