power-bi 0.6.0 → 1.0.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5a12ddf71e1ea1afad2cd5f4c7b059539cbc23da4a3e19834ad63bd6e62863ea
4
- data.tar.gz: 4960812cbfdcd0459afb47ff934177a289d5d0726e95e19c7d9f230b235fcbf6
3
+ metadata.gz: 418399b191274cab7954c37fa24d36f17cb2f802eff6654957e9b927fce9b9ee
4
+ data.tar.gz: 6f1681d24c0ad6b7a986753ccb53966a46a438b334cab39404e8584769daae0f
5
5
  SHA512:
6
- metadata.gz: b3ca5f092ba2f179e77fdb16fa3f984886d59c4ba10b5d3744038d15c56f5fdf59e37962c9a1fb543b9babcf84eea425a916869431354a480eae99493526f1e1
7
- data.tar.gz: 02044abfa2f718990f7584405b8ea5a4d6a2314c2aa00ea6465f7ad2da24c863d8294601f526b9fee8914d5f5f5656355be4c168472dc05fa68ca3080427fa74
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
@@ -18,3 +18,5 @@ require_relative "power-bi/dataset"
18
18
  require_relative "power-bi/datasource"
19
19
  require_relative "power-bi/parameter"
20
20
  require_relative "power-bi/refresh"
21
+ require_relative "power-bi/gateway"
22
+ require_relative "power-bi/gateway_datasource"
@@ -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
@@ -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
- @tenant.post("/groups/#{workspace.id}/reports/#{id}/ExportTo") do |req|
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
@@ -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
- if response.status != 200
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.6.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 - Currently supports workspaces
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