power-bi 0.6.0 → 1.0.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 +84 -0
- data/lib/power-bi.rb +2 -0
- data/lib/power-bi/dataset.rb +10 -0
- data/lib/power-bi/gateway.rb +25 -0
- data/lib/power-bi/gateway_datasource.rb +73 -0
- data/lib/power-bi/report.rb +27 -2
- data/lib/power-bi/tenant.rb +31 -2
- 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: 418399b191274cab7954c37fa24d36f17cb2f802eff6654957e9b927fce9b9ee
|
4
|
+
data.tar.gz: 6f1681d24c0ad6b7a986753ccb53966a46a438b334cab39404e8584769daae0f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1bfedc133434941eeb869dc909e322b85d21059311d1089963c48a0acd4b86d6e1d2ef05b5f2e6cd646b3eea16a5937c4aef6c46aef063afe887fcb7c2c2cb4d
|
7
|
+
data.tar.gz: 136a28bcfb43052f5aca2717200a4abcb3a3f81b1bcca272ff5ac79b9dad5bedb9f395446a37f00c971ed89d5edeb72e694a4c3ae685040911ec6eb1280c3cf6
|
data/README.md
CHANGED
@@ -1,3 +1,87 @@
|
|
1
1
|
# power-bi
|
2
2
|
|
3
3
|
Ruby wrapper around the Power BI API
|
4
|
+
|
5
|
+
# Initialization
|
6
|
+
|
7
|
+
The Power BI API does not handle the authorization part. It requires the user to pass a function where it can request tokens.
|
8
|
+
|
9
|
+
```
|
10
|
+
pbi = PowerBI::Tenant.new(->{token = get_token(client, token) ; token.token})
|
11
|
+
```
|
12
|
+
|
13
|
+
# Supported endpoints
|
14
|
+
|
15
|
+
## Workspaces (aka Groups)
|
16
|
+
|
17
|
+
* List workspaces: `pbi.workspaces`
|
18
|
+
* Create workspace: `pbi.workspaces.create`
|
19
|
+
* Upload PBIX to workspace: `ws.upload_pbix('./test.pbix', 'new_datasetname_in_the_service')`
|
20
|
+
* Delete workspace: `workspace.delete`
|
21
|
+
* Add a user to a wokspace: `workspace.add_user('company_0001@fabrikam.com')`
|
22
|
+
|
23
|
+
## Reports
|
24
|
+
|
25
|
+
* List reports in a workspace: `workspace.reports`
|
26
|
+
* Clone a report from one workspace to another: `report.clone(src_workspace, new_report_name)`
|
27
|
+
* Rebind report to another dataset: `report.rebind(dataset)`
|
28
|
+
* Export report to file: `report.export_to_file(filenam, format: 'PDF')`
|
29
|
+
|
30
|
+
## Datasets
|
31
|
+
|
32
|
+
* List datasets in a workspace: `workspace.datasets`
|
33
|
+
* Update parameter in a dataset: `dataset.update_parameter(parameter_name, new_value)`
|
34
|
+
* Get time of last refresh: `dataset.last_refresh`
|
35
|
+
* Refresh the dataset: `dataset.refresh`
|
36
|
+
* Delete the dataset: `dataset.delete`
|
37
|
+
* Bind dataset to a gateway datasource: `dataset.bind_to_gateway(gateway, gateway_datasource)`
|
38
|
+
|
39
|
+
## Gateways
|
40
|
+
|
41
|
+
* List gateways: `pbi.gateways`
|
42
|
+
|
43
|
+
## Gateway datasources
|
44
|
+
|
45
|
+
* List datasources in a gateway: `gateway.gateway_datasources`
|
46
|
+
* Update credentials of a gateway datasource: `gateway_datasource.update_credentials(new_credentials)`
|
47
|
+
* Create a new gateway datasource: `gateway.gateway_datasource.create(name, credentials, db_server, db_name)`
|
48
|
+
|
49
|
+
# Note about gateway credentials
|
50
|
+
|
51
|
+
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:
|
52
|
+
|
53
|
+
```
|
54
|
+
using System;
|
55
|
+
using Microsoft.PowerBI.Api.Models;
|
56
|
+
using Microsoft.PowerBI.Api.Models.Credentials;
|
57
|
+
using Microsoft.PowerBI.Api.Extensions;
|
58
|
+
|
59
|
+
|
60
|
+
namespace pbi_credentials
|
61
|
+
{
|
62
|
+
class Program
|
63
|
+
{
|
64
|
+
static void Main(string[] args)
|
65
|
+
{
|
66
|
+
Console.WriteLine("Kicking off");
|
67
|
+
|
68
|
+
var credentials = new BasicCredentials(username: "cdmuser", password: "cdmuserpw4879515365");
|
69
|
+
|
70
|
+
var publicKey = new GatewayPublicKey("AQAB", "ru5gTdHbJ+8eC/uwERTOMz9Yktf/kCDWeRDCY1M5fPCB9+p4c8Uk54/NzT5ZWPQCp958bLcO8nSOSOpz4I8fW/AI4d+JxwW6VCsxzue2mKbJjeuSDXXmIiNUFqvjOIolfSIxJFNlfWkZUFlaD3dXgJkjJxrrc4OrYBDUt0FF14UsvdZymTbOl39sAhD4i9CqkXTqm6+JDxsEkPE3GAZ6ZslCsRUqu7lX73anAHkm889FR9NOMtsLV02JDMKCblJqnoszTzgExEEeoTJKxLiJdC8Mfbl96fKFS8JElJIzfTPzldGx5TxdjRmekQODWr7SNMSVJJQTJaANh9C2FZ85pQ==");
|
71
|
+
var credentialsEncryptor = new AsymmetricKeyEncryptor(publicKey);
|
72
|
+
|
73
|
+
var credentialDetails = new CredentialDetails(
|
74
|
+
credentials,
|
75
|
+
PrivacyLevel.Private,
|
76
|
+
EncryptedConnection.Encrypted,
|
77
|
+
credentialsEncryptor
|
78
|
+
);
|
79
|
+
Console.WriteLine(credentialDetails.Credentials);
|
80
|
+
|
81
|
+
Console.WriteLine("Bye Bye");
|
82
|
+
}
|
83
|
+
}
|
84
|
+
}
|
85
|
+
```
|
86
|
+
|
87
|
+
|
data/lib/power-bi.rb
CHANGED
data/lib/power-bi/dataset.rb
CHANGED
@@ -50,6 +50,16 @@ module PowerBI
|
|
50
50
|
true
|
51
51
|
end
|
52
52
|
|
53
|
+
def bind_to_gateway(gateway, gateway_datasource)
|
54
|
+
@tenant.post("/groups/#{workspace.id}/datasets/#{id}/Default.BindToGateway") do |req|
|
55
|
+
req.body = {
|
56
|
+
gatewayObjectId: gateway.id,
|
57
|
+
datasourceObjectIds: [gateway_datasource.id]
|
58
|
+
}.to_json
|
59
|
+
end
|
60
|
+
true
|
61
|
+
end
|
62
|
+
|
53
63
|
end
|
54
64
|
|
55
65
|
class DatasetArray < Array
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module PowerBI
|
2
|
+
class Gateway
|
3
|
+
attr_reader :name, :id, :type, :public_key, :gateway_datasources
|
4
|
+
|
5
|
+
def initialize(tenant, data)
|
6
|
+
@id = data[:id]
|
7
|
+
@name = data[:name]
|
8
|
+
@type = data[:type]
|
9
|
+
@public_key = data[:publicKey]
|
10
|
+
@tenant = tenant
|
11
|
+
@gateway_datasources = GatewayDatasourceArray.new(@tenant, self)
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
class GatewayArray < Array
|
17
|
+
def self.get_class
|
18
|
+
Gateway
|
19
|
+
end
|
20
|
+
|
21
|
+
def get_data
|
22
|
+
@tenant.get("/gateways")[:value]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module PowerBI
|
2
|
+
class GatewayDatasource
|
3
|
+
attr_reader :id, :gateway_id, :datasource_type, :connection_details, :credential_type, :datasource_name, :gateway
|
4
|
+
|
5
|
+
def initialize(tenant, data)
|
6
|
+
@gateway_id = data[:gatewayId]
|
7
|
+
@datasource_type = data[:datasourceType]
|
8
|
+
@datasource_name = data[:datasourceName]
|
9
|
+
@connection_details = data[:connectionDetails]
|
10
|
+
@id = data[:id]
|
11
|
+
@credential_type = data[:credentialType]
|
12
|
+
@gateway = data[:gateway]
|
13
|
+
@tenant = tenant
|
14
|
+
end
|
15
|
+
|
16
|
+
def update_credentials(encrypted_credentials)
|
17
|
+
response = @tenant.patch("/gateways/#{gateway.id}/datasources/#{id}") do |req|
|
18
|
+
req.body = {
|
19
|
+
credentialDetails: {
|
20
|
+
credentialType: "Basic",
|
21
|
+
credentials: encrypted_credentials,
|
22
|
+
encryptedConnection: "Encrypted",
|
23
|
+
encryptionAlgorithm: "RSA-OAEP",
|
24
|
+
privacyLevel: "Organizational",
|
25
|
+
useCallerAADIdentity: false,
|
26
|
+
useEndUserOAuth2Credentials: false,
|
27
|
+
},
|
28
|
+
}.to_json
|
29
|
+
end
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
class GatewayDatasourceArray < Array
|
36
|
+
|
37
|
+
def initialize(tenant, gateway)
|
38
|
+
super(tenant)
|
39
|
+
@gateway = gateway
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.get_class
|
43
|
+
GatewayDatasource
|
44
|
+
end
|
45
|
+
|
46
|
+
# only MySQL type is currently supported
|
47
|
+
def create(name, encrypted_credentials, db_server, db_name)
|
48
|
+
data = @tenant.post("/gateways/#{@gateway.id}/datasources",) do |req|
|
49
|
+
req.body = {
|
50
|
+
connectionDetails: {server: db_server, database: db_name}.to_json,
|
51
|
+
credentialDetails: {
|
52
|
+
credentialType: "Basic",
|
53
|
+
credentials: encrypted_credentials,
|
54
|
+
encryptedConnection: "Encrypted",
|
55
|
+
encryptionAlgorithm: "RSA-OAEP",
|
56
|
+
privacyLevel: "Organizational",
|
57
|
+
useCallerAADIdentity: false,
|
58
|
+
useEndUserOAuth2Credentials: false,
|
59
|
+
},
|
60
|
+
datasourceName: name,
|
61
|
+
datasourceType: 'MySql',
|
62
|
+
}.to_json
|
63
|
+
end
|
64
|
+
self.reload
|
65
|
+
GatewayDatasource.new(@tenant, data)
|
66
|
+
end
|
67
|
+
|
68
|
+
def get_data
|
69
|
+
data = @tenant.get("/gateways/#{@gateway.id}/datasources")[:value]
|
70
|
+
data.each { |d| d[:gateway] = @gateway }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/power-bi/report.rb
CHANGED
@@ -2,6 +2,8 @@ module PowerBI
|
|
2
2
|
class Report
|
3
3
|
attr_reader :name, :id, :report_type, :web_url, :embed_url, :is_from_pbix, :is_owned_by_me, :dataset_id, :workspace
|
4
4
|
|
5
|
+
class ExportToFileError < PowerBI::Error ; end
|
6
|
+
|
5
7
|
def initialize(tenant, data)
|
6
8
|
@id = data[:id]
|
7
9
|
@report_type = data[:reportType]
|
@@ -36,12 +38,35 @@ module PowerBI
|
|
36
38
|
true
|
37
39
|
end
|
38
40
|
|
39
|
-
def export_to_file(format: 'PDF')
|
40
|
-
|
41
|
+
def export_to_file(filename, format: 'PDF', timeout: 300)
|
42
|
+
# post
|
43
|
+
data = @tenant.post("/groups/#{workspace.id}/reports/#{id}/ExportTo") do |req|
|
41
44
|
req.body = {
|
42
45
|
format: format
|
43
46
|
}.to_json
|
44
47
|
end
|
48
|
+
export_id = data[:id]
|
49
|
+
|
50
|
+
# poll
|
51
|
+
success = false
|
52
|
+
iterations = 0
|
53
|
+
status_history = ''
|
54
|
+
old_status = ''
|
55
|
+
while !success
|
56
|
+
sleep 0.1
|
57
|
+
iterations += 1
|
58
|
+
raise ExportToFileError.new("Report export to file did not succeed after #{timeout} seconds. Status history:#{status_history}") if iterations > (10 * timeout)
|
59
|
+
new_status = @tenant.get("/groups/#{workspace.id}/reports/#{id}/exports/#{export_id}")[:status].to_s
|
60
|
+
success = (new_status == "Succeeded")
|
61
|
+
if new_status != old_status
|
62
|
+
status_history += "\nStatus change after #{iterations/10.0}s: '#{old_status}' --> '#{new_status}'"
|
63
|
+
old_status = new_status
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# get and write file
|
68
|
+
data = @tenant.get_raw("/groups/#{workspace.id}/reports/#{id}/exports/#{export_id}/file")
|
69
|
+
File.open(filename, "wb") { |f| f.write(data) }
|
45
70
|
end
|
46
71
|
|
47
72
|
end
|
data/lib/power-bi/tenant.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
module PowerBI
|
2
2
|
class Tenant
|
3
|
-
attr_reader :workspaces
|
3
|
+
attr_reader :workspaces, :gateways
|
4
4
|
|
5
5
|
def initialize(token_generator)
|
6
6
|
@token_generator = token_generator
|
7
7
|
@workspaces = WorkspaceArray.new(self)
|
8
|
+
@gateways = GatewayArray.new(self)
|
8
9
|
end
|
9
10
|
|
10
11
|
def get(url, params = {})
|
@@ -14,7 +15,7 @@ module PowerBI
|
|
14
15
|
req.headers['authorization'] = "Bearer #{token}"
|
15
16
|
yield req if block_given?
|
16
17
|
end
|
17
|
-
|
18
|
+
unless [200, 202].include? response.status
|
18
19
|
raise APIError.new("Error calling Power BI API (status #{response.status}): #{response.body}")
|
19
20
|
end
|
20
21
|
unless response.body.empty?
|
@@ -22,6 +23,18 @@ module PowerBI
|
|
22
23
|
end
|
23
24
|
end
|
24
25
|
|
26
|
+
def get_raw(url, params = {})
|
27
|
+
response = Faraday.get(PowerBI::BASE_URL + url) do |req|
|
28
|
+
req.params = params
|
29
|
+
req.headers['authorization'] = "Bearer #{token}"
|
30
|
+
yield req if block_given?
|
31
|
+
end
|
32
|
+
unless [200, 202].include? response.status
|
33
|
+
raise APIError.new("Error calling Power BI API (status #{response.status}): #{response.body}")
|
34
|
+
end
|
35
|
+
response.body
|
36
|
+
end
|
37
|
+
|
25
38
|
def post(url, params = {})
|
26
39
|
response = Faraday.post(PowerBI::BASE_URL + url) do |req|
|
27
40
|
req.params = params
|
@@ -30,6 +43,22 @@ module PowerBI
|
|
30
43
|
req.headers['authorization'] = "Bearer #{token}"
|
31
44
|
yield req if block_given?
|
32
45
|
end
|
46
|
+
unless [200, 201, 202].include? response.status
|
47
|
+
raise APIError.new("Error calling Power BI API (status #{response.status}): #{response.body}")
|
48
|
+
end
|
49
|
+
unless response.body.empty?
|
50
|
+
JSON.parse(response.body, symbolize_names: true)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def patch(url, params = {})
|
55
|
+
response = Faraday.patch(PowerBI::BASE_URL + url) do |req|
|
56
|
+
req.params = params
|
57
|
+
req.headers['Accept'] = 'application/json'
|
58
|
+
req.headers['Content-Type'] = 'application/json'
|
59
|
+
req.headers['authorization'] = "Bearer #{token}"
|
60
|
+
yield req if block_given?
|
61
|
+
end
|
33
62
|
unless [200, 202].include? response.status
|
34
63
|
raise APIError.new("Error calling Power BI API (status #{response.status}): #{response.body}")
|
35
64
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: power-bi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lode Cools
|
@@ -38,7 +38,7 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '3.0'
|
41
|
-
description: Ruby wrapper for the Power BI API
|
41
|
+
description: Ruby wrapper for the Power BI API
|
42
42
|
email: lode.cools1@gmail.com
|
43
43
|
executables: []
|
44
44
|
extensions: []
|
@@ -50,6 +50,8 @@ files:
|
|
50
50
|
- lib/power-bi/array.rb
|
51
51
|
- lib/power-bi/dataset.rb
|
52
52
|
- lib/power-bi/datasource.rb
|
53
|
+
- lib/power-bi/gateway.rb
|
54
|
+
- lib/power-bi/gateway_datasource.rb
|
53
55
|
- lib/power-bi/parameter.rb
|
54
56
|
- lib/power-bi/refresh.rb
|
55
57
|
- lib/power-bi/report.rb
|