power-bi 2.2.0 → 2.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +68 -0
- data/lib/power-bi/gateway.rb +2 -2
- data/lib/power-bi/gateway_datasource.rb +7 -6
- data/lib/power-bi/gateway_datasource_user.rb +51 -0
- data/lib/power-bi/profile.rb +44 -0
- data/lib/power-bi/tenant.rb +42 -7
- data/lib/power-bi.rb +3 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 90d415a89ed5860c82824f7e152e4c58d42054bc293f5534f6669774682a1ffe
|
4
|
+
data.tar.gz: 536d4c7cea48c859c7b780db751da15375b060a45c59946a2335887d8e948494
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d31db71feb4b03d7810d5805b3d48016e89ead1782bfae66269aa2a0c55340e6cf21031525ffa6d5b917cdc36041670ef52c1ccff499ebdfc7d0051d8005b6a8
|
7
|
+
data.tar.gz: 1ea49ae1a15e45b16c21b9da86c8ac20abb9e96a6eb63e6a0ed9a7dfcbca25dfff8431896703f2d55736a148668cf4b85ac591eb20c68bf1b2d9f6baee455e3a
|
data/README.md
CHANGED
@@ -10,6 +10,62 @@ The Power BI API does not handle the authorization part. It requires the user to
|
|
10
10
|
pbi = PowerBI::Tenant.new(->{token = get_token(client, token) ; token.token})
|
11
11
|
```
|
12
12
|
|
13
|
+
## Authentication & authorization towards the Power BI API
|
14
|
+
|
15
|
+
Currently (april 2024), there are basically 3 ways to authenticate to the Power BI API.
|
16
|
+
|
17
|
+
### 1 - Master User
|
18
|
+
|
19
|
+
A master user is a classic Microsoft 365 user that you assign a Power BI Pro license. In order to allow the user to execute actions on the Power BI API you need to create an app registration in Azure AD. In the associated Enterprise application (gets created when you create the app registration), you need to add the permissions to use the Power BI service.
|
20
|
+
|
21
|
+
The resulting authentication looks like this:
|
22
|
+
|
23
|
+
```
|
24
|
+
TENANT = "53c835d6-6841-4d58-948a-55117409e1d8"
|
25
|
+
CLIENT_ID = '6fc64675-bee3-49a7-70d8-b3301a51a88d'
|
26
|
+
CLIENT_SECRET = 'sI/@ncYe=eVt7.XfZ7tsPU1aPbxm0V_H'
|
27
|
+
USERNAME = 'company_0001@example.com'
|
28
|
+
PASSWORD = 'mLXv5A1jrIb8dHopur7y'
|
29
|
+
|
30
|
+
client = OAuth2::Client.new(CLIENT_ID, CLIENT_SECRET, site: 'https://login.microsoftonline.com', token_url: "#{TENANT}/oauth2/token")
|
31
|
+
|
32
|
+
token = client.password.get_token(USERNAME, PASSWORD, scope: 'openid', resource: 'https://analysis.windows.net/powerbi/api')
|
33
|
+
```
|
34
|
+
|
35
|
+
Note that in this case the legacy (and slightly unsafe) Resource Owner Password Credentials (ROPC) OAuth flow is used.
|
36
|
+
|
37
|
+
### 2 - Service principal
|
38
|
+
|
39
|
+
A service principal is a fancy word for a machine user with a secret (eg. id + key) in Azure AD. You create them by creating an app registration and adding a secret on it. You need to allow service principals in your Power BI admin settings. But once you allow that, the setup is very easy. No need to configure anything special in AD.
|
40
|
+
|
41
|
+
The resulting authentication looks like this:
|
42
|
+
|
43
|
+
```
|
44
|
+
TENANT = "53c835d6-6841-4d58-948a-55117409e1d8"
|
45
|
+
CLIENT_ID = '6fc64675-bee3-49a7-70d8-b3301a51a88d'
|
46
|
+
CLIENT_SECRET = 'sI/@ncYe=eVt7.XfZ7tsPU1aPbxm0V_H'
|
47
|
+
|
48
|
+
client = OAuth2::Client.new(CLIENT_ID, CLIENT_SECRET, site: 'https://login.microsoftonline.com', token_url: "#{TENANT}/oauth2/token")
|
49
|
+
|
50
|
+
token = client.client_credentials.get_token(resource: 'https://analysis.windows.net/powerbi/api')
|
51
|
+
```
|
52
|
+
|
53
|
+
Note that in this case the Client Credentials OAuth flow is used.
|
54
|
+
|
55
|
+
### 3 - Service principal profiles
|
56
|
+
|
57
|
+
Service principal profiles is a Power-BI-only concept. Towards AD, it looks exactly the same as generic service principal. Hence the authenication looks exactly as in way 2.
|
58
|
+
|
59
|
+
Once authenticated, you can set profiles like this:
|
60
|
+
|
61
|
+
```
|
62
|
+
pbi.profile = profile
|
63
|
+
```
|
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 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.
|
68
|
+
|
13
69
|
# Supported endpoints
|
14
70
|
|
15
71
|
Note: where possible we use _lazy evaluation_: we only call the REST API endpoint when really needed. For examples `pbi.workspaces` won't trigger a call, while `pbi.workspaces.count` will trigger a call. And `pbi.workspace('123')` won't trigger a call, while `pbi.workspace('123').name` will trigger a call.
|
@@ -68,6 +124,11 @@ Note 2: to limit the number of API calls, it is best to directly use the _getter
|
|
68
124
|
* Create a new gateway datasource: `gateway.gateway_datasource.create(name, credentials, db_server, db_name)`
|
69
125
|
* Delete a new gateway datasource: `gateway_datasource.delete`
|
70
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
|
+
|
71
132
|
## Capacities
|
72
133
|
|
73
134
|
Note: Capacities are Azure creatures, you can't create them in Power BI.
|
@@ -75,6 +136,13 @@ Note: Capacities are Azure creatures, you can't create them in Power BI.
|
|
75
136
|
* List capacities: `pbi.capacities`
|
76
137
|
* Get a capacity: `pbi.capacity(id)`
|
77
138
|
|
139
|
+
## Profiles
|
140
|
+
|
141
|
+
* List profiles: `pbi.profiles`
|
142
|
+
* Get a profile: `pbi.profile(id)`
|
143
|
+
* Create a profile: `pbi.profiles.create`
|
144
|
+
* Delete a profile: `profile.delete`
|
145
|
+
|
78
146
|
# Note about gateway credentials
|
79
147
|
|
80
148
|
Power BI uses an obscure mechanism to encrypt credential exchange between the service and the gateway. The encryption must be done outside this module on a Windows machine based on th public key of the gateway. This is an example C# script:
|
data/lib/power-bi/gateway.rb
CHANGED
@@ -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
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module PowerBI
|
2
|
+
class Profile < Object
|
3
|
+
|
4
|
+
def initialize(tenant, parent, id = nil)
|
5
|
+
super(tenant, id)
|
6
|
+
end
|
7
|
+
|
8
|
+
def get_data(id)
|
9
|
+
@tenant.get("/profiles/#{id}", use_profile: false)
|
10
|
+
end
|
11
|
+
|
12
|
+
def data_to_attributes(data)
|
13
|
+
{
|
14
|
+
id: data[:id],
|
15
|
+
display_name: data[:displayName],
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def delete
|
20
|
+
@tenant.delete("/profiles/#{@id}", use_profile: false)
|
21
|
+
@tenant.profiles.reload
|
22
|
+
true
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
class ProfileArray < Array
|
28
|
+
def self.get_class
|
29
|
+
Profile
|
30
|
+
end
|
31
|
+
|
32
|
+
def create(name)
|
33
|
+
data = @tenant.post("/profiles", use_profile: false) do |req|
|
34
|
+
req.body = {displayName: name}.to_json
|
35
|
+
end
|
36
|
+
self.reload
|
37
|
+
Profile.instantiate_from_data(@tenant, nil, data)
|
38
|
+
end
|
39
|
+
|
40
|
+
def get_data
|
41
|
+
@tenant.get("/profiles", use_profile: false)[:value]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/power-bi/tenant.rb
CHANGED
@@ -1,13 +1,15 @@
|
|
1
1
|
module PowerBI
|
2
2
|
class Tenant
|
3
|
-
attr_reader :workspaces, :gateways, :capacities
|
3
|
+
attr_reader :workspaces, :gateways, :capacities, :profiles, :profile_id
|
4
4
|
|
5
5
|
def initialize(token_generator, retries: 5, logger: nil)
|
6
6
|
@token_generator = token_generator
|
7
7
|
@workspaces = WorkspaceArray.new(self)
|
8
8
|
@gateways = GatewayArray.new(self)
|
9
9
|
@capacities = CapacityArray.new(self)
|
10
|
+
@profiles = ProfileArray.new(self)
|
10
11
|
@logger = logger
|
12
|
+
@profile_id = nil
|
11
13
|
|
12
14
|
## WHY RETRIES? ##
|
13
15
|
# 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 :-)
|
@@ -42,7 +44,16 @@ module PowerBI
|
|
42
44
|
Capacity.new(self, nil, id)
|
43
45
|
end
|
44
46
|
|
45
|
-
def
|
47
|
+
def profile(id)
|
48
|
+
Profile.new(self, nil, id)
|
49
|
+
end
|
50
|
+
|
51
|
+
def profile=(profile)
|
52
|
+
@profile_id = profile.is_a?(String) ? profile : profile&.id
|
53
|
+
@workspaces.reload # we need to reload the workspaces because we look through the eyes of the profile
|
54
|
+
end
|
55
|
+
|
56
|
+
def get(url, params = {}, use_profile: true)
|
46
57
|
t0 = Time.now
|
47
58
|
conn = Faraday.new do |f|
|
48
59
|
f.request :retry, @retry_options
|
@@ -51,6 +62,9 @@ module PowerBI
|
|
51
62
|
req.params = params
|
52
63
|
req.headers['Accept'] = 'application/json'
|
53
64
|
req.headers['authorization'] = "Bearer #{token}"
|
65
|
+
if use_profile
|
66
|
+
add_spp_header(req)
|
67
|
+
end
|
54
68
|
yield req if block_given?
|
55
69
|
end
|
56
70
|
if response.status == 400
|
@@ -65,7 +79,7 @@ module PowerBI
|
|
65
79
|
end
|
66
80
|
end
|
67
81
|
|
68
|
-
def get_raw(url, params = {})
|
82
|
+
def get_raw(url, params = {}, use_profile: true)
|
69
83
|
t0 = Time.now
|
70
84
|
conn = Faraday.new do |f|
|
71
85
|
f.request :retry, @retry_options
|
@@ -73,6 +87,9 @@ module PowerBI
|
|
73
87
|
response = conn.get(PowerBI::BASE_URL + url) do |req|
|
74
88
|
req.params = params
|
75
89
|
req.headers['authorization'] = "Bearer #{token}"
|
90
|
+
if use_profile
|
91
|
+
add_spp_header(req)
|
92
|
+
end
|
76
93
|
yield req if block_given?
|
77
94
|
end
|
78
95
|
log "Calling (GET - raw) #{response.env.url.to_s} - took #{((Time.now - t0) * 1000).to_i} ms"
|
@@ -82,7 +99,7 @@ module PowerBI
|
|
82
99
|
response.body
|
83
100
|
end
|
84
101
|
|
85
|
-
def post(url, params = {})
|
102
|
+
def post(url, params = {}, use_profile: true)
|
86
103
|
t0 = Time.now
|
87
104
|
conn = Faraday.new do |f|
|
88
105
|
f.request :retry, @retry_options
|
@@ -92,6 +109,9 @@ module PowerBI
|
|
92
109
|
req.headers['Accept'] = 'application/json'
|
93
110
|
req.headers['Content-Type'] = 'application/json'
|
94
111
|
req.headers['authorization'] = "Bearer #{token}"
|
112
|
+
if use_profile
|
113
|
+
add_spp_header(req)
|
114
|
+
end
|
95
115
|
yield req if block_given?
|
96
116
|
end
|
97
117
|
log "Calling (POST) #{response.env.url.to_s} - took #{((Time.now - t0) * 1000).to_i} ms"
|
@@ -103,7 +123,7 @@ module PowerBI
|
|
103
123
|
end
|
104
124
|
end
|
105
125
|
|
106
|
-
def patch(url, params = {})
|
126
|
+
def patch(url, params = {}, use_profile: true)
|
107
127
|
t0 = Time.now
|
108
128
|
conn = Faraday.new do |f|
|
109
129
|
f.request :retry, @retry_options
|
@@ -113,6 +133,9 @@ module PowerBI
|
|
113
133
|
req.headers['Accept'] = 'application/json'
|
114
134
|
req.headers['Content-Type'] = 'application/json'
|
115
135
|
req.headers['authorization'] = "Bearer #{token}"
|
136
|
+
if use_profile
|
137
|
+
add_spp_header(req)
|
138
|
+
end
|
116
139
|
yield req if block_given?
|
117
140
|
end
|
118
141
|
log "Calling (PATCH) #{response.env.url.to_s} - took #{((Time.now - t0) * 1000).to_i} ms"
|
@@ -124,7 +147,7 @@ module PowerBI
|
|
124
147
|
end
|
125
148
|
end
|
126
149
|
|
127
|
-
def delete(url, params = {})
|
150
|
+
def delete(url, params = {}, use_profile: true)
|
128
151
|
t0 = Time.now
|
129
152
|
conn = Faraday.new do |f|
|
130
153
|
f.request :retry, @retry_options
|
@@ -133,6 +156,9 @@ module PowerBI
|
|
133
156
|
req.params = params
|
134
157
|
req.headers['Accept'] = 'application/json'
|
135
158
|
req.headers['authorization'] = "Bearer #{token}"
|
159
|
+
if use_profile
|
160
|
+
add_spp_header(req)
|
161
|
+
end
|
136
162
|
yield req if block_given?
|
137
163
|
end
|
138
164
|
log "Calling (DELETE) #{response.env.url.to_s} - took #{((Time.now - t0) * 1000).to_i} ms"
|
@@ -147,7 +173,7 @@ module PowerBI
|
|
147
173
|
end
|
148
174
|
end
|
149
175
|
|
150
|
-
def post_file(url, file, params = {})
|
176
|
+
def post_file(url, file, params = {}, use_profile: true)
|
151
177
|
t0 = Time.now
|
152
178
|
conn = Faraday.new do |f|
|
153
179
|
f.request :multipart
|
@@ -158,6 +184,9 @@ module PowerBI
|
|
158
184
|
req.headers['Accept'] = 'application/json'
|
159
185
|
req.headers['Content-Type'] = 'multipart/form-data'
|
160
186
|
req.headers['authorization'] = "Bearer #{token}"
|
187
|
+
if use_profile
|
188
|
+
add_spp_header(req)
|
189
|
+
end
|
161
190
|
req.body = {value: Faraday::UploadIO.new(file, 'application/octet-stream')}
|
162
191
|
req.options.timeout = 120 # default is 60 seconds Net::ReadTimeout
|
163
192
|
end
|
@@ -170,6 +199,12 @@ module PowerBI
|
|
170
199
|
|
171
200
|
private
|
172
201
|
|
202
|
+
def add_spp_header(req)
|
203
|
+
if @profile_id
|
204
|
+
req.headers['X-PowerBI-Profile-Id'] = @profile_id
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
173
208
|
def token
|
174
209
|
@token_generator.call
|
175
210
|
end
|
data/lib/power-bi.rb
CHANGED
@@ -22,6 +22,8 @@ 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
|
-
require_relative "power-bi/capacity"
|
28
|
+
require_relative "power-bi/capacity"
|
29
|
+
require_relative "power-bi/profile"
|
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.
|
4
|
+
version: 2.4.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:
|
11
|
+
date: 2024-05-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
@@ -95,9 +95,11 @@ files:
|
|
95
95
|
- lib/power-bi/datasource.rb
|
96
96
|
- lib/power-bi/gateway.rb
|
97
97
|
- lib/power-bi/gateway_datasource.rb
|
98
|
+
- lib/power-bi/gateway_datasource_user.rb
|
98
99
|
- lib/power-bi/object.rb
|
99
100
|
- lib/power-bi/page.rb
|
100
101
|
- lib/power-bi/parameter.rb
|
102
|
+
- lib/power-bi/profile.rb
|
101
103
|
- lib/power-bi/refresh.rb
|
102
104
|
- lib/power-bi/report.rb
|
103
105
|
- lib/power-bi/tenant.rb
|