tableau_rest_api 0.1.2

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.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +8 -0
  3. data/Guardfile +21 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +103 -0
  6. data/Rakefile +10 -0
  7. data/bin/console +14 -0
  8. data/bin/setup +8 -0
  9. data/fixtures/vcr_cassettes/add_user.yml +223 -0
  10. data/fixtures/vcr_cassettes/add_user_to_group.yml +451 -0
  11. data/fixtures/vcr_cassettes/add_user_to_group_through_site.yml +451 -0
  12. data/fixtures/vcr_cassettes/create_group.yml +224 -0
  13. data/fixtures/vcr_cassettes/create_group_through_site.yml +224 -0
  14. data/fixtures/vcr_cassettes/datasources.yml +234 -0
  15. data/fixtures/vcr_cassettes/datasources_through_site.yml +234 -0
  16. data/fixtures/vcr_cassettes/delete_datasource.yml +334 -0
  17. data/fixtures/vcr_cassettes/delete_group.yml +328 -0
  18. data/fixtures/vcr_cassettes/delete_group_through_site.yml +329 -0
  19. data/fixtures/vcr_cassettes/delete_schedule.yml +276 -0
  20. data/fixtures/vcr_cassettes/delete_site.yml +214 -0
  21. data/fixtures/vcr_cassettes/delete_subscription.yml +329 -0
  22. data/fixtures/vcr_cassettes/delete_workbook.yml +350 -0
  23. data/fixtures/vcr_cassettes/get_datasource.yml +673 -0
  24. data/fixtures/vcr_cassettes/get_workbook.yml +15044 -0
  25. data/fixtures/vcr_cassettes/groups.yml +229 -0
  26. data/fixtures/vcr_cassettes/groups_through_site.yml +230 -0
  27. data/fixtures/vcr_cassettes/projects_through_site.yml +230 -0
  28. data/fixtures/vcr_cassettes/query_workbooks.yml +250 -0
  29. data/fixtures/vcr_cassettes/remove_user_from_group_through_site.yml +440 -0
  30. data/fixtures/vcr_cassettes/remove_user_from_site_through_site.yml +329 -0
  31. data/fixtures/vcr_cassettes/schedules.yml +115 -0
  32. data/fixtures/vcr_cassettes/server_info.yml +53 -0
  33. data/fixtures/vcr_cassettes/signin.yml +57 -0
  34. data/fixtures/vcr_cassettes/signout.yml +102 -0
  35. data/fixtures/vcr_cassettes/sites.yml +118 -0
  36. data/fixtures/vcr_cassettes/subscriptions.yml +226 -0
  37. data/fixtures/vcr_cassettes/switch_site.yml +113 -0
  38. data/fixtures/vcr_cassettes/update_group_through_site.yml +343 -0
  39. data/fixtures/vcr_cassettes/update_user_through_site.yml +333 -0
  40. data/fixtures/vcr_cassettes/users_in_group.yml +339 -0
  41. data/fixtures/vcr_cassettes/users_on_site.yml +230 -0
  42. data/fixtures/vcr_cassettes/users_through_site.yml +230 -0
  43. data/fixtures/vcr_cassettes/workbooks_through_site.yml +250 -0
  44. data/lib/tableau_rest_api.rb +32 -0
  45. data/lib/tableau_rest_api/api_error.rb +7 -0
  46. data/lib/tableau_rest_api/area/schedule_subscription.rb +41 -0
  47. data/lib/tableau_rest_api/area/user_group.rb +72 -0
  48. data/lib/tableau_rest_api/area/workbook_datasource.rb +87 -0
  49. data/lib/tableau_rest_api/client.rb +65 -0
  50. data/lib/tableau_rest_api/helpers/helpers.rb +9 -0
  51. data/lib/tableau_rest_api/resource.rb +42 -0
  52. data/lib/tableau_rest_api/resources/base.rb +24 -0
  53. data/lib/tableau_rest_api/resources/datasource.rb +12 -0
  54. data/lib/tableau_rest_api/resources/group.rb +10 -0
  55. data/lib/tableau_rest_api/resources/project.rb +12 -0
  56. data/lib/tableau_rest_api/resources/schedule.rb +12 -0
  57. data/lib/tableau_rest_api/resources/server.rb +12 -0
  58. data/lib/tableau_rest_api/resources/site.rb +68 -0
  59. data/lib/tableau_rest_api/resources/subscription.rb +10 -0
  60. data/lib/tableau_rest_api/resources/user.rb +12 -0
  61. data/lib/tableau_rest_api/resources/workbook.rb +12 -0
  62. data/lib/tableau_rest_api/util/config.rb +22 -0
  63. data/lib/tableau_rest_api/util/response.rb +11 -0
  64. data/lib/tableau_rest_api/util/token.rb +16 -0
  65. data/lib/tableau_rest_api/util/upload.rb +29 -0
  66. data/lib/tableau_rest_api/version.rb +3 -0
  67. data/tableau_rest_api.gemspec +33 -0
  68. metadata +222 -0
@@ -0,0 +1,65 @@
1
+ module TableauRestApi
2
+ # Client class wrapping a subset of the Tableau Rest API
3
+ # This class just contains worker methods for interacting with Tableau.
4
+ # See the Request sub-class for resource requests.
5
+ class Client
6
+ attr_reader :config, :token
7
+
8
+ def initialize
9
+ @config = Config.new.options
10
+ end
11
+
12
+ def configure(config)
13
+ @config = Config.new(config).options
14
+ end
15
+
16
+ def login(*_args)
17
+ return if authorised?
18
+ url = build_url ['auth', 'signin']
19
+ resp = post url, @config[:credentials]
20
+ @token = Token.new(resp.credentials.token, @config[:auth_duration])
21
+ end
22
+
23
+ def logout
24
+ post(build_url ['auth', 'signout'])
25
+ @token = nil
26
+ !authorised?
27
+ end
28
+
29
+ def authorised?
30
+ token = self.token
31
+ token && !token.expired? ? true : false
32
+ end
33
+
34
+ private
35
+
36
+ def header(boundary=nil)
37
+ head = { :accept => :json, :content_type => :json }
38
+ head = authorised? ? head.merge({ :x_tableau_auth => self.token.value }) : head
39
+ head = boundary ? head.merge({ :content_type => "multipart/mixed; boundary=#{boundary}"}) : head
40
+ end
41
+
42
+ def build_url(endpoint)
43
+ endpoint = endpoint.is_a?(Array) ? endpoint.join('/') : endpoint
44
+ "#{@config[:host]}/#{@config[:base]}/#{@config[:api_version]}/#{endpoint}"
45
+ end
46
+
47
+ def get(url)
48
+ Response.new(RestClient.get(url, header)).parse
49
+ end
50
+
51
+ def post(url, data={}, boundary=nil)
52
+ data = data.to_json unless boundary
53
+ headers = header boundary
54
+ Response.new(RestClient.post(url, data, headers)).parse
55
+ end
56
+
57
+ def put(url, data={})
58
+ Response.new(RestClient.put(url, data.to_json, header)).parse
59
+ end
60
+
61
+ def delete(url)
62
+ Response.new(RestClient.delete(url, header)).parse
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,9 @@
1
+ module TableauRestApi
2
+ module Helpers
3
+ def self.camel_case_lower(term)
4
+ term.split('_')
5
+ .inject([]){ |buf,elm| buf.push(buf.empty? ? elm : elm.capitalize) }
6
+ .join
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,42 @@
1
+ module TableauRestApi
2
+ # Subclass providing access to Tableau resources.
3
+ # An auth token is requested if missing or expired.
4
+ class Resource < Client
5
+ aspector do
6
+ before :sites, :login
7
+ before :create_site, :login
8
+ before :switch_site, :login
9
+ before :delete_site, :login
10
+ end
11
+
12
+ include TableauRestApi::UserGroup
13
+ include TableauRestApi::WorkbookDatasource
14
+ include TableauRestApi::ScheduleSubscription
15
+
16
+ def server_info
17
+ url = build_url 'serverinfo'
18
+ Server.new((get url).serverInfo)
19
+ end
20
+
21
+ def sites
22
+ url = build_url 'sites'
23
+ (get url).sites.site.to_a.map { |site| Site.new(site, self) }
24
+ end
25
+
26
+ def create_site(site)
27
+ url = build_url 'sites'
28
+ Site.new((post url, site).site, self)
29
+ end
30
+
31
+ def switch_site(site)
32
+ url = build_url ['auth', 'switchSite']
33
+ @token = Token.new((post url, site).credentials.token, self.config[:auth_duration])
34
+ end
35
+
36
+ def delete_site(site_id)
37
+ url = build_url ['sites', site_id]
38
+ delete url
39
+ @token = nil
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,24 @@
1
+ module TableauRestApi
2
+ class Base
3
+ def to_hash
4
+ self.to_array.to_h
5
+ end
6
+
7
+ def to_h
8
+ self.to_hash
9
+ end
10
+
11
+ def to_array
12
+ filter_instance_variables.map do |var|
13
+ [Helpers::camel_case_lower(var[1..-1]).to_sym, self.instance_variable_get(var)]
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def filter_instance_variables
20
+ self.instance_variables.select { |var| var != :@called_by }
21
+ end
22
+ end
23
+ end
24
+
@@ -0,0 +1,12 @@
1
+ module TableauRestApi
2
+ class Datasource < Base
3
+ attr_reader :id, :name, :type, :created_at
4
+
5
+ def initialize(ds)
6
+ @id = ds.id
7
+ @name = ds.name
8
+ @type = ds.type
9
+ @created_at = ds.createdAt
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,10 @@
1
+ module TableauRestApi
2
+ class Group < Base
3
+ attr_reader :id, :name
4
+
5
+ def initialize(group)
6
+ @id = group.id
7
+ @name = group.name
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,12 @@
1
+ module TableauRestApi
2
+ class Project < Base
3
+ attr_reader :id, :name, :description, :content_permissions
4
+
5
+ def initialize(project)
6
+ @id = project.id
7
+ @name = project.name
8
+ @description = project.description
9
+ @content_permissions = project.contentPermissions
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module TableauRestApi
2
+ class Schedule < Base
3
+ attr_reader :id, :name, :state, :priority
4
+
5
+ def initialize(schedule)
6
+ @id = schedule.id
7
+ @name = schedule.name
8
+ @state = schedule.state
9
+ @priority = schedule.priority
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module TableauRestApi
2
+ class Server < Base
3
+ attr_reader :version, :build , :api_version
4
+
5
+ def initialize(server)
6
+ product_version = server.productVersion
7
+ @version = product_version.value
8
+ @build = product_version.build
9
+ @api_version = server.restApiVersion
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,68 @@
1
+ module TableauRestApi
2
+ class Site < Base
3
+ attr_reader :id, :name, :content_url
4
+
5
+ def initialize(site, called_by)
6
+ @id = site.id
7
+ @name = site.name
8
+ @content_url = site.contentUrl
9
+ @called_by = called_by
10
+ end
11
+
12
+ def users
13
+ @called_by.users_on_site(@id)
14
+ end
15
+
16
+ def groups
17
+ @called_by.groups(@id)
18
+ end
19
+
20
+ def datasources
21
+ @called_by.datasources(@id)
22
+ end
23
+
24
+ def projects
25
+ @called_by.query_projects(@id)
26
+ end
27
+
28
+ def workbooks
29
+ @called_by.query_workbooks(@id)
30
+ end
31
+
32
+ def users_in_group(group_id)
33
+ @called_by.users_in_group(@id, group_id)
34
+ end
35
+
36
+ def create_group(group)
37
+ @called_by.create_group(@id, group)
38
+ end
39
+
40
+ def delete_group(group_id)
41
+ @called_by.delete_group(@id, group_id)
42
+ end
43
+
44
+ def add_user_to_group(group_id, user_id)
45
+ @called_by.add_user_to_group(@id, group_id, user_id)
46
+ end
47
+
48
+ def add_user_to_site(user)
49
+ @called_by.add_user_to_site(@id, user)
50
+ end
51
+
52
+ def remove_user_from_group(group_id, user_id)
53
+ @called_by.remove_user_from_group(@id, group_id, user_id)
54
+ end
55
+
56
+ def remove_user_from_site(user_id)
57
+ @called_by.remove_user_from_site(@id, user_id)
58
+ end
59
+
60
+ def update_user(user)
61
+ @called_by.update_user(@id, user)
62
+ end
63
+
64
+ def update_group(group)
65
+ @called_by.update_group(@id, group)
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,10 @@
1
+ module TableauRestApi
2
+ class Subscription < Base
3
+ attr_reader :id, :subject
4
+
5
+ def initialize(sub)
6
+ @id = sub.id
7
+ @subject = sub.subject
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,12 @@
1
+ module TableauRestApi
2
+ class User < Base
3
+ attr_reader :id, :name, :site_role, :last_login
4
+
5
+ def initialize(user)
6
+ @id = user.id
7
+ @name = user.name
8
+ @site_role = user.siteRole
9
+ @last_login = user.lastLogin
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module TableauRestApi
2
+ class Workbook < Base
3
+ attr_reader :id, :name, :content_url, :project
4
+
5
+ def initialize(workbook)
6
+ @id = workbook.id
7
+ @name = workbook.name
8
+ @content_url = workbook.contentUrl
9
+ @project = Project.new(workbook.project)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,22 @@
1
+ module TableauRestApi
2
+ class Config
3
+ attr_reader :options
4
+
5
+ def initialize(config={})
6
+ @options = {
7
+ host: ENV['TABLEAU_HOST'] || 'https://tableau.lvh.me',
8
+ api_version: '2.6',
9
+ base: 'api',
10
+ auth_duration: ENV['TABLEAU_AUTH_TOKEN_DURATION'] || 4.hours,
11
+ credentials: {
12
+ credentials: {
13
+ name: ENV['TABLEAU_USERNAME'] || 'admin',
14
+ password: ENV['TABLEAU_PASSWORD'] || 'admin',
15
+ site: { contentUrl: "" }
16
+ }
17
+ }
18
+ }
19
+ @options = @options.merge(config)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,11 @@
1
+ module TableauRestApi
2
+ class Response
3
+ def initialize(resp)
4
+ @body = resp.body
5
+ end
6
+
7
+ def parse
8
+ @body.length >= 2 ? JSON::parse(@body, object_class: OpenStruct) : nil
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,16 @@
1
+ module TableauRestApi
2
+ # Representation of a x_auth_tableau token
3
+ # These tokens have a finite lifespan, the default is 4 hours.
4
+ class Token
5
+ attr_reader :value, :expires
6
+
7
+ def initialize(token, duration)
8
+ @value = token
9
+ @expires = duration.from_now
10
+ end
11
+
12
+ def expired?
13
+ @value ? Time.now > @expires : true
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,29 @@
1
+ module TableauRestApi
2
+ class Upload
3
+ def initialize(metadata, payload, boundary)
4
+ @metadata = metadata
5
+ @payload = payload
6
+ @boundary = boundary
7
+ end
8
+
9
+ def build(obj=:workbook)
10
+ <<-END
11
+ --#{@boundary}\r
12
+ Content-Disposition: name="request_payload"\r
13
+ Content-Type: text/xml\r
14
+ \r
15
+ <tsRequest>\r
16
+ <#{obj} name="#{@metadata[:name]}">\r
17
+ <project id="#{@metadata[:project]}"/>\r
18
+ </#{obj}>\r
19
+ </tsRequest>\r
20
+ --#{@boundary}\r
21
+ Content-Disposition: name="tableau_#{obj}"; filename="#{@payload[:filename]}"\r
22
+ Content-Type: application/octet-stream\r
23
+ \r
24
+ #{@payload[:data]}\r
25
+ --#{@boundary}--\r
26
+ END
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,3 @@
1
+ module TableauRestApi
2
+ VERSION = "0.1.2"
3
+ end
@@ -0,0 +1,33 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'tableau_rest_api/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "tableau_rest_api"
8
+ spec.version = TableauRestApi::VERSION
9
+ spec.authors = ["Dave Lancaster"]
10
+ spec.email = ["lancaster.dave@gmail.com"]
11
+
12
+ spec.summary = %q{Ruby library wrapping the Tableau REST API.}
13
+ spec.description = %q{Exposes a subset of the Tablea v2.6 REST API (json).}
14
+ spec.homepage = "https://github.com/davelancaster/tableau-rest-api"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = "exe"
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.14"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "minitest", "~> 5.0"
27
+ spec.add_development_dependency "vcr", "~> 3.0"
28
+ spec.add_development_dependency "webmock", "~> 3.0"
29
+
30
+ spec.add_dependency "rest-client", "~> 2.0"
31
+ spec.add_dependency "aspector", "~> 0.14"
32
+ spec.add_dependency "timerizer", "~> 0.1"
33
+ end