tableau_api 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 30da344a82d4ce33c21a5e921b40b9efbdc4493c
4
+ data.tar.gz: 97271bab0ec0bbc33a94cbbe60e3af0aa9e48f70
5
+ SHA512:
6
+ metadata.gz: a340b001cd5d0ffbb75707fc2059f65342424d696e8cb5851f5078f9b9ad6ac641437e20816f7abf6bdece110681cc1993528397eafa34918dd37ab3215542e4
7
+ data.tar.gz: 33ee40ceee96a90680e7daaa1b87acd96cdbb99d24644854818f1d363b184d3717da4e00879b2e8d60915059829ff66eb30f515f665cc34c76371caf7098e709
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.rubocop.yml ADDED
@@ -0,0 +1,17 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.2
3
+
4
+ Style/Documentation:
5
+ Enabled: false
6
+
7
+ LineLength:
8
+ Max: 170
9
+
10
+ MethodLength:
11
+ Max: 20
12
+
13
+ Metrics/AbcSize:
14
+ Max: 40
15
+
16
+ MethodLength:
17
+ Max: 30
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.2.5
data/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ cache: bundler
2
+ branches:
3
+ only:
4
+ - master
5
+ language: ruby
6
+ rvm:
7
+ - 2.3.1
8
+ - 2.2.5
9
+ - 2.1.9
data/CHANGELOG.md ADDED
@@ -0,0 +1,12 @@
1
+ # Change Log
2
+ All notable changes to this project will be documented in this file.
3
+ This project adheres to [Semantic Versioning](http://semver.org/).
4
+
5
+ ## [Unreleased]
6
+
7
+ ## [1.0.0] - 2016-06-06
8
+ ### Added
9
+ - Initial Release
10
+
11
+ [Unreleased]: https://github.com/civisanalytics/tableau_api/compare/v1.0.0...HEAD
12
+ [1.0.0]: https://github.com/civisanalytics/tableau_api/tree/v1.0.0
@@ -0,0 +1,49 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, and in the interest of
4
+ fostering an open and welcoming community, we pledge to respect all people who
5
+ contribute through reporting issues, posting feature requests, updating
6
+ documentation, submitting pull requests or patches, and other activities.
7
+
8
+ We are committed to making participation in this project a harassment-free
9
+ experience for everyone, regardless of level of experience, gender, gender
10
+ identity and expression, sexual orientation, disability, personal appearance,
11
+ body size, race, ethnicity, age, religion, or nationality.
12
+
13
+ Examples of unacceptable behavior by participants include:
14
+
15
+ * The use of sexualized language or imagery
16
+ * Personal attacks
17
+ * Trolling or insulting/derogatory comments
18
+ * Public or private harassment
19
+ * Publishing other's private information, such as physical or electronic
20
+ addresses, without explicit permission
21
+ * Other unethical or unprofessional conduct
22
+
23
+ Project maintainers have the right and responsibility to remove, edit, or
24
+ reject comments, commits, code, wiki edits, issues, and other contributions
25
+ that are not aligned to this Code of Conduct, or to ban temporarily or
26
+ permanently any contributor for other behaviors that they deem inappropriate,
27
+ threatening, offensive, or harmful.
28
+
29
+ By adopting this Code of Conduct, project maintainers commit themselves to
30
+ fairly and consistently applying these principles to every aspect of managing
31
+ this project. Project maintainers who do not follow or enforce the Code of
32
+ Conduct may be permanently removed from the project team.
33
+
34
+ This code of conduct applies both within project spaces and in public spaces
35
+ when an individual is representing the project or its community.
36
+
37
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
38
+ reported by contacting a project maintainer at opensource@civisanalytics.com. All
39
+ complaints will be reviewed and investigated and will result in a response that
40
+ is deemed necessary and appropriate to the circumstances. Maintainers are
41
+ obligated to maintain confidentiality with regard to the reporter of an
42
+ incident.
43
+
44
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
45
+ version 1.3.0, available at
46
+ [http://contributor-covenant.org/version/1/3/0/][version]
47
+
48
+ [homepage]: http://contributor-covenant.org
49
+ [version]: http://contributor-covenant.org/version/1/3/0/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in tableau_api.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,27 @@
1
+ Copyright (c) 2016, Civis Analytics
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice, this
8
+ list of conditions and the following disclaimer.
9
+
10
+ * Redistributions in binary form must reproduce the above copyright notice,
11
+ this list of conditions and the following disclaimer in the documentation
12
+ and/or other materials provided with the distribution.
13
+
14
+ * Neither the name of Civis Analytics nor the names of its
15
+ contributors may be used to endorse or promote products derived from
16
+ this software without specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # tableau_api
2
+
3
+ [![Build Status](https://travis-ci.org/civisanalytics/tableau_api.svg?branch=master)](https://travis-ci.org/civisanalytics/tableau_api)
4
+ [![Gem Version](https://badge.fury.io/rb/tableau_api.svg)](http://badge.fury.io/rb/tableau_api)
5
+
6
+ Ruby interface to the Tableau 9.0 API.
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'tableau_api'
14
+ ```
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install tableau_api
23
+
24
+ ## Usage
25
+
26
+ ### Basic Authentication
27
+ ```
28
+ client = TableauApi.new(host: 'https://tableau.domain.tld', site_name: 'Default', username: 'ExampleUsername', password: 'ExamplePassword')
29
+ client.users.create(username: 'baz')
30
+ ```
31
+
32
+ ### Trusted Authentication
33
+ ```
34
+ client = TableauApi.new(host: 'https://tableau.domain.tld', site_name: 'Default', username: 'ExampleUsername')
35
+ client.auth.trusted_ticket
36
+ ```
37
+
38
+ ### Workbooks
39
+ ```
40
+ # find a workbook by name
41
+ workbook = client.workbooks.list.find do |w|
42
+ w['name'] == 'Example Workbook Name'
43
+ end
44
+ ```
45
+
46
+ ## Development
47
+
48
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake` to run the tests and linters. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
49
+
50
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
51
+
52
+ ## Testing
53
+
54
+ ### Docker
55
+
56
+ ```
57
+ docker run -it -d -v $(pwd):/src ruby /bin/bash
58
+ docker exec -it CONTAINER_ID /bin/bash -c "cd /src && bundle && rake"
59
+ ```
60
+
61
+ ### Creating New VCR Cassettes
62
+
63
+ Set the environment variables below to an administrator account, create a tunnel
64
+ to your tableau server on port 2000, then run the commands below.
65
+
66
+ ```
67
+ docker run -it -d \
68
+ -v $(pwd):/src \
69
+ --add-host=docker:$(ifconfig en0 | grep 'inet\b' | cut -d ' ' -f 2) \
70
+ -e TABLEAU_HOST='http://docker:2000' -e TABLEAU_ADMIN_USERNAME -e TABLEAU_ADMIN_PASSWORD \
71
+ ruby /bin/bash
72
+ docker exec -it CONTAINER_ID /bin/bash -c "cd /src && bundle && rake"
73
+ ```
74
+
75
+ ## Contributing
76
+
77
+ Bug reports and pull requests are welcome on GitHub at https://github.com/civisanalytics/tableau_api. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
78
+
79
+ ## License
80
+
81
+ tableau_api is released under the [BSD 3-Clause License](LICENSE.txt).
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ require 'rubocop/rake_task'
4
+
5
+ RuboCop::RakeTask.new(:rubocop)
6
+
7
+ RSpec::Core::RakeTask.new(:spec)
8
+
9
+ task default: [:rubocop, :spec]
data/bin/console ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'tableau_api'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ require 'pry'
10
+ Pry.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,22 @@
1
+ require 'httparty'
2
+ require 'builder'
3
+ require 'net/http/post/multipart'
4
+
5
+ require 'tableau_api/client'
6
+ require 'tableau_api/connection'
7
+ require 'tableau_api/error'
8
+ require 'tableau_api/resources/base'
9
+ require 'tableau_api/resources/auth'
10
+ require 'tableau_api/resources/projects'
11
+ require 'tableau_api/resources/sites'
12
+ require 'tableau_api/resources/users'
13
+ require 'tableau_api/resources/workbooks'
14
+ require 'tableau_api/version'
15
+
16
+ module TableauApi
17
+ class << self
18
+ def new(options = {})
19
+ Client.new(options)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,43 @@
1
+ module TableauApi
2
+ class Client
3
+ attr_reader :host, :username, :password, :site_id, :site_name
4
+
5
+ def initialize(host:, site_name:, username:, password: nil)
6
+ @resources = {}
7
+
8
+ raise 'host is required' if host.to_s.empty?
9
+ @host = host
10
+
11
+ raise 'site_name is required' if site_name.to_s.empty?
12
+ @site_name = site_name
13
+
14
+ raise 'username is required' if username.to_s.empty?
15
+ @username = username
16
+
17
+ @password = password
18
+ end
19
+
20
+ def connection
21
+ @connection ||= Connection.new(self)
22
+ end
23
+
24
+ def self.resources
25
+ {
26
+ auth: TableauApi::Resources::Auth,
27
+ projects: TableauApi::Resources::Projects,
28
+ sites: TableauApi::Resources::Sites,
29
+ users: TableauApi::Resources::Users,
30
+ workbooks: TableauApi::Resources::Workbooks
31
+ }
32
+ end
33
+
34
+ def method_missing(name, *args, &block)
35
+ if self.class.resources.keys.include?(name)
36
+ @resources[name] ||= self.class.resources[name].new(self)
37
+ @resources[name]
38
+ else
39
+ super
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,90 @@
1
+ module TableauApi
2
+ class Connection
3
+ API_VERSION = '2.0'.freeze
4
+
5
+ include HTTParty
6
+
7
+ def initialize(client)
8
+ @client = client
9
+ end
10
+
11
+ def post(path, *args)
12
+ self.class.post("#{@client.host}/#{path}", *args)
13
+ end
14
+
15
+ # if the result is paginated, it will fetch subsequent pages
16
+ # collection can be delimited with a period to do nested hash lookups
17
+ # e.g. objects.object
18
+ def api_get_collection(path, collection, *args)
19
+ Enumerator.new do |enum|
20
+ args[0] = {} unless args[0]
21
+ page_size = (args[0].delete(:page_size) { 100 }).to_i
22
+ page_number = (args[0].delete(:page_number) { 1 }).to_i
23
+
24
+ loop do
25
+ uri = URI::HTTP.build(path: "/#{path}", query: URI.encode_www_form(pageSize: page_size, pageNumber: page_number)).request_uri
26
+
27
+ res = api_get(uri, *args)
28
+ raise TableauError, res if res.code.to_s != '200'
29
+
30
+ # ensure the result is an array because it will not be an array if there is only one element
31
+ [collection.split('.').reduce(res['tsResponse']) { |a, e| a && a[e] }].flatten.compact.each do |obj|
32
+ enum.yield obj
33
+ end
34
+
35
+ break if res['tsResponse']['pagination'].nil?
36
+ break if page_number >= (res['tsResponse']['pagination']['totalAvailable'].to_i / page_size.to_f).ceil
37
+
38
+ page_number += 1
39
+ end
40
+ end
41
+ end
42
+
43
+ def api_get(path, *args)
44
+ api_method(:get, path, *args)
45
+ end
46
+
47
+ def api_post(path, *args)
48
+ api_method(:post, path, *args)
49
+ end
50
+
51
+ def api_put(path, *args)
52
+ api_method(:put, path, *args)
53
+ end
54
+
55
+ def api_post_multipart(path, parts, headers)
56
+ headers = auth_headers(headers)
57
+ headers['Content-Type'] = 'multipart/mixed'
58
+
59
+ uri = URI.parse(url_for(path))
60
+
61
+ req = Net::HTTP::Post::Multipart.new(uri.to_s, parts, headers)
62
+
63
+ Net::HTTP.start(uri.host, uri.port) do |http|
64
+ http.request(req)
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ def api_method(method, path, *args)
71
+ # do not attach auth headers or attempt to signin if we're signing in
72
+ unless path == 'auth/signin'
73
+ args[0] = {} unless args[0]
74
+ args[0][:headers] = auth_headers
75
+ end
76
+ self.class.send(method, url_for(path), *args)
77
+ end
78
+
79
+ def url_for(path)
80
+ "#{@client.host}/api/#{API_VERSION}#{'/' unless path[0] == '/'}#{path}"
81
+ end
82
+
83
+ # will attempt to signin if the token hasn't been loaded
84
+ def auth_headers(headers = {})
85
+ h = headers.dup
86
+ h['X-Tableau-Auth'] = @client.auth.token
87
+ h
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,14 @@
1
+ module TableauApi
2
+ class TableauError < StandardError
3
+ attr_reader :http_response_code, :error_code, :summary, :detail
4
+
5
+ def initialize(net_response)
6
+ @http_response_code = net_response.code
7
+ error = HTTParty::Parser.new(net_response.body, :xml).parse['tsResponse']['error']
8
+ @error_code = error['code']
9
+ @summary = error['summary']
10
+ @detail = error['detail']
11
+ super(summary)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,78 @@
1
+ module TableauApi
2
+ module Resources
3
+ class Auth < Base
4
+ def token
5
+ sign_in unless signed_in?
6
+ @token
7
+ end
8
+
9
+ def site_id
10
+ sign_in unless signed_in?
11
+ @site_id
12
+ end
13
+
14
+ def user_id
15
+ sign_in unless signed_in?
16
+ @user_id
17
+ end
18
+
19
+ def sign_in
20
+ return true if signed_in?
21
+
22
+ request = Builder::XmlMarkup.new.tsRequest do |ts|
23
+ ts.credentials(name: @client.username, password: @client.password) do |cred|
24
+ cred.site(contentUrl: @client.site_name == 'Default' ? '' : @client.site_name)
25
+ end
26
+ end
27
+
28
+ res = @client.connection.api_post('auth/signin', body: request)
29
+
30
+ return false unless res.code == 200
31
+
32
+ @token = res['tsResponse']['credentials']['token']
33
+ @site_id = res['tsResponse']['credentials']['site']['id']
34
+ @user_id = res['tsResponse']['credentials']['user']['id']
35
+
36
+ true
37
+ end
38
+
39
+ def signed_in?
40
+ !@token.nil?
41
+ end
42
+
43
+ def sign_out
44
+ return true unless signed_in?
45
+
46
+ res = @client.connection.api_post('auth/signout', body: nil)
47
+
48
+ # consider 401 to be successful since signing out with an expired
49
+ # token fails, but we can still consider the user signed out
50
+ return false unless res.code == 204 || res.code == 401
51
+
52
+ @token = nil
53
+ @site_id = nil
54
+ @user_id = nil
55
+
56
+ true
57
+ end
58
+
59
+ def trusted_ticket
60
+ body = {
61
+ username: @client.username,
62
+ target_site: @client.site_name == 'Default' ? '' : @client.site_name
63
+ }
64
+
65
+ begin
66
+ res = @client.connection.post('trusted', body: body, limit: 1)
67
+ rescue HTTParty::RedirectionTooDeep
68
+ # redirects if the site_name is invalid
69
+ res = false
70
+ end
71
+
72
+ return unless res && res.code == 200 && res.body != '-1'
73
+
74
+ res.body
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,9 @@
1
+ module TableauApi
2
+ module Resources
3
+ class Base
4
+ def initialize(client)
5
+ @client = client
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,20 @@
1
+ module TableauApi
2
+ module Resources
3
+ class Projects < Base
4
+ def create(name:, description: '')
5
+ request = Builder::XmlMarkup.new.tsRequest do |ts|
6
+ ts.project(name: name, description: description)
7
+ end
8
+
9
+ res = @client.connection.api_post("sites/#{@client.auth.site_id}/projects", body: request)
10
+
11
+ res['tsResponse']['project'] if res.code == 201
12
+ end
13
+
14
+ def list
15
+ url = "sites/#{@client.auth.site_id}/projects"
16
+ @client.connection.api_get_collection(url, 'projects.project')
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,9 @@
1
+ module TableauApi
2
+ module Resources
3
+ class Sites < Base
4
+ def list
5
+ @client.connection.api_get_collection('sites', 'sites.site')
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,32 @@
1
+ module TableauApi
2
+ module Resources
3
+ class Users < Base
4
+ SITE_ROLES = %w(
5
+ Interactor
6
+ Publisher
7
+ SiteAdministrator
8
+ Unlicensed
9
+ UnlicensedWithPublish
10
+ Viewer
11
+ ViewerWithPublish
12
+ ).freeze
13
+
14
+ def create(username:, site_role: 'Viewer')
15
+ raise 'invalid site_role' unless SITE_ROLES.include? site_role
16
+
17
+ request = Builder::XmlMarkup.new.tsRequest do |ts|
18
+ ts.user(name: username, siteRole: site_role)
19
+ end
20
+
21
+ res = @client.connection.api_post("sites/#{@client.auth.site_id}/users", body: request)
22
+
23
+ res['tsResponse']['user'] if res.code == 201
24
+ end
25
+
26
+ def list
27
+ url = "sites/#{@client.auth.site_id}/users"
28
+ @client.connection.api_get_collection(url, 'users.user')
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,107 @@
1
+ require 'zip'
2
+
3
+ module TableauApi
4
+ module Resources
5
+ class Workbooks < Base
6
+ def version(file)
7
+ version = nil
8
+
9
+ if File.exist?(file) && File.extname(file) == '.twbx'
10
+ Zip::File.open(file) do |zip_file|
11
+ entry = zip_file.glob('*.twb').first
12
+ version = HTTParty::Parser.new(entry.get_input_stream.read, :xml).parse['workbook']['version']
13
+ end
14
+ end
15
+
16
+ version
17
+ end
18
+
19
+ # rubocop:disable Metrics/ParameterLists
20
+ def publish(name:, project_id:, file:, overwrite: false, show_tabs: false, connection_username: nil, connection_password: nil, connection_embed: false)
21
+ request = Builder::XmlMarkup.new.tsRequest do |ts|
22
+ ts.workbook(name: name, showTabs: show_tabs) do |wb|
23
+ wb.project(id: project_id)
24
+ wb.connectionCredentials(name: connection_username, password: connection_password, embed: connection_embed) if connection_username
25
+ end
26
+ end
27
+
28
+ query = URI.encode_www_form([['overwrite', overwrite]])
29
+ path = "sites/#{@client.auth.site_id}/workbooks?#{query}"
30
+
31
+ parts = {
32
+ 'request_payload' => request,
33
+ 'tableau_workbook' => UploadIO.new(file, 'application/octet-stream')
34
+ }
35
+
36
+ headers = {
37
+ parts: {
38
+ 'request_payload' => { 'Content-Type' => 'text/xml' },
39
+ 'tableau_workbook' => { 'Content-Type' => 'application/octet-string' }
40
+ }
41
+ }
42
+
43
+ res = @client.connection.api_post_multipart(path, parts, headers)
44
+
45
+ return HTTParty::Parser.new(res.body, :xml).parse['tsResponse']['workbook'] if res.code == '201'
46
+
47
+ raise TableauError, res
48
+ end
49
+ # rubocop:enable Metrics/ParameterLists
50
+
51
+ CAPABILITIES = %w(
52
+ AddComment ChangeHierarchy ChangePermissions Delete ExportData ExportImage ExportXml
53
+ Filter Read ShareView ViewComments ViewUnderlyingData WebAuthoring Write
54
+ ).freeze
55
+
56
+ # capabilities is a hash of symbol keys to booleans { Read: true, ChangePermissions: false }
57
+ def permissions(workbook_id:, user_id:, capabilities:)
58
+ request = Builder::XmlMarkup.new.tsRequest do |ts|
59
+ ts.permissions do |p|
60
+ p.workbook(id: workbook_id)
61
+ p.granteeCapabilities do |gc|
62
+ gc.user(id: user_id)
63
+ gc.capabilities do |c|
64
+ capabilities.each do |k, v|
65
+ k = k.to_s
66
+ raise "invalid capability #{k}" unless CAPABILITIES.include? k
67
+ c.capability(name: k, mode: v ? 'Allow' : 'Deny')
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ res = @client.connection.api_put("sites/#{@client.auth.site_id}/workbooks/#{workbook_id}/permissions", body: request)
75
+
76
+ res.code == 200
77
+ end
78
+
79
+ def update(workbook_id:, owner_user_id:)
80
+ request = Builder::XmlMarkup.new.tsRequest do |ts|
81
+ ts.workbook(id: workbook_id) do |w|
82
+ w.owner(id: owner_user_id)
83
+ end
84
+ end
85
+
86
+ res = @client.connection.api_put("sites/#{@client.auth.site_id}/workbooks/#{workbook_id}", body: request)
87
+
88
+ res.code == 200
89
+ end
90
+
91
+ def find(workbook_id)
92
+ res = @client.connection.api_get("sites/#{@client.auth.site_id}/workbooks/#{workbook_id}")
93
+ res['tsResponse']['workbook'] if res.code == 200
94
+ end
95
+
96
+ def views(workbook_id)
97
+ url = "sites/#{@client.auth.site_id}/workbooks/#{workbook_id}/views"
98
+ @client.connection.api_get_collection(url, 'views.view')
99
+ end
100
+
101
+ def list
102
+ url = "sites/#{@client.auth.site_id}/users/#{@client.auth.user_id}/workbooks"
103
+ @client.connection.api_get_collection(url, 'workbooks.workbook')
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,3 @@
1
+ module TableauApi
2
+ VERSION = '1.0.0'.freeze
3
+ end
@@ -0,0 +1,35 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'tableau_api/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'tableau_api'
8
+ spec.version = TableauApi::VERSION
9
+ spec.authors = ['Christopher Manning']
10
+ spec.email = ['opensource@civisanalytics.com']
11
+
12
+ spec.summary = 'Ruby interface to the Tableau 9.0 API.'
13
+ spec.homepage = 'https://github.com/civisanalytics/tableau_api'
14
+ spec.license = 'BSD-3-Clause'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = 'exe'
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ['lib']
20
+ spec.required_ruby_version = '~> 2.1'
21
+
22
+ spec.add_dependency 'httparty', '~> 0.13'
23
+ spec.add_dependency 'builder', '~> 3.2'
24
+ spec.add_dependency 'multipart-post', '~> 2.0'
25
+ spec.add_dependency 'rubyzip', '~> 1.0'
26
+
27
+ spec.add_development_dependency 'bundler', '~> 1.12'
28
+ spec.add_development_dependency 'rake', '~> 11.0'
29
+ spec.add_development_dependency 'rspec', '~> 3.4'
30
+ spec.add_development_dependency 'vcr', '~> 3.0'
31
+ spec.add_development_dependency 'webmock', '~> 1.24'
32
+ spec.add_development_dependency 'pry', '~> 0.10'
33
+ spec.add_development_dependency 'pry-byebug', '~> 3.4'
34
+ spec.add_development_dependency 'rubocop', '~> 0.40'
35
+ end
metadata ADDED
@@ -0,0 +1,237 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tableau_api
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Christopher Manning
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-06-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: httparty
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.13'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.13'
27
+ - !ruby/object:Gem::Dependency
28
+ name: builder
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: multipart-post
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubyzip
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: bundler
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.12'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.12'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '11.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '11.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '3.4'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '3.4'
111
+ - !ruby/object:Gem::Dependency
112
+ name: vcr
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '3.0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '3.0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: webmock
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '1.24'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '1.24'
139
+ - !ruby/object:Gem::Dependency
140
+ name: pry
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '0.10'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '0.10'
153
+ - !ruby/object:Gem::Dependency
154
+ name: pry-byebug
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '3.4'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '3.4'
167
+ - !ruby/object:Gem::Dependency
168
+ name: rubocop
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: '0.40'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: '0.40'
181
+ description:
182
+ email:
183
+ - opensource@civisanalytics.com
184
+ executables: []
185
+ extensions: []
186
+ extra_rdoc_files: []
187
+ files:
188
+ - ".gitignore"
189
+ - ".rspec"
190
+ - ".rubocop.yml"
191
+ - ".ruby-version"
192
+ - ".travis.yml"
193
+ - CHANGELOG.md
194
+ - CODE_OF_CONDUCT.md
195
+ - Gemfile
196
+ - LICENSE.txt
197
+ - README.md
198
+ - Rakefile
199
+ - bin/console
200
+ - bin/setup
201
+ - lib/tableau_api.rb
202
+ - lib/tableau_api/client.rb
203
+ - lib/tableau_api/connection.rb
204
+ - lib/tableau_api/error.rb
205
+ - lib/tableau_api/resources/auth.rb
206
+ - lib/tableau_api/resources/base.rb
207
+ - lib/tableau_api/resources/projects.rb
208
+ - lib/tableau_api/resources/sites.rb
209
+ - lib/tableau_api/resources/users.rb
210
+ - lib/tableau_api/resources/workbooks.rb
211
+ - lib/tableau_api/version.rb
212
+ - tableau_api.gemspec
213
+ homepage: https://github.com/civisanalytics/tableau_api
214
+ licenses:
215
+ - BSD-3-Clause
216
+ metadata: {}
217
+ post_install_message:
218
+ rdoc_options: []
219
+ require_paths:
220
+ - lib
221
+ required_ruby_version: !ruby/object:Gem::Requirement
222
+ requirements:
223
+ - - "~>"
224
+ - !ruby/object:Gem::Version
225
+ version: '2.1'
226
+ required_rubygems_version: !ruby/object:Gem::Requirement
227
+ requirements:
228
+ - - ">="
229
+ - !ruby/object:Gem::Version
230
+ version: '0'
231
+ requirements: []
232
+ rubyforge_project:
233
+ rubygems_version: 2.4.5.1
234
+ signing_key:
235
+ specification_version: 4
236
+ summary: Ruby interface to the Tableau 9.0 API.
237
+ test_files: []