power-bi 0.3.0 → 1.0.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: 61e60749c7e0da1010f9404770158dd257c2e4ea7bef82a6a97457dd85f8b7b8
4
- data.tar.gz: 017767f4e263817f1d0fec40f56869a5191bd1778d53ef383c2b7f97869b0068
3
+ metadata.gz: 418399b191274cab7954c37fa24d36f17cb2f802eff6654957e9b927fce9b9ee
4
+ data.tar.gz: 6f1681d24c0ad6b7a986753ccb53966a46a438b334cab39404e8584769daae0f
5
5
  SHA512:
6
- metadata.gz: b9e2c7483672138a160dfcca55ab7422c20ee3bb01499d959f2fde71cafff5b208acf8deb5dbbe3e80866f937b4a08bf99b653a9f35a120c3c56bad257c72e8a
7
- data.tar.gz: cccd0a870109d01e4db575487c402172144b9d0b928b9e62459c3a36cca5b4f09b3f5ac7274c187b7e9ea73b2aaad5bc6890d4411ad74aa61bb8124b5514e8f3
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
@@ -1,6 +1,7 @@
1
1
  require 'pry' # TODO remove in final product
2
2
  require 'faraday'
3
3
  require 'json'
4
+ require 'date'
4
5
 
5
6
  module PowerBI
6
7
  BASE_URL = 'https://api.powerbi.com/v1.0/myorg'
@@ -16,3 +17,6 @@ require_relative "power-bi/report"
16
17
  require_relative "power-bi/dataset"
17
18
  require_relative "power-bi/datasource"
18
19
  require_relative "power-bi/parameter"
20
+ require_relative "power-bi/refresh"
21
+ require_relative "power-bi/gateway"
22
+ require_relative "power-bi/gateway_datasource"
@@ -2,7 +2,7 @@ module PowerBI
2
2
  class Dataset
3
3
  attr_reader :id, :name, :add_rows_API_enabled, :configured_by, :is_refreshable, :is_effective_identity_required,
4
4
  :is_effective_identity_roles_required, :is_on_prem_gateway_required, :target_storage_mode, :workspace, :datasources,
5
- :parameters
5
+ :parameters, :refresh_history
6
6
 
7
7
  def initialize(tenant, data)
8
8
  @id = data[:id]
@@ -18,6 +18,7 @@ module PowerBI
18
18
  @tenant = tenant
19
19
  @datasources = DatasourceArray.new(@tenant, self)
20
20
  @parameters = ParameterArray.new(@tenant, self)
21
+ @refresh_history = RefreshArray.new(@tenant, self)
21
22
  end
22
23
 
23
24
  def update_parameter(name, value)
@@ -30,6 +31,9 @@ module PowerBI
30
31
  true
31
32
  end
32
33
 
34
+ def last_refresh
35
+ @refresh_history.first
36
+ end
33
37
 
34
38
  def refresh
35
39
  @tenant.post("/groups/#{workspace.id}/datasets/#{id}/refreshes") do |req|
@@ -37,6 +41,7 @@ module PowerBI
37
41
  notifyOption: "NoNotification"
38
42
  }.to_json
39
43
  end
44
+ @refresh_history.reload
40
45
  true
41
46
  end
42
47
 
@@ -45,6 +50,16 @@ module PowerBI
45
50
  true
46
51
  end
47
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
+
48
63
  end
49
64
 
50
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
@@ -0,0 +1,32 @@
1
+ module PowerBI
2
+ class Refresh
3
+ attr_reader :refresh_type, :start_time, :end_time, :service_exception_json, :status, :request_id
4
+
5
+ def initialize(tenant, data)
6
+ @id = data[:id]
7
+ @refresh_type = data[:refreshType]
8
+ @start_time = DateTime.iso8601(data[:startTime])
9
+ @end_time = DateTime.iso8601(data[:endTime]) if data[:endTime]
10
+ @service_exception_json = data[:serviceExceptionJson]
11
+ @status = data[:status]
12
+ @request_id = data[:requestId]
13
+ end
14
+
15
+ end
16
+
17
+ class RefreshArray < Array
18
+
19
+ def initialize(tenant, dataset)
20
+ super(tenant)
21
+ @dataset = dataset
22
+ end
23
+
24
+ def self.get_class
25
+ Refresh
26
+ end
27
+
28
+ def get_data
29
+ @tenant.get("/groups/#{@dataset.workspace.id}/datasets/#{@dataset.id}/refreshes", {'$top': '3'})[:value]
30
+ end
31
+ end
32
+ 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,6 +38,37 @@ module PowerBI
36
38
  true
37
39
  end
38
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|
44
+ req.body = {
45
+ format: format
46
+ }.to_json
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) }
70
+ end
71
+
39
72
  end
40
73
 
41
74
  class ReportArray < Array
@@ -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
@@ -14,23 +14,35 @@ module PowerBI
14
14
  @datasets = DatasetArray.new(@tenant, self)
15
15
  end
16
16
 
17
- def upload_pbix(file, dataset_name)
17
+ def upload_pbix(file, dataset_name, timeout: 30)
18
18
  data = @tenant.post_file("/groups/#{@id}/imports", file, {datasetDisplayName: dataset_name})
19
19
  import_id = data[:id]
20
20
  success = false
21
21
  iterations = 0
22
+ status_history = ''
23
+ old_status = ''
22
24
  while !success
23
25
  sleep 0.1
24
26
  iterations += 1
25
- raise UploadError if iterations > 300 # 30 seconds
26
- status = @tenant.get("/groups/#{@id}/imports/#{import_id}")
27
- success = (status[:importState] == "Succeeded")
27
+ raise UploadError.new("Upload did not succeed after #{timeout} seconds. Status history:#{status_history}") if iterations > (10 * timeout)
28
+ new_status = @tenant.get("/groups/#{@id}/imports/#{import_id}")[:importState].to_s
29
+ success = (new_status == "Succeeded")
30
+ if new_status != old_status
31
+ status_history += "\nStatus change after #{iterations/10.0}s: '#{old_status}' --> '#{new_status}'"
32
+ old_status = new_status
33
+ end
28
34
  end
29
35
  @reports.reload
30
36
  @datasets.reload
31
37
  true
32
38
  end
33
39
 
40
+ def delete
41
+ @tenant.delete("/groups/#{@id}")
42
+ @tenant.workspaces.reload
43
+ true
44
+ end
45
+
34
46
  # TODO LATER: the 'Viewer' acces right is currently not settable throught the API. Fix that later
35
47
  def add_user(email_address, access_right = 'Member')
36
48
  @tenant.post("/groups/#{id}/users") do |req|
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: 0.3.0
4
+ version: 1.0.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: 2020-01-27 00:00:00.000000000 Z
11
+ date: 2020-11-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -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,7 +50,10 @@ 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
56
+ - lib/power-bi/refresh.rb
54
57
  - lib/power-bi/report.rb
55
58
  - lib/power-bi/tenant.rb
56
59
  - lib/power-bi/workspace.rb
@@ -73,7 +76,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
73
76
  - !ruby/object:Gem::Version
74
77
  version: '0'
75
78
  requirements: []
76
- rubygems_version: 3.0.3
79
+ rubygems_version: 3.1.4
77
80
  signing_key:
78
81
  specification_version: 4
79
82
  summary: Ruby wrapper for the Power BI API