├── .gitignore
├── .rspec
├── .rubocop.yml
├── .rubocop_todo.yml
├── .ruby-version
├── .travis.yml
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── bin
├── console
└── setup
├── lib
├── tableau_api.rb
└── tableau_api
│ ├── client.rb
│ ├── connection.rb
│ ├── error.rb
│ ├── resources
│ ├── auth.rb
│ ├── base.rb
│ ├── datasources.rb
│ ├── groups.rb
│ ├── jobs.rb
│ ├── projects.rb
│ ├── sites.rb
│ ├── users.rb
│ └── workbooks.rb
│ └── version.rb
├── spec
├── client_spec.rb
├── fixtures
│ ├── vcr_cassettes
│ │ ├── auth.yml
│ │ ├── datasources.yml
│ │ ├── groups.yml
│ │ ├── jobs.yml
│ │ ├── projects.yml
│ │ ├── sites.yml
│ │ ├── users.yml
│ │ └── workbooks.yml
│ └── workbooks
│ │ └── test.twbx
├── resources
│ ├── auth_spec.rb
│ ├── datasources_spec.rb
│ ├── groups_spec.rb
│ ├── jobs_spec.rb
│ ├── projects_spec.rb
│ ├── sites_spec.rb
│ ├── users_spec.rb
│ └── workbooks_spec.rb
├── spec_helper.rb
└── tableau_api_spec.rb
└── tableau_api.gemspec
/.gitignore:
--------------------------------------------------------------------------------
1 | /.bundle/
2 | /.yardoc
3 | /Gemfile.lock
4 | /_yardoc/
5 | /coverage/
6 | /doc/
7 | /pkg/
8 | /spec/reports/
9 | /tmp/
10 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --format documentation
2 | --color
3 |
--------------------------------------------------------------------------------
/.rubocop.yml:
--------------------------------------------------------------------------------
1 | inherit_from: .rubocop_todo.yml
2 |
3 | AllCops:
4 | TargetRubyVersion: 2.7
5 | NewCops: enable
6 |
7 | Gemspec/RequiredRubyVersion:
8 | Enabled: false
9 |
10 | Layout/LineLength:
11 | Max: 170
12 |
13 | Style/Documentation:
14 | Enabled: false
15 |
16 | Metrics/ClassLength:
17 | Max: 200
18 |
19 | Metrics/AbcSize:
20 | Max: 40
21 |
22 | Metrics/MethodLength:
23 | Max: 30
24 |
25 | Metrics/BlockLength:
26 | Exclude:
27 | - ./tableau_api.gemspec
28 | - ./spec/resources/workbooks_spec.rb
29 |
--------------------------------------------------------------------------------
/.rubocop_todo.yml:
--------------------------------------------------------------------------------
1 | # This configuration was generated by
2 | # `rubocop --auto-gen-config`
3 | # on 2019-08-21 17:54:45 +0000 using RuboCop version 0.49.1.
4 | # The point is for the user to remove these configuration records
5 | # one by one as the offenses are removed from the code base.
6 | # Note that changes in the inspected code, or installation of new
7 | # versions of RuboCop, may require this file to be generated again.
8 |
9 | # Offense count: 13
10 | # Configuration parameters: CountComments, ExcludedMethods.
11 | Metrics/BlockLength:
12 | Max: 316
13 |
--------------------------------------------------------------------------------
/.ruby-version:
--------------------------------------------------------------------------------
1 | 3.2.1
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | cache: bundler
2 | branches:
3 | only:
4 | - master
5 | language: ruby
6 | rvm:
7 | - 3.2.1
8 | - 3.1.3
9 | - 2.7.0
10 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to this project will be documented in this file.
4 | This project adheres to [Semantic Versioning](http://semver.org/).
5 |
6 | ## upcoming
7 |
8 | ### Changed
9 |
10 | ## [5.0.0] - 2023-03-23
11 |
12 | - Added support for Ruby 3
13 | - Bumped dev Ruby version to 3.2.1
14 | - Bumped minimum required Ruby version to 2.7.0
15 | - Bumped Rake version for development to 12.3.3
16 | - Bumped Rubocop version for development to 1.48.1
17 | - Bumped vcr version for development to 6.0
18 | - Bumped Travis matrix Ruby versions to 3.2.1, 3.0.5, and 2.7.0
19 | - Added Rubygems MFA requirement
20 |
21 | ## [4.1.0] - 2022-04-13
22 |
23 | - Specify major version number of ruby in Docker testing command in README
24 | - Add support for CreateRefreshMetrics and RunExplainData workbook capabilities
25 |
26 | ## [4.0.0] - 2020-11-30
27 |
28 | ### Changed/Fixed
29 |
30 | - Changed interface to Connection#api_get_collection to properly merge a string
31 | query with the pagination parameters instead of overwriting the pagination
32 | params. This means you can't pass a Hash `query` parameter to `Jobs#list`
33 | anymore, but job filtering now works properly because colons in the filter
34 | will not be URL-encoded.
35 | - Avoid mutating extra argument hash to endpoint methods
36 |
37 |
38 | ## [3.0.0] - 2020-11-11
39 |
40 | ### Added
41 |
42 | - Added Jobs resource
43 |
44 |
45 | ### Changed
46 |
47 | - Updated to API version 3.1
48 | - This is a breaking change for site roles:
49 | https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_concepts_new_site_roles.htm
50 | - Include full error detail in TableauError message
51 |
52 |
53 | ## [2.0.0] - 2019-08-29
54 |
55 | - Updated to API version 2.8, compatible with Tableau Server >= 10.5
56 | - Add `refresh` and `image_preview` methods for workbooks
57 | - Bumped the Ruby versions in the Travis matrix build to 2.3.4, 2.2.7, and
58 |
59 | ## [1.1.2] - 2017-05-24
60 |
61 | ### Fixed
62 |
63 | - Replaced corrupt `.gem` upload to RubyGems
64 |
65 | ## [1.1.1] - 2017-05-24 - [YANKED]
66 |
67 | ### Added
68 |
69 | - Added Ruby 2.4.1 to the Travis matrix build
70 |
71 | ### Changed
72 |
73 | - Bumped the Ruby versions in the Travis matrix build to 2.3.4, 2.2.7, and
74 | 2.1.10
75 | - Bumped the Ruby version for development to 2.4.1
76 | - Bumped the RSpec version for development to 3.6
77 | - Bumped the WebMock version for development to 3.0
78 | - Updated the authors
79 |
80 | ### Fixed
81 |
82 | - Updated `TableauApi::VERSION`
83 |
84 | ## [1.1.0] - 2017-03-02
85 |
86 | ### Added
87 |
88 | - [#2](https://github.com/civisanalytics/tableau_api/pull/2)
89 | - `TableauApi::Resources::Groups` added to support API calls for
90 | adding/deleting/updating groups.
91 | - `TableauApi::Resources::Workbook#remove_permissions` added, including
92 | support for user and group permissions.
93 | - `TableauApi::Resources::Workbook#add_permissions` supports group
94 | permissions.
95 | - [#6](https://github.com/civisanalytics/tableau_api/pull/6)
96 | Added `Users#update_user`
97 | - [#7](https://github.com/civisanalytics/tableau_api/pull/7)
98 | Added `Sites#create` and `Sites#delete`
99 |
100 | ### Changed
101 |
102 | - [#3](https://github.com/civisanalytics/tableau_api/pull/3)
103 | `TableauApi::Resources::Workbook#permissions` now returns existing permissions
104 | instead of adding new permissions. New permissions can be added with
105 | `TableauApi::Resources::Workbook#add_permissions`.
106 |
107 | ### Fixed
108 |
109 | - [#4](https://github.com/civisanalytics/tableau_api/pull/4)
110 | Always parse/return workbook permissions as an array
111 |
112 | ## [1.0.0] - 2016-06-06
113 |
114 | - Initial Release
115 |
116 | [Unreleased]: https://github.com/civisanalytics/tableau_api/compare/v1.1.2...HEAD
117 | [1.1.2]: https://github.com/civisanalytics/tableau_api/compare/v1.1.1...v1.1.2
118 | [1.1.1]: https://github.com/civisanalytics/tableau_api/compare/v1.1.0...v1.1.1
119 | [1.1.0]: https://github.com/civisanalytics/tableau_api/compare/v1.0.0...v1.1.0
120 | [1.0.0]: https://github.com/civisanalytics/tableau_api/tree/v1.0.0
121 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
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/
50 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | source 'https://rubygems.org'
4 |
5 | # Specify your gem's dependencies in tableau_api.gemspec
6 | gemspec
7 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
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.
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # tableau_api
2 |
3 | [](https://travis-ci.org/civisanalytics/tableau_api)
4 | [](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:2 /bin/bash
58 | docker exec -it CONTAINER_ID /bin/bash -c "cd /src && bundle && rake"
59 | ```
60 |
61 | ### Creating New VCR Cassettes
62 |
63 | Cassettes should be self-contained, generated by a single spec file
64 | run in defined order. To make changes to specs, it's best to delete a whole cassette
65 | and rerun the whole spec file, except for auth.yml, since it could be difficult to
66 | generate a trusted ticket from a non-trusted host.
67 |
68 | To regenerate all the the cassettes, you'll first need to create the following on the Tableau server:
69 | * *Site*: Default
70 | * *Site*: TestSite
71 | * *Datasource*: test (this might need to be created from Tableau Desktop)
72 | * *Username*: test_test
73 |
74 | And delete the following if they exist:
75 | * *Group*: testgroup (probably under TestSite)
76 | * *Project*: test_project (probably under Default)
77 | * *Site*: Test Site 2
78 | * *User*: test (probably under TestSite)
79 | * *Workbook*: test
80 | * *Workbook*: testpublish
81 |
82 | Set the environment variables below to an administrator account and your Tableau Server hostname.
83 |
84 | Then run the commands below:
85 |
86 | ```
87 | docker run -it -d \
88 | -v $(pwd):/src \
89 | -e TABLEAU_HOST -e TABLEAU_ADMIN_USERNAME -e TABLEAU_ADMIN_PASSWORD \
90 | ruby /bin/bash
91 | docker exec -it CONTAINER_ID /bin/bash -c "cd /src && bundle && rake"
92 | ```
93 |
94 | ## Contributing
95 |
96 | 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.
97 |
98 | ## License
99 |
100 | tableau_api is released under the [BSD 3-Clause License](LICENSE.txt).
101 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'bundler/gem_tasks'
4 | require 'rspec/core/rake_task'
5 | require 'rubocop/rake_task'
6 |
7 | RuboCop::RakeTask.new(:rubocop)
8 |
9 | RSpec::Core::RakeTask.new(:spec)
10 |
11 | task default: %i[rubocop spec]
12 |
--------------------------------------------------------------------------------
/bin/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | require 'bundler/setup'
5 | require 'tableau_api'
6 |
7 | # You can add fixtures and/or initialization code here to make experimenting
8 | # with your gem easier. You can also use a different console, if you like.
9 |
10 | require 'pry'
11 | Pry.start
12 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
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
9 |
--------------------------------------------------------------------------------
/lib/tableau_api.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'httparty'
4 | require 'builder'
5 | require 'net/http/post/multipart'
6 |
7 | require 'tableau_api/version'
8 |
9 | require 'tableau_api/client'
10 | require 'tableau_api/connection'
11 | require 'tableau_api/error'
12 | require 'tableau_api/resources/base'
13 | require 'tableau_api/resources/auth'
14 | require 'tableau_api/resources/projects'
15 | require 'tableau_api/resources/sites'
16 | require 'tableau_api/resources/users'
17 | require 'tableau_api/resources/groups'
18 | require 'tableau_api/resources/workbooks'
19 | require 'tableau_api/resources/datasources'
20 | require 'tableau_api/resources/jobs'
21 |
22 | module TableauApi
23 | class << self
24 | def new(**options)
25 | Client.new(**options)
26 | end
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/lib/tableau_api/client.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module TableauApi
4 | class Client
5 | attr_reader :host, :username, :password, :site_id, :site_name
6 |
7 | def initialize(host:, site_name:, username:, password: nil)
8 | @resources = {}
9 |
10 | raise 'host is required' if host.to_s.empty?
11 |
12 | @host = host
13 |
14 | raise 'site_name is required' if site_name.to_s.empty?
15 |
16 | @site_name = site_name
17 |
18 | raise 'username is required' if username.to_s.empty?
19 |
20 | @username = username
21 |
22 | @password = password
23 | end
24 |
25 | def connection
26 | @connection ||= Connection.new(self)
27 | end
28 |
29 | def self.resources
30 | {
31 | auth: TableauApi::Resources::Auth,
32 | projects: TableauApi::Resources::Projects,
33 | sites: TableauApi::Resources::Sites,
34 | users: TableauApi::Resources::Users,
35 | groups: TableauApi::Resources::Groups,
36 | workbooks: TableauApi::Resources::Workbooks,
37 | datasources: TableauApi::Resources::Datasources,
38 | jobs: TableauApi::Resources::Jobs
39 | }
40 | end
41 |
42 | def method_missing(name, *args, &block)
43 | if self.class.resources.keys.include?(name)
44 | @resources[name] ||= self.class.resources[name].new(self)
45 | @resources[name]
46 | else
47 | super
48 | end
49 | end
50 |
51 | def respond_to_missing?(name, include_private = false)
52 | self.class.resources.keys.include?(name) || super
53 | end
54 | end
55 | end
56 |
--------------------------------------------------------------------------------
/lib/tableau_api/connection.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module TableauApi
4 | class Connection
5 | API_VERSION = '3.1'
6 |
7 | include HTTParty
8 | headers 'User-Agent' => "tableau_api/#{::TableauApi::VERSION} Ruby/#{RUBY_VERSION}"
9 |
10 | def initialize(client)
11 | @client = client
12 | end
13 |
14 | def post(path, **kwargs)
15 | self.class.post("#{@client.host}/#{path}", kwargs)
16 | end
17 |
18 | # if the result is paginated, it will fetch subsequent pages
19 | # collection can be delimited with a period to do nested hash lookups
20 | # e.g. objects.object
21 | # rubocop:disable Metrics/CyclomaticComplexity
22 | def api_get_collection(path, collection, page_number: 1, page_size: 100, **kwargs)
23 | Enumerator.new do |enum|
24 | loop do
25 | query = kwargs.fetch(:query, '')
26 | query += '&' unless query.empty?
27 | query += "pageSize=#{page_size}&pageNumber=#{page_number}"
28 | new_kwargs = kwargs.merge(query: query)
29 |
30 | res = api_get(path, **new_kwargs)
31 | raise TableauError, res if res.code.to_s != '200'
32 |
33 | # ensure the result is an array because it will not be an array if there is only one element
34 | [collection.split('.').reduce(res['tsResponse']) { |acc, elem| acc && acc[elem] }].flatten.compact.each do |obj|
35 | enum.yield obj
36 | end
37 |
38 | break if res['tsResponse']['pagination'].nil?
39 | break if page_number >= (res['tsResponse']['pagination']['totalAvailable'].to_i / page_size.to_f).ceil
40 |
41 | page_number += 1
42 | end
43 | end
44 | end
45 | # rubocop:enable Metrics/CyclomaticComplexity
46 |
47 | def api_get(path, **kwargs)
48 | api_method(:get, path, kwargs)
49 | end
50 |
51 | def api_post(path, **kwargs)
52 | new_headers = kwargs.fetch(:headers, {}).merge('Content-Type' => 'application/xml')
53 | api_method(:post, path, kwargs.merge(headers: new_headers))
54 | end
55 |
56 | def api_put(path, **kwargs)
57 | new_headers = kwargs.fetch(:headers, {}).merge('Content-Type' => 'application/xml')
58 | api_method(:put, path, kwargs.merge(headers: new_headers))
59 | end
60 |
61 | def api_delete(path, **kwargs)
62 | api_method(:delete, path, kwargs)
63 | end
64 |
65 | def api_post_multipart(path, parts, headers)
66 | headers = auth_headers(headers)
67 | headers['Content-Type'] = 'multipart/mixed'
68 |
69 | uri = URI.parse(url_for(path))
70 |
71 | req = Net::HTTP::Post::Multipart.new(uri.to_s, parts, headers)
72 |
73 | Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
74 | http.request(req)
75 | end
76 | end
77 |
78 | private
79 |
80 | def api_method(method, path, kwargs)
81 | # do not attach auth headers or attempt to signin if we're signing in
82 | new_headers = auth_headers(kwargs.fetch(:headers, {})) unless path == 'auth/signin'
83 | self.class.public_send(method, url_for(path), kwargs.merge(headers: new_headers))
84 | end
85 |
86 | def url_for(path)
87 | "#{@client.host}/api/#{API_VERSION}#{'/' unless path[0] == '/'}#{path}"
88 | end
89 |
90 | # will attempt to signin if the token hasn't been loaded
91 | def auth_headers(headers = {})
92 | h = headers.dup
93 | h['X-Tableau-Auth'] = @client.auth.token
94 | h
95 | end
96 | end
97 | end
98 |
--------------------------------------------------------------------------------
/lib/tableau_api/error.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module TableauApi
4 | class TableauError < StandardError
5 | attr_reader :http_response_code, :error_code, :summary, :detail
6 |
7 | def initialize(net_response)
8 | @http_response_code = net_response.code
9 | error = HTTParty::Parser.new(net_response.body, :xml).parse['tsResponse']['error']
10 | @error_code = error['code']
11 | @summary = error['summary']
12 | @detail = error['detail']
13 | super("#{error_code}: #{summary}; #{detail}")
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/lib/tableau_api/resources/auth.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module TableauApi
4 | module Resources
5 | class Auth < Base
6 | def token
7 | sign_in unless signed_in?
8 | @token
9 | end
10 |
11 | def site_id
12 | sign_in unless signed_in?
13 | @site_id
14 | end
15 |
16 | def user_id
17 | sign_in unless signed_in?
18 | @user_id
19 | end
20 |
21 | def sign_in
22 | return true if signed_in?
23 |
24 | request = Builder::XmlMarkup.new.tsRequest do |ts|
25 | ts.credentials(name: @client.username, password: @client.password) do |cred|
26 | cred.site(contentUrl: @client.site_name == 'Default' ? '' : @client.site_name)
27 | end
28 | end
29 |
30 | res = @client.connection.api_post('auth/signin', body: request)
31 |
32 | return false unless res.code == 200
33 |
34 | @token = res['tsResponse']['credentials']['token']
35 | @site_id = res['tsResponse']['credentials']['site']['id']
36 | @user_id = res['tsResponse']['credentials']['user']['id']
37 |
38 | true
39 | end
40 |
41 | def signed_in?
42 | !@token.nil?
43 | end
44 |
45 | def sign_out
46 | return true unless signed_in?
47 |
48 | res = @client.connection.api_post('auth/signout', body: nil)
49 |
50 | # consider 401 to be successful since signing out with an expired
51 | # token fails, but we can still consider the user signed out
52 | return false unless res.code == 204 || res.code == 401
53 |
54 | @token = nil
55 | @site_id = nil
56 | @user_id = nil
57 |
58 | true
59 | end
60 |
61 | def trusted_ticket
62 | body = {
63 | username: @client.username,
64 | target_site: @client.site_name == 'Default' ? '' : @client.site_name
65 | }
66 |
67 | begin
68 | res = @client.connection.post('trusted', body: body, limit: 1)
69 | rescue HTTParty::RedirectionTooDeep
70 | # redirects if the site_name is invalid
71 | res = false
72 | end
73 |
74 | return unless res && res.code == 200 && res.body != '-1'
75 |
76 | res.body
77 | end
78 | end
79 | end
80 | end
81 |
--------------------------------------------------------------------------------
/lib/tableau_api/resources/base.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module TableauApi
4 | module Resources
5 | class Base
6 | def initialize(client)
7 | @client = client
8 | end
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/lib/tableau_api/resources/datasources.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module TableauApi
4 | module Resources
5 | class Datasources < Base
6 | def list
7 | url = "sites/#{@client.auth.site_id}/datasources"
8 | @client.connection.api_get_collection(url, 'datasources.datasource')
9 | end
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/lib/tableau_api/resources/groups.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module TableauApi
4 | module Resources
5 | class Groups < Base
6 | def create(name:, default_site_role: 'Viewer')
7 | raise 'invalid default_site_role' unless Users::SITE_ROLES.include? default_site_role
8 |
9 | request = Builder::XmlMarkup.new.tsRequest do |ts|
10 | ts.group(name: name, defaultSiteRole: default_site_role)
11 | end
12 |
13 | res = @client.connection.api_post("sites/#{@client.auth.site_id}/groups", body: request)
14 |
15 | res['tsResponse']['group'] if res.code == 201
16 | end
17 |
18 | def list
19 | url = "sites/#{@client.auth.site_id}/groups"
20 | @client.connection.api_get_collection(url, 'groups.group')
21 | end
22 |
23 | def users(group_id:)
24 | url = "sites/#{@client.auth.site_id}/groups/#{group_id}/users"
25 | @client.connection.api_get_collection(url, 'users.user')
26 | end
27 |
28 | def add_user(group_id:, user_id:)
29 | request = Builder::XmlMarkup.new.tsRequest do |ts|
30 | ts.user(id: user_id)
31 | end
32 |
33 | res = @client.connection.api_post("sites/#{@client.auth.site_id}/groups/#{group_id}/users", body: request)
34 |
35 | res.code == 200
36 | end
37 |
38 | def remove_user(group_id:, user_id:)
39 | res = @client.connection.api_delete("sites/#{@client.auth.site_id}/groups/#{group_id}/users/#{user_id}")
40 |
41 | res.code == 204
42 | end
43 | end
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/lib/tableau_api/resources/jobs.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module TableauApi
4 | module Resources
5 | class Jobs < Base
6 | def list(params = {})
7 | url = "sites/#{@client.auth.site_id}/jobs"
8 | @client.connection.api_get_collection(url, 'backgroundJobs.backgroundJob', **params)
9 | end
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/lib/tableau_api/resources/projects.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module TableauApi
4 | module Resources
5 | class Projects < Base
6 | def create(name:, description: '')
7 | request = Builder::XmlMarkup.new.tsRequest do |ts|
8 | ts.project(name: name, description: description)
9 | end
10 |
11 | res = @client.connection.api_post("sites/#{@client.auth.site_id}/projects", body: request)
12 |
13 | res['tsResponse']['project'] if res.code == 201
14 | end
15 |
16 | def list
17 | url = "sites/#{@client.auth.site_id}/projects"
18 | @client.connection.api_get_collection(url, 'projects.project')
19 | end
20 | end
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/lib/tableau_api/resources/sites.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module TableauApi
4 | module Resources
5 | class Sites < Base
6 | def list
7 | @client.connection.api_get_collection('sites', 'sites.site')
8 | end
9 |
10 | def create(name:, content_url:, admin_mode: nil, num_users: nil, storage_quota: nil)
11 | # required parameters
12 | request_hash = {
13 | name: name,
14 | contentUrl: content_url
15 | }
16 | # optional parameters
17 | request_hash[:admin_mode] = admin_mode if admin_mode
18 | request_hash[:num_users] = num_users if num_users
19 | request_hash[:storage_quota] = storage_quota if storage_quota
20 |
21 | request = Builder::XmlMarkup.new.tsRequest do |ts|
22 | ts.site(request_hash)
23 | end
24 |
25 | res = @client.connection.api_post('sites', body: request)
26 |
27 | return res['tsResponse']['site'] if res.code == 201
28 |
29 | raise TableauError, res
30 | end
31 |
32 | def delete(site_id:)
33 | res = @client.connection.api_delete("sites/#{site_id}")
34 | return true if res.code == 204
35 |
36 | raise TableauError, res
37 | end
38 | end
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/lib/tableau_api/resources/users.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module TableauApi
4 | module Resources
5 | class Users < Base
6 | SITE_ROLES = %w[
7 | Creator
8 | Explorer
9 | ExplorerCanPublish
10 | SiteAdministratorCreator
11 | SiteAdministratorExplorer
12 | ServerAdministrator
13 | Unlicensed
14 | Viewer
15 | ].freeze
16 |
17 | def create(username:, site_role: 'Viewer')
18 | raise 'invalid site_role' unless SITE_ROLES.include? site_role
19 |
20 | request = Builder::XmlMarkup.new.tsRequest do |ts|
21 | ts.user(name: username, siteRole: site_role)
22 | end
23 |
24 | res = @client.connection.api_post("sites/#{@client.auth.site_id}/users", body: request)
25 |
26 | res['tsResponse']['user'] if res.code == 201
27 | end
28 |
29 | def list
30 | url = "sites/#{@client.auth.site_id}/users"
31 | @client.connection.api_get_collection(url, 'users.user')
32 | end
33 |
34 | def update_user(user_id:, site_role:)
35 | raise 'invalid site_role' unless SITE_ROLES.include? site_role
36 |
37 | res = @client.connection.api_get("sites/#{@client.auth.site_id}/users/#{user_id}")
38 |
39 | raise 'failed to find user' if res.code != 200
40 |
41 | user = res['tsResponse']['user']
42 |
43 | request = Builder::XmlMarkup.new.tsRequest do |ts|
44 | ts.user(name: user['name'], siteRole: site_role)
45 | end
46 |
47 | res = @client.connection.api_put("sites/#{@client.auth.site_id}/users/#{user_id}", body: request)
48 |
49 | res['tsResponse']['user'] if res.code == 200
50 | end
51 | end
52 | end
53 | end
54 |
--------------------------------------------------------------------------------
/lib/tableau_api/resources/workbooks.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'zip'
4 |
5 | module TableauApi
6 | module Resources
7 | class Workbooks < Base
8 | def version(file)
9 | version = nil
10 |
11 | if File.exist?(file) && File.extname(file) == '.twbx'
12 | Zip::File.open(file) do |zip_file|
13 | entry = zip_file.glob('*.twb').first
14 | version = HTTParty::Parser.new(entry.get_input_stream.read, :xml).parse['workbook']['version']
15 | end
16 | end
17 |
18 | version
19 | end
20 |
21 | # rubocop:disable Metrics/ParameterLists
22 | def publish(name:, project_id:, file:, overwrite: false, show_tabs: false, connection_username: nil, connection_password: nil, connection_embed: false)
23 | request = Builder::XmlMarkup.new.tsRequest do |ts|
24 | ts.workbook(name: name, showTabs: show_tabs) do |wb|
25 | wb.project(id: project_id)
26 | wb.connectionCredentials(name: connection_username, password: connection_password, embed: connection_embed) if connection_username
27 | end
28 | end
29 |
30 | query = URI.encode_www_form([['overwrite', overwrite]])
31 | path = "sites/#{@client.auth.site_id}/workbooks?#{query}"
32 |
33 | parts = {
34 | 'request_payload' => request,
35 | 'tableau_workbook' => UploadIO.new(file, 'application/octet-stream')
36 | }
37 |
38 | headers = {
39 | parts: {
40 | 'request_payload' => { 'Content-Type' => 'text/xml' },
41 | 'tableau_workbook' => { 'Content-Type' => 'application/octet-string' }
42 | }
43 | }
44 |
45 | res = @client.connection.api_post_multipart(path, parts, headers)
46 |
47 | return HTTParty::Parser.new(res.body, :xml).parse['tsResponse']['workbook'] if res.code == '201'
48 |
49 | raise TableauError, res
50 | end
51 | # rubocop:enable Metrics/ParameterLists
52 |
53 | CAPABILITIES = %w[
54 | AddComment ChangeHierarchy ChangePermissions CreateRefreshMetrics Delete ExportData ExportImage
55 | ExportXml Filter Read RunExplainData ShareView ViewComments ViewUnderlyingData WebAuthoring Write
56 | ].freeze
57 |
58 | CAPABILITY_MODES = %w[ALLOW DENY].freeze
59 |
60 | # rubocop:disable Metrics/CyclomaticComplexity
61 | def permissions(workbook_id:)
62 | res = @client.connection.api_get("sites/#{@client.auth.site_id}/workbooks/#{workbook_id}/permissions")
63 |
64 | raise TableauError, res if res.code != 200
65 |
66 | permissions = HTTParty::Parser.new(res.body, :xml).parse['tsResponse']['permissions']['granteeCapabilities']
67 | return [] if permissions.nil?
68 |
69 | permissions = [permissions] unless permissions.is_a? Array
70 | permissions.map do |p|
71 | grantee_type = p['group'].nil? ? 'user' : 'group'
72 |
73 | capabilities = {}
74 | capabilities_list = p['capabilities']['capability']
75 | capabilities_list = [capabilities_list] unless capabilities_list.is_a? Array
76 |
77 | capabilities_list.each do |c|
78 | capabilities[c['name'].to_sym] = c['mode'] == 'Allow'
79 | end
80 |
81 | {
82 | grantee_type: grantee_type,
83 | grantee_id: p[grantee_type]['id'],
84 | capabilities: capabilities
85 | }
86 | end
87 | end
88 | # rubocop:enable Metrics/CyclomaticComplexity
89 |
90 | # capabilities is a hash of symbol keys to booleans { Read: true, ChangePermissions: false }
91 | def add_permissions(workbook_id:, capabilities:, user_id: nil, group_id: nil)
92 | validate_user_group_exclusivity(user_id, group_id)
93 |
94 | request = permissions_request(workbook_id, user_id, group_id, capabilities)
95 | res = @client.connection.api_put("sites/#{@client.auth.site_id}/workbooks/#{workbook_id}/permissions", body: request)
96 |
97 | res.code == 200
98 | end
99 |
100 | def delete_permissions(workbook_id:, capability:, capability_mode:, user_id: nil, group_id: nil)
101 | validate_user_group_exclusivity(user_id, group_id)
102 | raise 'invalid capability' unless CAPABILITIES.include? capability.to_s
103 | raise 'invalid mode' unless CAPABILITY_MODES.include? capability_mode.to_s
104 |
105 | subpath = user_id ? "users/#{user_id}" : "groups/#{group_id}"
106 | subpath += "/#{capability}/#{capability_mode.capitalize}"
107 | res = @client.connection.api_delete("sites/#{@client.auth.site_id}/workbooks/#{workbook_id}/permissions/#{subpath}")
108 |
109 | res.code == 204
110 | end
111 |
112 | def update(workbook_id:, owner_user_id:)
113 | request = Builder::XmlMarkup.new.tsRequest do |ts|
114 | ts.workbook(id: workbook_id) do |w|
115 | w.owner(id: owner_user_id)
116 | end
117 | end
118 |
119 | res = @client.connection.api_put("sites/#{@client.auth.site_id}/workbooks/#{workbook_id}", body: request)
120 |
121 | res.code == 200
122 | end
123 |
124 | def refresh(workbook_id:)
125 | request = Builder::XmlMarkup.new.tsRequest
126 | res = @client.connection.api_post("sites/#{@client.auth.site_id}/workbooks/#{workbook_id}/refresh", body: request)
127 | res.code == 202
128 | end
129 |
130 | def preview_image(workbook_id:)
131 | res = @client.connection.api_get("sites/#{@client.auth.site_id}/workbooks/#{workbook_id}/previewImage")
132 | res.body if res.code == 200
133 | end
134 |
135 | def find(workbook_id)
136 | res = @client.connection.api_get("sites/#{@client.auth.site_id}/workbooks/#{workbook_id}")
137 | res['tsResponse']['workbook'] if res.code == 200
138 | end
139 |
140 | def views(workbook_id)
141 | url = "sites/#{@client.auth.site_id}/workbooks/#{workbook_id}/views"
142 | @client.connection.api_get_collection(url, 'views.view')
143 | end
144 |
145 | def list
146 | url = "sites/#{@client.auth.site_id}/users/#{@client.auth.user_id}/workbooks"
147 | @client.connection.api_get_collection(url, 'workbooks.workbook')
148 | end
149 |
150 | private
151 |
152 | def permissions_request(workbook_id, user_id, group_id, capabilities)
153 | Builder::XmlMarkup.new.tsRequest do |ts|
154 | ts.permissions do |p|
155 | p.workbook(id: workbook_id)
156 | p.granteeCapabilities do |gc|
157 | gc.user(id: user_id) if user_id
158 | gc.group(id: group_id) if group_id
159 | gc.capabilities do |c|
160 | capabilities.each do |k, v|
161 | k = k.to_s
162 | raise "invalid capability #{k}" unless CAPABILITIES.include? k
163 |
164 | c.capability(name: k, mode: v ? 'Allow' : 'Deny')
165 | end
166 | end
167 | end
168 | end
169 | end
170 | end
171 |
172 | def validate_user_group_exclusivity(user_id, group_id)
173 | raise 'cannot specify user_id and group_id simultaneously' if user_id && group_id
174 | raise 'must specify user_id or group_id' unless user_id || group_id
175 | end
176 | end
177 | end
178 | end
179 |
--------------------------------------------------------------------------------
/lib/tableau_api/version.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module TableauApi
4 | VERSION = '5.0.0'
5 | end
6 |
--------------------------------------------------------------------------------
/spec/client_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'spec_helper'
4 |
5 | describe TableauApi::Client do
6 | it 'can create a client without issuing any web requests' do
7 | # this would exception if this made any http requests because VCR is set to not allow http connections when no cassette is loaded
8 | client = TableauApi.new(host: 'tableau.domain.tld', site_name: 'Default', username: 'ExampleUsername', password: 'ExamplePassword')
9 | expect(client).to be_an_instance_of(TableauApi::Client)
10 | end
11 |
12 | it 'requires the host, site_name, and username' do
13 | expect { TableauApi.new(host: nil, site_name: 'bar', username: 'baz') }.to raise_error('host is required')
14 | expect { TableauApi.new(host: '', site_name: 'bar', username: 'baz') }.to raise_error('host is required')
15 |
16 | expect { TableauApi.new(host: 'foo', site_name: nil, username: 'baz') }.to raise_error('site_name is required')
17 | expect { TableauApi.new(host: 'foo', site_name: '', username: 'baz') }.to raise_error('site_name is required')
18 |
19 | expect { TableauApi.new(host: 'foo', site_name: 'bar', username: nil) }.to raise_error('username is required')
20 | expect { TableauApi.new(host: 'foo', site_name: 'bar', username: '') }.to raise_error('username is required')
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/spec/fixtures/vcr_cassettes/auth.yml:
--------------------------------------------------------------------------------
1 | ---
2 | http_interactions:
3 | - request:
4 | method: post
5 | uri: http://TABLEAU_HOST/trusted
6 | body:
7 | encoding: UTF-8
8 | string: username=test&target_site=
9 | headers:
10 | Accept-Encoding:
11 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
12 | Accept:
13 | - "*/*"
14 | User-Agent:
15 | - Ruby
16 | response:
17 | status:
18 | code: 200
19 | message: OK
20 | headers:
21 | Cache-Control:
22 | - private, max-age=0, must-revalidate
23 | Content-Security-Policy-Report-Only:
24 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
25 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
26 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
27 | Content-Type:
28 | - text/plain;charset=UTF-8
29 | Date:
30 | - Mon, 26 Aug 2019 19:38:04 GMT
31 | P3p:
32 | - CP="NON"
33 | Pragma:
34 | - ''
35 | Server:
36 | - Tableau
37 | X-Content-Type-Options:
38 | - nosniff
39 | X-Tableau:
40 | - Tableau Server
41 | X-Tsi-Request-Id:
42 | - XWQ1HO1OS0ZGKhqUWLZFSAAAAAM
43 | X-Ua-Compatible:
44 | - IE=Edge
45 | X-Xss-Protection:
46 | - 1; mode=block
47 | Content-Length:
48 | - '2'
49 | Connection:
50 | - keep-alive
51 | body:
52 | encoding: UTF-8
53 | string: ZXN-jGH7QYSdctK-K4cSWQ==:QTpusOYkzCrotcubBNo7htxy
54 | http_version:
55 | recorded_at: Mon, 26 Aug 2019 19:38:04 GMT
56 | - request:
57 | method: post
58 | uri: http://TABLEAU_HOST/trusted
59 | body:
60 | encoding: UTF-8
61 | string: username=test_test&target_site=test
62 | headers:
63 | Accept-Encoding:
64 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
65 | Accept:
66 | - "*/*"
67 | User-Agent:
68 | - Ruby
69 | response:
70 | status:
71 | code: 200
72 | message: OK
73 | headers:
74 | Cache-Control:
75 | - private, max-age=0, must-revalidate
76 | Content-Security-Policy-Report-Only:
77 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
78 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
79 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
80 | Content-Type:
81 | - text/plain;charset=UTF-8
82 | Date:
83 | - Mon, 26 Aug 2019 19:38:04 GMT
84 | P3p:
85 | - CP="NON"
86 | Pragma:
87 | - ''
88 | Server:
89 | - Tableau
90 | X-Content-Type-Options:
91 | - nosniff
92 | X-Tableau:
93 | - Tableau Server
94 | X-Tsi-Request-Id:
95 | - XWQ1HO1OS0ZGKhqUWLZFSQAAAB0
96 | X-Ua-Compatible:
97 | - IE=Edge
98 | X-Xss-Protection:
99 | - 1; mode=block
100 | Content-Length:
101 | - '2'
102 | Connection:
103 | - keep-alive
104 | body:
105 | encoding: UTF-8
106 | string: ZXN-jGH7QYSdctK-K4cSWQ==:QTpusOYkzCrotcubBNo7htxy
107 | http_version:
108 | recorded_at: Mon, 26 Aug 2019 19:38:04 GMT
109 | - request:
110 | method: post
111 | uri: http://TABLEAU_HOST/trusted
112 | body:
113 | encoding: UTF-8
114 | string: username=test&target_site=test
115 | headers:
116 | Accept-Encoding:
117 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
118 | Accept:
119 | - "*/*"
120 | User-Agent:
121 | - Ruby
122 | response:
123 | status:
124 | code: 200
125 | message: OK
126 | headers:
127 | Cache-Control:
128 | - private, max-age=0, must-revalidate
129 | Content-Security-Policy-Report-Only:
130 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
131 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
132 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
133 | Content-Type:
134 | - text/plain;charset=UTF-8
135 | Date:
136 | - Mon, 26 Aug 2019 19:38:04 GMT
137 | P3p:
138 | - CP="NON"
139 | Pragma:
140 | - ''
141 | Server:
142 | - Tableau
143 | X-Content-Type-Options:
144 | - nosniff
145 | X-Tableau:
146 | - Tableau Server
147 | X-Tsi-Request-Id:
148 | - XWQ1HRAYKqeRms0l4B9FSgAAAJE
149 | X-Ua-Compatible:
150 | - IE=Edge
151 | X-Xss-Protection:
152 | - 1; mode=block
153 | Content-Length:
154 | - '2'
155 | Connection:
156 | - keep-alive
157 | body:
158 | encoding: UTF-8
159 | string: "-1"
160 | http_version:
161 | recorded_at: Mon, 26 Aug 2019 19:38:05 GMT
162 | - request:
163 | method: post
164 | uri: http://TABLEAU_HOST/trusted
165 | body:
166 | encoding: UTF-8
167 | string: username=invalid_user&target_site=
168 | headers:
169 | Accept-Encoding:
170 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
171 | Accept:
172 | - "*/*"
173 | User-Agent:
174 | - Ruby
175 | response:
176 | status:
177 | code: 200
178 | message: OK
179 | headers:
180 | Cache-Control:
181 | - private, max-age=0, must-revalidate
182 | Content-Security-Policy-Report-Only:
183 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
184 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
185 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
186 | Content-Type:
187 | - text/plain;charset=UTF-8
188 | Date:
189 | - Mon, 26 Aug 2019 19:38:04 GMT
190 | P3p:
191 | - CP="NON"
192 | Pragma:
193 | - ''
194 | Server:
195 | - Tableau
196 | X-Content-Type-Options:
197 | - nosniff
198 | X-Tableau:
199 | - Tableau Server
200 | X-Tsi-Request-Id:
201 | - XWQ1He1OS0ZGKhqUWLZFSgAAAD8
202 | X-Ua-Compatible:
203 | - IE=Edge
204 | X-Xss-Protection:
205 | - 1; mode=block
206 | Content-Length:
207 | - '2'
208 | Connection:
209 | - keep-alive
210 | body:
211 | encoding: UTF-8
212 | string: "-1"
213 | http_version:
214 | recorded_at: Mon, 26 Aug 2019 19:38:05 GMT
215 | - request:
216 | method: post
217 | uri: http://TABLEAU_HOST/trusted
218 | body:
219 | encoding: UTF-8
220 | string: username=test&target_site=invalid_site
221 | headers:
222 | Accept-Encoding:
223 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
224 | Accept:
225 | - "*/*"
226 | User-Agent:
227 | - Ruby
228 | response:
229 | status:
230 | code: 302
231 | message: Found
232 | headers:
233 | Cache-Control:
234 | - no-store
235 | Content-Security-Policy-Report-Only:
236 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
237 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
238 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
239 | Date:
240 | - Mon, 26 Aug 2019 19:38:04 GMT
241 | Location:
242 | - http://TABLEAU_HOST/vizportal/api/web/v1/auth/signin?path=%2Ftrusted%3F&siteUrlName=
243 | P3p:
244 | - CP="NON"
245 | Server:
246 | - Tableau
247 | X-Content-Type-Options:
248 | - nosniff
249 | X-Tableau:
250 | - Tableau Server
251 | X-Tsi-Request-Id:
252 | - XWQ1He1OS0ZGKhqUWLZFSwAAADg
253 | X-Ua-Compatible:
254 | - IE=Edge
255 | X-Xss-Protection:
256 | - 1; mode=block
257 | Content-Length:
258 | - '0'
259 | Connection:
260 | - keep-alive
261 | body:
262 | encoding: UTF-8
263 | string: ''
264 | http_version:
265 | recorded_at: Mon, 26 Aug 2019 19:38:05 GMT
266 | - request:
267 | method: post
268 | uri: http://TABLEAU_HOST/api/3.1/auth/signin
269 | body:
270 | encoding: UTF-8
271 | string:
272 | headers:
273 | Content-Type:
274 | - application/xml
275 | Accept-Encoding:
276 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
277 | Accept:
278 | - "*/*"
279 | User-Agent:
280 | - Ruby
281 | response:
282 | status:
283 | code: 401
284 | message: Unauthorized
285 | headers:
286 | Content-Security-Policy-Report-Only:
287 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
288 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
289 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
290 | Content-Type:
291 | - application/xml;charset=utf-8
292 | Date:
293 | - Tue, 24 Nov 2020 21:59:25 GMT
294 | P3p:
295 | - CP="NON"
296 | Server:
297 | - Tableau
298 | X-Content-Type-Options:
299 | - nosniff
300 | X-Tableau:
301 | - Tableau Server
302 | X-Ua-Compatible:
303 | - IE=Edge
304 | X-Xss-Protection:
305 | - 1; mode=block
306 | Content-Length:
307 | - '341'
308 | Connection:
309 | - keep-alive
310 | body:
311 | encoding: UTF-8
312 | string: Signin
315 | ErrorError signing in to Tableau Server
316 | http_version:
317 | recorded_at: Tue, 24 Nov 2020 21:59:25 GMT
318 | - request:
319 | method: post
320 | uri: http://TABLEAU_HOST/api/3.1/auth/signin
321 | body:
322 | encoding: UTF-8
323 | string:
325 | headers:
326 | Content-Type:
327 | - application/xml
328 | Accept-Encoding:
329 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
330 | Accept:
331 | - "*/*"
332 | User-Agent:
333 | - Ruby
334 | response:
335 | status:
336 | code: 200
337 | message: OK
338 | headers:
339 | Content-Security-Policy-Report-Only:
340 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
341 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
342 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
343 | Content-Type:
344 | - application/xml;charset=utf-8
345 | Date:
346 | - Tue, 24 Nov 2020 21:59:25 GMT
347 | P3p:
348 | - CP="NON"
349 | Server:
350 | - Tableau
351 | Vary:
352 | - Accept-Encoding
353 | X-Content-Type-Options:
354 | - nosniff
355 | X-Tableau:
356 | - Tableau Server
357 | X-Ua-Compatible:
358 | - IE=Edge
359 | X-Xss-Protection:
360 | - 1; mode=block
361 | Transfer-Encoding:
362 | - chunked
363 | Connection:
364 | - keep-alive
365 | body:
366 | encoding: ASCII-8BIT
367 | string:
371 | http_version:
372 | recorded_at: Tue, 24 Nov 2020 21:59:26 GMT
373 | - request:
374 | method: post
375 | uri: http://TABLEAU_HOST/api/3.1/auth/signin
376 | body:
377 | encoding: UTF-8
378 | string:
380 | headers:
381 | Content-Type:
382 | - application/xml
383 | Accept-Encoding:
384 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
385 | Accept:
386 | - "*/*"
387 | User-Agent:
388 | - Ruby
389 | response:
390 | status:
391 | code: 200
392 | message: OK
393 | headers:
394 | Content-Security-Policy-Report-Only:
395 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
396 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
397 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
398 | Content-Type:
399 | - application/xml;charset=utf-8
400 | Date:
401 | - Tue, 24 Nov 2020 21:59:26 GMT
402 | P3p:
403 | - CP="NON"
404 | Server:
405 | - Tableau
406 | Vary:
407 | - Accept-Encoding
408 | X-Content-Type-Options:
409 | - nosniff
410 | X-Tableau:
411 | - Tableau Server
412 | X-Ua-Compatible:
413 | - IE=Edge
414 | X-Xss-Protection:
415 | - 1; mode=block
416 | Transfer-Encoding:
417 | - chunked
418 | Connection:
419 | - keep-alive
420 | body:
421 | encoding: ASCII-8BIT
422 | string:
426 | http_version:
427 | recorded_at: Tue, 24 Nov 2020 21:59:26 GMT
428 | - request:
429 | method: post
430 | uri: http://TABLEAU_HOST/api/3.1/auth/signout
431 | body:
432 | encoding: UTF-8
433 | string: ''
434 | headers:
435 | Content-Type:
436 | - application/xml
437 | X-Tableau-Auth:
438 | - lyRRiXUQTpqu44g7D79CsQ|dnKJm45x9iBtVokNOKBhAZ7aHKb2bBvC
439 | Accept-Encoding:
440 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
441 | Accept:
442 | - "*/*"
443 | User-Agent:
444 | - Ruby
445 | response:
446 | status:
447 | code: 204
448 | message: No Content
449 | headers:
450 | Content-Security-Policy-Report-Only:
451 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
452 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
453 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
454 | Date:
455 | - Tue, 24 Nov 2020 21:59:26 GMT
456 | P3p:
457 | - CP="NON"
458 | Server:
459 | - Tableau
460 | X-Content-Type-Options:
461 | - nosniff
462 | X-Tableau:
463 | - Tableau Server
464 | X-Ua-Compatible:
465 | - IE=Edge
466 | X-Xss-Protection:
467 | - 1; mode=block
468 | Connection:
469 | - keep-alive
470 | body:
471 | encoding: UTF-8
472 | string: ''
473 | http_version:
474 | recorded_at: Tue, 24 Nov 2020 21:59:26 GMT
475 | recorded_with: VCR 3.0.3
476 |
--------------------------------------------------------------------------------
/spec/fixtures/vcr_cassettes/datasources.yml:
--------------------------------------------------------------------------------
1 | ---
2 | http_interactions:
3 | - request:
4 | method: post
5 | uri: http://TABLEAU_HOST/api/3.1/auth/signin
6 | body:
7 | encoding: UTF-8
8 | string:
10 | headers:
11 | Content-Type:
12 | - application/xml
13 | Accept-Encoding:
14 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
15 | Accept:
16 | - "*/*"
17 | User-Agent:
18 | - Ruby
19 | response:
20 | status:
21 | code: 200
22 | message: OK
23 | headers:
24 | Content-Security-Policy-Report-Only:
25 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
26 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
27 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
28 | Content-Type:
29 | - application/xml;charset=utf-8
30 | Date:
31 | - Wed, 28 Oct 2020 20:40:16 GMT
32 | P3p:
33 | - CP="NON"
34 | Server:
35 | - Tableau
36 | Vary:
37 | - Accept-Encoding
38 | X-Content-Type-Options:
39 | - nosniff
40 | X-Tableau:
41 | - Tableau Server
42 | X-Ua-Compatible:
43 | - IE=Edge
44 | X-Xss-Protection:
45 | - 1; mode=block
46 | Transfer-Encoding:
47 | - chunked
48 | Connection:
49 | - keep-alive
50 | body:
51 | encoding: ASCII-8BIT
52 | string:
56 | http_version:
57 | recorded_at: Wed, 28 Oct 2020 20:40:16 GMT
58 | - request:
59 | method: get
60 | uri: http://TABLEAU_HOST/api/3.1/sites/f31923d2-dea4-4470-843a-9fc999d240aa/datasources?pageNumber=1&pageSize=100
61 | body:
62 | encoding: US-ASCII
63 | string: ''
64 | headers:
65 | X-Tableau-Auth:
66 | - V-Mj5mcBTJCdjUYTwGXbsQ|jvpee6clUK0B57aiJejDYRrMHhsm4s9h
67 | Accept-Encoding:
68 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
69 | Accept:
70 | - "*/*"
71 | User-Agent:
72 | - Ruby
73 | response:
74 | status:
75 | code: 200
76 | message: OK
77 | headers:
78 | Content-Security-Policy-Report-Only:
79 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
80 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
81 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
82 | Content-Type:
83 | - application/xml;charset=utf-8
84 | Date:
85 | - Wed, 28 Oct 2020 20:40:16 GMT
86 | P3p:
87 | - CP="NON"
88 | Server:
89 | - Tableau
90 | Vary:
91 | - Accept-Encoding
92 | X-Content-Type-Options:
93 | - nosniff
94 | X-Tableau:
95 | - Tableau Server
96 | X-Ua-Compatible:
97 | - IE=Edge
98 | X-Xss-Protection:
99 | - 1; mode=block
100 | Content-Length:
101 | - '469'
102 | Connection:
103 | - keep-alive
104 | body:
105 | encoding: ASCII-8BIT
106 | string:
114 | http_version:
115 | recorded_at: Wed, 28 Oct 2020 20:40:16 GMT
116 | recorded_with: VCR 3.0.3
117 |
--------------------------------------------------------------------------------
/spec/fixtures/vcr_cassettes/groups.yml:
--------------------------------------------------------------------------------
1 | ---
2 | http_interactions:
3 | - request:
4 | method: post
5 | uri: http://TABLEAU_HOST/api/3.1/auth/signin
6 | body:
7 | encoding: UTF-8
8 | string:
10 | headers:
11 | Content-Type:
12 | - application/xml
13 | Accept-Encoding:
14 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
15 | Accept:
16 | - "*/*"
17 | User-Agent:
18 | - Ruby
19 | response:
20 | status:
21 | code: 200
22 | message: OK
23 | headers:
24 | Content-Security-Policy-Report-Only:
25 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
26 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
27 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
28 | Content-Type:
29 | - application/xml;charset=utf-8
30 | Date:
31 | - Tue, 24 Nov 2020 22:20:57 GMT
32 | P3p:
33 | - CP="NON"
34 | Server:
35 | - Tableau
36 | Vary:
37 | - Accept-Encoding
38 | X-Content-Type-Options:
39 | - nosniff
40 | X-Tableau:
41 | - Tableau Server
42 | X-Ua-Compatible:
43 | - IE=Edge
44 | X-Xss-Protection:
45 | - 1; mode=block
46 | Transfer-Encoding:
47 | - chunked
48 | Connection:
49 | - keep-alive
50 | body:
51 | encoding: ASCII-8BIT
52 | string:
56 | http_version:
57 | recorded_at: Tue, 24 Nov 2020 22:20:57 GMT
58 | - request:
59 | method: post
60 | uri: http://TABLEAU_HOST/api/3.1/sites/fb50c166-f809-44e0-995e-2cf56ceffbc0/groups
61 | body:
62 | encoding: UTF-8
63 | string:
64 | headers:
65 | Content-Type:
66 | - application/xml
67 | X-Tableau-Auth:
68 | - 3BUXJOEXTiuty-6nSMXGzw|X12QskQTQyIU0hSubd6HRac46EKUtWTF
69 | Accept-Encoding:
70 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
71 | Accept:
72 | - "*/*"
73 | User-Agent:
74 | - Ruby
75 | response:
76 | status:
77 | code: 201
78 | message: Created
79 | headers:
80 | Content-Security-Policy-Report-Only:
81 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
82 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
83 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
84 | Content-Type:
85 | - application/xml;charset=utf-8
86 | Date:
87 | - Tue, 24 Nov 2020 22:20:57 GMT
88 | Location:
89 | - "/api/3.1/sites/fb50c166-f809-44e0-995e-2cf56ceffbc0/groups/90ff8ad8-bb15-404d-a84e-f3ffde19c902"
90 | P3p:
91 | - CP="NON"
92 | Server:
93 | - Tableau
94 | X-Content-Type-Options:
95 | - nosniff
96 | X-Tableau:
97 | - Tableau Server
98 | X-Ua-Compatible:
99 | - IE=Edge
100 | X-Xss-Protection:
101 | - 1; mode=block
102 | Transfer-Encoding:
103 | - chunked
104 | Connection:
105 | - keep-alive
106 | body:
107 | encoding: UTF-8
108 | string:
112 | http_version:
113 | recorded_at: Tue, 24 Nov 2020 22:20:57 GMT
114 | - request:
115 | method: get
116 | uri: http://TABLEAU_HOST/api/3.1/sites/fb50c166-f809-44e0-995e-2cf56ceffbc0/groups?pageNumber=1&pageSize=100
117 | body:
118 | encoding: US-ASCII
119 | string: ''
120 | headers:
121 | X-Tableau-Auth:
122 | - 3BUXJOEXTiuty-6nSMXGzw|X12QskQTQyIU0hSubd6HRac46EKUtWTF
123 | Accept-Encoding:
124 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
125 | Accept:
126 | - "*/*"
127 | User-Agent:
128 | - Ruby
129 | response:
130 | status:
131 | code: 200
132 | message: OK
133 | headers:
134 | Content-Security-Policy-Report-Only:
135 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
136 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
137 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
138 | Content-Type:
139 | - application/xml;charset=utf-8
140 | Date:
141 | - Tue, 24 Nov 2020 22:21:13 GMT
142 | P3p:
143 | - CP="NON"
144 | Server:
145 | - Tableau
146 | Vary:
147 | - Accept-Encoding
148 | X-Content-Type-Options:
149 | - nosniff
150 | X-Tableau:
151 | - Tableau Server
152 | X-Ua-Compatible:
153 | - IE=Edge
154 | X-Xss-Protection:
155 | - 1; mode=block
156 | Content-Length:
157 | - '320'
158 | Connection:
159 | - keep-alive
160 | body:
161 | encoding: ASCII-8BIT
162 | string:
168 | http_version:
169 | recorded_at: Tue, 24 Nov 2020 22:21:13 GMT
170 | - request:
171 | method: get
172 | uri: http://TABLEAU_HOST/api/3.1/sites/fb50c166-f809-44e0-995e-2cf56ceffbc0/users?pageNumber=1&pageSize=100
173 | body:
174 | encoding: US-ASCII
175 | string: ''
176 | headers:
177 | X-Tableau-Auth:
178 | - 3BUXJOEXTiuty-6nSMXGzw|X12QskQTQyIU0hSubd6HRac46EKUtWTF
179 | Accept-Encoding:
180 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
181 | Accept:
182 | - "*/*"
183 | User-Agent:
184 | - Ruby
185 | response:
186 | status:
187 | code: 200
188 | message: OK
189 | headers:
190 | Content-Security-Policy-Report-Only:
191 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
192 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
193 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
194 | Content-Type:
195 | - application/xml;charset=utf-8
196 | Date:
197 | - Tue, 24 Nov 2020 22:21:13 GMT
198 | P3p:
199 | - CP="NON"
200 | Server:
201 | - Tableau
202 | Vary:
203 | - Accept-Encoding
204 | X-Content-Type-Options:
205 | - nosniff
206 | X-Tableau:
207 | - Tableau Server
208 | X-Ua-Compatible:
209 | - IE=Edge
210 | X-Xss-Protection:
211 | - 1; mode=block
212 | Content-Length:
213 | - '420'
214 | Connection:
215 | - keep-alive
216 | body:
217 | encoding: ASCII-8BIT
218 | string:
226 | http_version:
227 | recorded_at: Tue, 24 Nov 2020 22:21:13 GMT
228 | - request:
229 | method: post
230 | uri: http://TABLEAU_HOST/api/3.1/sites/fb50c166-f809-44e0-995e-2cf56ceffbc0/groups/90ff8ad8-bb15-404d-a84e-f3ffde19c902/users
231 | body:
232 | encoding: UTF-8
233 | string:
234 | headers:
235 | Content-Type:
236 | - application/xml
237 | X-Tableau-Auth:
238 | - 3BUXJOEXTiuty-6nSMXGzw|X12QskQTQyIU0hSubd6HRac46EKUtWTF
239 | Accept-Encoding:
240 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
241 | Accept:
242 | - "*/*"
243 | User-Agent:
244 | - Ruby
245 | response:
246 | status:
247 | code: 200
248 | message: OK
249 | headers:
250 | Content-Security-Policy-Report-Only:
251 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
252 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
253 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
254 | Content-Type:
255 | - application/xml;charset=utf-8
256 | Date:
257 | - Tue, 24 Nov 2020 22:21:13 GMT
258 | P3p:
259 | - CP="NON"
260 | Server:
261 | - Tableau
262 | Vary:
263 | - Accept-Encoding
264 | X-Content-Type-Options:
265 | - nosniff
266 | X-Tableau:
267 | - Tableau Server
268 | X-Ua-Compatible:
269 | - IE=Edge
270 | X-Xss-Protection:
271 | - 1; mode=block
272 | Content-Length:
273 | - '251'
274 | Connection:
275 | - keep-alive
276 | body:
277 | encoding: ASCII-8BIT
278 | string:
282 | http_version:
283 | recorded_at: Tue, 24 Nov 2020 22:21:13 GMT
284 | - request:
285 | method: get
286 | uri: http://TABLEAU_HOST/api/3.1/sites/fb50c166-f809-44e0-995e-2cf56ceffbc0/groups/90ff8ad8-bb15-404d-a84e-f3ffde19c902/users?pageNumber=1&pageSize=100
287 | body:
288 | encoding: US-ASCII
289 | string: ''
290 | headers:
291 | X-Tableau-Auth:
292 | - 3BUXJOEXTiuty-6nSMXGzw|X12QskQTQyIU0hSubd6HRac46EKUtWTF
293 | Accept-Encoding:
294 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
295 | Accept:
296 | - "*/*"
297 | User-Agent:
298 | - Ruby
299 | response:
300 | status:
301 | code: 200
302 | message: OK
303 | headers:
304 | Content-Security-Policy-Report-Only:
305 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
306 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
307 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
308 | Content-Type:
309 | - application/xml;charset=utf-8
310 | Date:
311 | - Tue, 24 Nov 2020 22:21:13 GMT
312 | P3p:
313 | - CP="NON"
314 | Server:
315 | - Tableau
316 | Vary:
317 | - Accept-Encoding
318 | X-Content-Type-Options:
319 | - nosniff
320 | X-Tableau:
321 | - Tableau Server
322 | X-Ua-Compatible:
323 | - IE=Edge
324 | X-Xss-Protection:
325 | - 1; mode=block
326 | Content-Length:
327 | - '292'
328 | Connection:
329 | - keep-alive
330 | body:
331 | encoding: ASCII-8BIT
332 | string:
337 | http_version:
338 | recorded_at: Tue, 24 Nov 2020 22:21:13 GMT
339 | - request:
340 | method: delete
341 | uri: http://TABLEAU_HOST/api/3.1/sites/fb50c166-f809-44e0-995e-2cf56ceffbc0/groups/90ff8ad8-bb15-404d-a84e-f3ffde19c902/users/ce93aaeb-fb25-4fbd-a573-74b549f5e95b
342 | body:
343 | encoding: US-ASCII
344 | string: ''
345 | headers:
346 | X-Tableau-Auth:
347 | - 3BUXJOEXTiuty-6nSMXGzw|X12QskQTQyIU0hSubd6HRac46EKUtWTF
348 | Accept-Encoding:
349 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
350 | Accept:
351 | - "*/*"
352 | User-Agent:
353 | - Ruby
354 | response:
355 | status:
356 | code: 204
357 | message: No Content
358 | headers:
359 | Content-Security-Policy-Report-Only:
360 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
361 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
362 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
363 | Date:
364 | - Tue, 24 Nov 2020 22:21:14 GMT
365 | P3p:
366 | - CP="NON"
367 | Server:
368 | - Tableau
369 | X-Content-Type-Options:
370 | - nosniff
371 | X-Tableau:
372 | - Tableau Server
373 | X-Ua-Compatible:
374 | - IE=Edge
375 | X-Xss-Protection:
376 | - 1; mode=block
377 | Connection:
378 | - keep-alive
379 | body:
380 | encoding: UTF-8
381 | string: ''
382 | http_version:
383 | recorded_at: Tue, 24 Nov 2020 22:21:14 GMT
384 | recorded_with: VCR 3.0.3
385 |
--------------------------------------------------------------------------------
/spec/fixtures/vcr_cassettes/jobs.yml:
--------------------------------------------------------------------------------
1 | ---
2 | http_interactions:
3 | - request:
4 | method: post
5 | uri: http://TABLEAU_HOST/api/3.1/auth/signin
6 | body:
7 | encoding: UTF-8
8 | string:
10 | headers:
11 | Accept-Encoding:
12 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
13 | Accept:
14 | - "*/*"
15 | User-Agent:
16 | - Ruby
17 | response:
18 | status:
19 | code: 200
20 | message: OK
21 | headers:
22 | Content-Security-Policy-Report-Only:
23 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
24 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
25 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
26 | Content-Type:
27 | - application/xml;charset=utf-8
28 | Date:
29 | - Mon, 30 Nov 2020 21:37:25 GMT
30 | P3p:
31 | - CP="NON"
32 | Server:
33 | - Tableau
34 | Vary:
35 | - Accept-Encoding
36 | X-Content-Type-Options:
37 | - nosniff
38 | X-Tableau:
39 | - Tableau Server
40 | X-Ua-Compatible:
41 | - IE=Edge
42 | X-Xss-Protection:
43 | - 1; mode=block
44 | Content-Length:
45 | - '329'
46 | Connection:
47 | - keep-alive
48 | body:
49 | encoding: ASCII-8BIT
50 | string:
54 | http_version:
55 | recorded_at: Mon, 30 Nov 2020 21:37:25 GMT
56 | - request:
57 | method: get
58 | uri: http://TABLEAU_HOST/api/3.1/sites/fb50c166-f809-44e0-995e-2cf56ceffbc0/projects?pageNumber=1&pageSize=100
59 | body:
60 | encoding: US-ASCII
61 | string: ''
62 | headers:
63 | User-Agent:
64 | - tableau_api/4.0.0 Ruby/2.4.1
65 | X-Tableau-Auth:
66 | - JLiIUYvpRIKOgQ5MtStpMQ|3P04IdDmWUI3nCSpmYIViDJvvTS6hVch
67 | Accept-Encoding:
68 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
69 | Accept:
70 | - "*/*"
71 | response:
72 | status:
73 | code: 200
74 | message: OK
75 | headers:
76 | Content-Security-Policy-Report-Only:
77 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
78 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
79 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
80 | Content-Type:
81 | - application/xml;charset=utf-8
82 | Date:
83 | - Mon, 30 Nov 2020 21:37:25 GMT
84 | P3p:
85 | - CP="NON"
86 | Server:
87 | - Tableau
88 | Vary:
89 | - Accept-Encoding
90 | X-Content-Type-Options:
91 | - nosniff
92 | X-Tableau:
93 | - Tableau Server
94 | X-Ua-Compatible:
95 | - IE=Edge
96 | X-Xss-Protection:
97 | - 1; mode=block
98 | Content-Length:
99 | - '522'
100 | Connection:
101 | - keep-alive
102 | body:
103 | encoding: ASCII-8BIT
104 | string:
116 | http_version:
117 | recorded_at: Mon, 30 Nov 2020 21:37:25 GMT
118 | - request:
119 | method: get
120 | uri: http://TABLEAU_HOST/api/3.1/sites/fb50c166-f809-44e0-995e-2cf56ceffbc0/users/962f69f4-db35-4a1d-b2b5-c5c5c5c5bcaa/workbooks?pageNumber=1&pageSize=100
121 | body:
122 | encoding: US-ASCII
123 | string: ''
124 | headers:
125 | User-Agent:
126 | - tableau_api/4.0.0 Ruby/2.4.1
127 | X-Tableau-Auth:
128 | - JLiIUYvpRIKOgQ5MtStpMQ|3P04IdDmWUI3nCSpmYIViDJvvTS6hVch
129 | Accept-Encoding:
130 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
131 | Accept:
132 | - "*/*"
133 | response:
134 | status:
135 | code: 200
136 | message: OK
137 | headers:
138 | Content-Security-Policy-Report-Only:
139 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
140 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
141 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
142 | Content-Type:
143 | - application/xml;charset=utf-8
144 | Date:
145 | - Mon, 30 Nov 2020 21:37:25 GMT
146 | P3p:
147 | - CP="NON"
148 | Server:
149 | - Tableau
150 | Vary:
151 | - Accept-Encoding
152 | X-Content-Type-Options:
153 | - nosniff
154 | X-Tableau:
155 | - Tableau Server
156 | X-Ua-Compatible:
157 | - IE=Edge
158 | X-Xss-Protection:
159 | - 1; mode=block
160 | Content-Length:
161 | - '659'
162 | Connection:
163 | - keep-alive
164 | body:
165 | encoding: ASCII-8BIT
166 | string:
188 | http_version:
189 | recorded_at: Mon, 30 Nov 2020 21:37:26 GMT
190 | - request:
191 | method: get
192 | uri: http://TABLEAU_HOST/api/3.1/sites/fb50c166-f809-44e0-995e-2cf56ceffbc0/workbooks/578ed308-3715-46ac-ae36-1af2caad6216
193 | body:
194 | encoding: US-ASCII
195 | string: ''
196 | headers:
197 | User-Agent:
198 | - tableau_api/4.0.0 Ruby/2.4.1
199 | X-Tableau-Auth:
200 | - JLiIUYvpRIKOgQ5MtStpMQ|3P04IdDmWUI3nCSpmYIViDJvvTS6hVch
201 | Accept-Encoding:
202 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
203 | Accept:
204 | - "*/*"
205 | response:
206 | status:
207 | code: 200
208 | message: OK
209 | headers:
210 | Content-Security-Policy-Report-Only:
211 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
212 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
213 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
214 | Content-Type:
215 | - application/xml;charset=utf-8
216 | Date:
217 | - Mon, 30 Nov 2020 21:37:25 GMT
218 | P3p:
219 | - CP="NON"
220 | Server:
221 | - Tableau
222 | Vary:
223 | - Accept-Encoding
224 | X-Content-Type-Options:
225 | - nosniff
226 | X-Tableau:
227 | - Tableau Server
228 | X-Ua-Compatible:
229 | - IE=Edge
230 | X-Xss-Protection:
231 | - 1; mode=block
232 | Content-Length:
233 | - '478'
234 | Connection:
235 | - keep-alive
236 | body:
237 | encoding: ASCII-8BIT
238 | string:
246 | http_version:
247 | recorded_at: Mon, 30 Nov 2020 21:37:26 GMT
248 | - request:
249 | method: post
250 | uri: http://TABLEAU_HOST/api/3.1/sites/fb50c166-f809-44e0-995e-2cf56ceffbc0/workbooks/578ed308-3715-46ac-ae36-1af2caad6216/refresh
251 | body:
252 | encoding: UTF-8
253 | string: ""
254 | headers:
255 | User-Agent:
256 | - tableau_api/4.0.0 Ruby/2.4.1
257 | Content-Type:
258 | - application/xml
259 | X-Tableau-Auth:
260 | - JLiIUYvpRIKOgQ5MtStpMQ|3P04IdDmWUI3nCSpmYIViDJvvTS6hVch
261 | Accept-Encoding:
262 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
263 | Accept:
264 | - "*/*"
265 | response:
266 | status:
267 | code: 202
268 | message: Accepted
269 | headers:
270 | Content-Security-Policy-Report-Only:
271 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
272 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
273 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
274 | Content-Type:
275 | - application/xml;charset=utf-8
276 | Date:
277 | - Mon, 30 Nov 2020 21:37:26 GMT
278 | Location:
279 | - "/api/3.1/sites/fb50c166-f809-44e0-995e-2cf56ceffbc0/jobs/4cd38c29-d21a-4efb-9ccc-b6055fd98dad"
280 | P3p:
281 | - CP="NON"
282 | Server:
283 | - Tableau
284 | X-Content-Type-Options:
285 | - nosniff
286 | X-Tableau:
287 | - Tableau Server
288 | X-Ua-Compatible:
289 | - IE=Edge
290 | X-Xss-Protection:
291 | - 1; mode=block
292 | Transfer-Encoding:
293 | - chunked
294 | Connection:
295 | - keep-alive
296 | body:
297 | encoding: UTF-8
298 | string:
303 | http_version:
304 | recorded_at: Mon, 30 Nov 2020 21:37:26 GMT
305 | - request:
306 | method: get
307 | uri: http://TABLEAU_HOST/api/3.1/sites/fb50c166-f809-44e0-995e-2cf56ceffbc0/jobs?pageNumber=1&pageSize=100
308 | body:
309 | encoding: US-ASCII
310 | string: ''
311 | headers:
312 | User-Agent:
313 | - tableau_api/4.0.0 Ruby/2.4.1
314 | X-Tableau-Auth:
315 | - JLiIUYvpRIKOgQ5MtStpMQ|3P04IdDmWUI3nCSpmYIViDJvvTS6hVch
316 | Accept-Encoding:
317 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
318 | Accept:
319 | - "*/*"
320 | response:
321 | status:
322 | code: 200
323 | message: OK
324 | headers:
325 | Content-Security-Policy-Report-Only:
326 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
327 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
328 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
329 | Content-Type:
330 | - application/xml;charset=utf-8
331 | Date:
332 | - Mon, 30 Nov 2020 21:37:26 GMT
333 | P3p:
334 | - CP="NON"
335 | Server:
336 | - Tableau
337 | Vary:
338 | - Accept-Encoding
339 | X-Content-Type-Options:
340 | - nosniff
341 | X-Tableau:
342 | - Tableau Server
343 | X-Ua-Compatible:
344 | - IE=Edge
345 | X-Xss-Protection:
346 | - 1; mode=block
347 | Transfer-Encoding:
348 | - chunked
349 | Connection:
350 | - keep-alive
351 | body:
352 | encoding: ASCII-8BIT
353 | string:
448 | http_version:
449 | recorded_at: Mon, 30 Nov 2020 21:37:27 GMT
450 | - request:
451 | method: get
452 | uri: http://TABLEAU_HOST/api/3.1/sites/fb50c166-f809-44e0-995e-2cf56ceffbc0/jobs?filter=jobType:eq:refresh_extracts,status:eq:Success&pageNumber=1&pageSize=100
453 | body:
454 | encoding: US-ASCII
455 | string: ''
456 | headers:
457 | User-Agent:
458 | - tableau_api/4.0.0 Ruby/2.4.1
459 | X-Tableau-Auth:
460 | - JLiIUYvpRIKOgQ5MtStpMQ|3P04IdDmWUI3nCSpmYIViDJvvTS6hVch
461 | Accept-Encoding:
462 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
463 | Accept:
464 | - "*/*"
465 | response:
466 | status:
467 | code: 200
468 | message: OK
469 | headers:
470 | Content-Security-Policy-Report-Only:
471 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
472 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
473 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
474 | Content-Type:
475 | - application/xml;charset=utf-8
476 | Date:
477 | - Mon, 30 Nov 2020 21:37:26 GMT
478 | P3p:
479 | - CP="NON"
480 | Server:
481 | - Tableau
482 | Vary:
483 | - Accept-Encoding
484 | X-Content-Type-Options:
485 | - nosniff
486 | X-Tableau:
487 | - Tableau Server
488 | X-Ua-Compatible:
489 | - IE=Edge
490 | X-Xss-Protection:
491 | - 1; mode=block
492 | Transfer-Encoding:
493 | - chunked
494 | Connection:
495 | - keep-alive
496 | body:
497 | encoding: ASCII-8BIT
498 | string:
502 | http_version:
503 | recorded_at: Mon, 30 Nov 2020 21:37:27 GMT
504 | recorded_with: VCR 3.0.3
505 |
--------------------------------------------------------------------------------
/spec/fixtures/vcr_cassettes/projects.yml:
--------------------------------------------------------------------------------
1 | ---
2 | http_interactions:
3 | - request:
4 | method: post
5 | uri: http://TABLEAU_HOST/api/3.1/auth/signin
6 | body:
7 | encoding: UTF-8
8 | string:
10 | headers:
11 | Content-Type:
12 | - application/xml
13 | Accept-Encoding:
14 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
15 | Accept:
16 | - "*/*"
17 | User-Agent:
18 | - Ruby
19 | response:
20 | status:
21 | code: 200
22 | message: OK
23 | headers:
24 | Content-Security-Policy-Report-Only:
25 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
26 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
27 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
28 | Content-Type:
29 | - application/xml;charset=utf-8
30 | Date:
31 | - Tue, 24 Nov 2020 21:59:47 GMT
32 | P3p:
33 | - CP="NON"
34 | Server:
35 | - Tableau
36 | Vary:
37 | - Accept-Encoding
38 | X-Content-Type-Options:
39 | - nosniff
40 | X-Tableau:
41 | - Tableau Server
42 | X-Ua-Compatible:
43 | - IE=Edge
44 | X-Xss-Protection:
45 | - 1; mode=block
46 | Content-Length:
47 | - '330'
48 | Connection:
49 | - keep-alive
50 | body:
51 | encoding: ASCII-8BIT
52 | string:
56 | http_version:
57 | recorded_at: Tue, 24 Nov 2020 21:59:47 GMT
58 | - request:
59 | method: post
60 | uri: http://TABLEAU_HOST/api/3.1/sites/fb50c166-f809-44e0-995e-2cf56ceffbc0/projects
61 | body:
62 | encoding: UTF-8
63 | string:
64 | headers:
65 | Content-Type:
66 | - application/xml
67 | X-Tableau-Auth:
68 | - E86lXTA6SW23T7zCtKhNQA|lh0ZYV3gilTVQkTeifS2g0BfPiPEtyJU
69 | Accept-Encoding:
70 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
71 | Accept:
72 | - "*/*"
73 | User-Agent:
74 | - Ruby
75 | response:
76 | status:
77 | code: 201
78 | message: Created
79 | headers:
80 | Content-Security-Policy-Report-Only:
81 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
82 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
83 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
84 | Content-Type:
85 | - application/xml;charset=utf-8
86 | Date:
87 | - Tue, 24 Nov 2020 21:59:47 GMT
88 | Location:
89 | - "/api/3.1/sites/fb50c166-f809-44e0-995e-2cf56ceffbc0/projects/965b0b2c-074f-48ff-8032-dbeaa5d22b27"
90 | P3p:
91 | - CP="NON"
92 | Server:
93 | - Tableau
94 | X-Content-Type-Options:
95 | - nosniff
96 | X-Tableau:
97 | - Tableau Server
98 | X-Ua-Compatible:
99 | - IE=Edge
100 | X-Xss-Protection:
101 | - 1; mode=block
102 | Content-Length:
103 | - '478'
104 | Connection:
105 | - keep-alive
106 | body:
107 | encoding: UTF-8
108 | string:
113 | http_version:
114 | recorded_at: Tue, 24 Nov 2020 21:59:47 GMT
115 | - request:
116 | method: get
117 | uri: http://TABLEAU_HOST/api/3.1/sites/fb50c166-f809-44e0-995e-2cf56ceffbc0/projects?pageNumber=1&pageSize=100
118 | body:
119 | encoding: US-ASCII
120 | string: ''
121 | headers:
122 | X-Tableau-Auth:
123 | - E86lXTA6SW23T7zCtKhNQA|lh0ZYV3gilTVQkTeifS2g0BfPiPEtyJU
124 | Accept-Encoding:
125 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
126 | Accept:
127 | - "*/*"
128 | User-Agent:
129 | - Ruby
130 | response:
131 | status:
132 | code: 200
133 | message: OK
134 | headers:
135 | Content-Security-Policy-Report-Only:
136 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
137 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
138 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
139 | Content-Type:
140 | - application/xml;charset=utf-8
141 | Date:
142 | - Tue, 24 Nov 2020 22:00:03 GMT
143 | P3p:
144 | - CP="NON"
145 | Server:
146 | - Tableau
147 | Vary:
148 | - Accept-Encoding
149 | X-Content-Type-Options:
150 | - nosniff
151 | X-Tableau:
152 | - Tableau Server
153 | X-Ua-Compatible:
154 | - IE=Edge
155 | X-Xss-Protection:
156 | - 1; mode=block
157 | Content-Length:
158 | - '522'
159 | Connection:
160 | - keep-alive
161 | body:
162 | encoding: ASCII-8BIT
163 | string:
175 | http_version:
176 | recorded_at: Tue, 24 Nov 2020 22:00:03 GMT
177 | recorded_with: VCR 3.0.3
178 |
--------------------------------------------------------------------------------
/spec/fixtures/vcr_cassettes/sites.yml:
--------------------------------------------------------------------------------
1 | ---
2 | http_interactions:
3 | - request:
4 | method: post
5 | uri: http://TABLEAU_HOST/api/3.1/auth/signin
6 | body:
7 | encoding: UTF-8
8 | string:
10 | headers:
11 | Content-Type:
12 | - application/xml
13 | Accept-Encoding:
14 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
15 | Accept:
16 | - "*/*"
17 | User-Agent:
18 | - Ruby
19 | response:
20 | status:
21 | code: 200
22 | message: OK
23 | headers:
24 | Content-Security-Policy-Report-Only:
25 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
26 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
27 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
28 | Content-Type:
29 | - application/xml;charset=utf-8
30 | Date:
31 | - Tue, 24 Nov 2020 22:00:03 GMT
32 | P3p:
33 | - CP="NON"
34 | Server:
35 | - Tableau
36 | Vary:
37 | - Accept-Encoding
38 | X-Content-Type-Options:
39 | - nosniff
40 | X-Tableau:
41 | - Tableau Server
42 | X-Ua-Compatible:
43 | - IE=Edge
44 | X-Xss-Protection:
45 | - 1; mode=block
46 | Content-Length:
47 | - '327'
48 | Connection:
49 | - keep-alive
50 | body:
51 | encoding: ASCII-8BIT
52 | string:
56 | http_version:
57 | recorded_at: Tue, 24 Nov 2020 22:00:03 GMT
58 | - request:
59 | method: post
60 | uri: http://TABLEAU_HOST/api/3.1/sites
61 | body:
62 | encoding: UTF-8
63 | string:
64 | headers:
65 | Content-Type:
66 | - application/xml
67 | X-Tableau-Auth:
68 | - qvSyAq_XTTiIrqf4GQBUiw|xQZk64kPTOCyxdzlPYwe1P3pg4C58z3H
69 | Accept-Encoding:
70 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
71 | Accept:
72 | - "*/*"
73 | User-Agent:
74 | - Ruby
75 | response:
76 | status:
77 | code: 201
78 | message: Created
79 | headers:
80 | Content-Security-Policy-Report-Only:
81 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
82 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
83 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
84 | Content-Type:
85 | - application/xml;charset=utf-8
86 | Date:
87 | - Tue, 24 Nov 2020 22:00:03 GMT
88 | Location:
89 | - "/api/3.1/sites/33548c67-b955-49f0-8e0a-8d6f6ee207e9"
90 | P3p:
91 | - CP="NON"
92 | Server:
93 | - Tableau
94 | X-Content-Type-Options:
95 | - nosniff
96 | X-Tableau:
97 | - Tableau Server
98 | X-Ua-Compatible:
99 | - IE=Edge
100 | X-Xss-Protection:
101 | - 1; mode=block
102 | Content-Length:
103 | - '549'
104 | Connection:
105 | - keep-alive
106 | body:
107 | encoding: UTF-8
108 | string:
114 | http_version:
115 | recorded_at: Tue, 24 Nov 2020 22:00:03 GMT
116 | - request:
117 | method: post
118 | uri: http://TABLEAU_HOST/api/3.1/sites
119 | body:
120 | encoding: UTF-8
121 | string:
122 | headers:
123 | Content-Type:
124 | - application/xml
125 | X-Tableau-Auth:
126 | - qvSyAq_XTTiIrqf4GQBUiw|xQZk64kPTOCyxdzlPYwe1P3pg4C58z3H
127 | Accept-Encoding:
128 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
129 | Accept:
130 | - "*/*"
131 | User-Agent:
132 | - Ruby
133 | response:
134 | status:
135 | code: 409
136 | message: Conflict
137 | headers:
138 | Content-Security-Policy-Report-Only:
139 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
140 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
141 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
142 | Content-Type:
143 | - application/xml;charset=utf-8
144 | Date:
145 | - Tue, 24 Nov 2020 22:00:04 GMT
146 | P3p:
147 | - CP="NON"
148 | Server:
149 | - Tableau
150 | X-Content-Type-Options:
151 | - nosniff
152 | X-Tableau:
153 | - Tableau Server
154 | X-Ua-Compatible:
155 | - IE=Edge
156 | X-Xss-Protection:
157 | - 1; mode=block
158 | Content-Length:
159 | - '366'
160 | Connection:
161 | - keep-alive
162 | body:
163 | encoding: UTF-8
164 | string: Resource
167 | ConflictA site already exists with the content URL 'TestSite2'
168 | http_version:
169 | recorded_at: Tue, 24 Nov 2020 22:00:04 GMT
170 | - request:
171 | method: post
172 | uri: http://TABLEAU_HOST/api/3.1/auth/signin
173 | body:
174 | encoding: UTF-8
175 | string:
177 | headers:
178 | Content-Type:
179 | - application/xml
180 | Accept-Encoding:
181 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
182 | Accept:
183 | - "*/*"
184 | User-Agent:
185 | - Ruby
186 | response:
187 | status:
188 | code: 200
189 | message: OK
190 | headers:
191 | Content-Security-Policy-Report-Only:
192 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
193 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
194 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
195 | Content-Type:
196 | - application/xml;charset=utf-8
197 | Date:
198 | - Tue, 24 Nov 2020 22:00:04 GMT
199 | P3p:
200 | - CP="NON"
201 | Server:
202 | - Tableau
203 | Vary:
204 | - Accept-Encoding
205 | X-Content-Type-Options:
206 | - nosniff
207 | X-Tableau:
208 | - Tableau Server
209 | X-Ua-Compatible:
210 | - IE=Edge
211 | X-Xss-Protection:
212 | - 1; mode=block
213 | Content-Length:
214 | - '330'
215 | Connection:
216 | - keep-alive
217 | body:
218 | encoding: ASCII-8BIT
219 | string:
223 | http_version:
224 | recorded_at: Tue, 24 Nov 2020 22:00:04 GMT
225 | - request:
226 | method: get
227 | uri: http://TABLEAU_HOST/api/3.1/sites?pageNumber=1&pageSize=100
228 | body:
229 | encoding: US-ASCII
230 | string: ''
231 | headers:
232 | X-Tableau-Auth:
233 | - CGQsDHeZRG6Ntay3oguN8g|LokUHBbcO1MXyACKeZQ1mSETDz2HHlKR
234 | Accept-Encoding:
235 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
236 | Accept:
237 | - "*/*"
238 | User-Agent:
239 | - Ruby
240 | response:
241 | status:
242 | code: 200
243 | message: OK
244 | headers:
245 | Content-Security-Policy-Report-Only:
246 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
247 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
248 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
249 | Content-Type:
250 | - application/xml;charset=utf-8
251 | Date:
252 | - Tue, 24 Nov 2020 22:00:04 GMT
253 | P3p:
254 | - CP="NON"
255 | Server:
256 | - Tableau
257 | Vary:
258 | - Accept-Encoding
259 | X-Content-Type-Options:
260 | - nosniff
261 | X-Tableau:
262 | - Tableau Server
263 | X-Ua-Compatible:
264 | - IE=Edge
265 | X-Xss-Protection:
266 | - 1; mode=block
267 | Content-Length:
268 | - '3805'
269 | Connection:
270 | - keep-alive
271 | body:
272 | encoding: ASCII-8BIT
273 | string:
287 | http_version:
288 | recorded_at: Tue, 24 Nov 2020 22:00:04 GMT
289 | - request:
290 | method: post
291 | uri: http://TABLEAU_HOST/api/3.1/auth/signin
292 | body:
293 | encoding: UTF-8
294 | string:
296 | headers:
297 | Content-Type:
298 | - application/xml
299 | Accept-Encoding:
300 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
301 | Accept:
302 | - "*/*"
303 | User-Agent:
304 | - Ruby
305 | response:
306 | status:
307 | code: 200
308 | message: OK
309 | headers:
310 | Content-Security-Policy-Report-Only:
311 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
312 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
313 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
314 | Content-Type:
315 | - application/xml;charset=utf-8
316 | Date:
317 | - Tue, 24 Nov 2020 22:00:05 GMT
318 | P3p:
319 | - CP="NON"
320 | Server:
321 | - Tableau
322 | Vary:
323 | - Accept-Encoding
324 | X-Content-Type-Options:
325 | - nosniff
326 | X-Tableau:
327 | - Tableau Server
328 | X-Ua-Compatible:
329 | - IE=Edge
330 | X-Xss-Protection:
331 | - 1; mode=block
332 | Content-Length:
333 | - '330'
334 | Connection:
335 | - keep-alive
336 | body:
337 | encoding: ASCII-8BIT
338 | string:
342 | http_version:
343 | recorded_at: Tue, 24 Nov 2020 22:00:05 GMT
344 | - request:
345 | method: delete
346 | uri: http://TABLEAU_HOST/api/3.1/sites/33548c67-b955-49f0-8e0a-8d6f6ee207e9
347 | body:
348 | encoding: US-ASCII
349 | string: ''
350 | headers:
351 | X-Tableau-Auth:
352 | - S0H_LNZ4SIGtHH8BJ29oUw|saC6UTopyudpr6p6TZ6ycFSnezGWeTvr
353 | Accept-Encoding:
354 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
355 | Accept:
356 | - "*/*"
357 | User-Agent:
358 | - Ruby
359 | response:
360 | status:
361 | code: 204
362 | message: No Content
363 | headers:
364 | Content-Security-Policy-Report-Only:
365 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
366 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
367 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
368 | Date:
369 | - Tue, 24 Nov 2020 22:00:05 GMT
370 | P3p:
371 | - CP="NON"
372 | Server:
373 | - Tableau
374 | X-Content-Type-Options:
375 | - nosniff
376 | X-Tableau:
377 | - Tableau Server
378 | X-Ua-Compatible:
379 | - IE=Edge
380 | X-Xss-Protection:
381 | - 1; mode=block
382 | Connection:
383 | - keep-alive
384 | body:
385 | encoding: UTF-8
386 | string: ''
387 | http_version:
388 | recorded_at: Tue, 24 Nov 2020 22:00:05 GMT
389 | - request:
390 | method: delete
391 | uri: http://TABLEAU_HOST/api/3.1/sites/does-not-exist
392 | body:
393 | encoding: US-ASCII
394 | string: ''
395 | headers:
396 | X-Tableau-Auth:
397 | - qvSyAq_XTTiIrqf4GQBUiw|xQZk64kPTOCyxdzlPYwe1P3pg4C58z3H
398 | Accept-Encoding:
399 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
400 | Accept:
401 | - "*/*"
402 | User-Agent:
403 | - Ruby
404 | response:
405 | status:
406 | code: 404
407 | message: Not Found
408 | headers:
409 | Content-Security-Policy-Report-Only:
410 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
411 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
412 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
413 | Content-Type:
414 | - application/xml;charset=utf-8
415 | Date:
416 | - Tue, 24 Nov 2020 22:00:05 GMT
417 | P3p:
418 | - CP="NON"
419 | Server:
420 | - Tableau
421 | X-Content-Type-Options:
422 | - nosniff
423 | X-Tableau:
424 | - Tableau Server
425 | X-Ua-Compatible:
426 | - IE=Edge
427 | X-Xss-Protection:
428 | - 1; mode=block
429 | Content-Length:
430 | - '354'
431 | Connection:
432 | - keep-alive
433 | body:
434 | encoding: UTF-8
435 | string: Resource
438 | Not FoundSite 'does-not-exist' could not be found.
439 | http_version:
440 | recorded_at: Tue, 24 Nov 2020 22:00:06 GMT
441 | recorded_with: VCR 3.0.3
442 |
--------------------------------------------------------------------------------
/spec/fixtures/vcr_cassettes/users.yml:
--------------------------------------------------------------------------------
1 | ---
2 | http_interactions:
3 | - request:
4 | method: post
5 | uri: http://TABLEAU_HOST/api/3.1/auth/signin
6 | body:
7 | encoding: UTF-8
8 | string:
10 | headers:
11 | Content-Type:
12 | - application/xml
13 | Accept-Encoding:
14 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
15 | Accept:
16 | - "*/*"
17 | User-Agent:
18 | - Ruby
19 | response:
20 | status:
21 | code: 200
22 | message: OK
23 | headers:
24 | Content-Security-Policy-Report-Only:
25 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
26 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
27 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
28 | Content-Type:
29 | - application/xml;charset=utf-8
30 | Date:
31 | - Tue, 24 Nov 2020 22:00:06 GMT
32 | P3p:
33 | - CP="NON"
34 | Server:
35 | - Tableau
36 | Vary:
37 | - Accept-Encoding
38 | X-Content-Type-Options:
39 | - nosniff
40 | X-Tableau:
41 | - Tableau Server
42 | X-Ua-Compatible:
43 | - IE=Edge
44 | X-Xss-Protection:
45 | - 1; mode=block
46 | Content-Length:
47 | - '331'
48 | Connection:
49 | - keep-alive
50 | body:
51 | encoding: ASCII-8BIT
52 | string:
56 | http_version:
57 | recorded_at: Tue, 24 Nov 2020 22:00:06 GMT
58 | - request:
59 | method: post
60 | uri: http://TABLEAU_HOST/api/3.1/sites/fb50c166-f809-44e0-995e-2cf56ceffbc0/users
61 | body:
62 | encoding: UTF-8
63 | string:
64 | headers:
65 | Content-Type:
66 | - application/xml
67 | X-Tableau-Auth:
68 | - DaJ94DvRTXG9DmnQtMxo_Q|dewvIZbNaq6Xg8pSWZndgbySnBJcV8jv
69 | Accept-Encoding:
70 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
71 | Accept:
72 | - "*/*"
73 | User-Agent:
74 | - Ruby
75 | response:
76 | status:
77 | code: 201
78 | message: Created
79 | headers:
80 | Content-Security-Policy-Report-Only:
81 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
82 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
83 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
84 | Content-Type:
85 | - application/xml;charset=utf-8
86 | Date:
87 | - Tue, 24 Nov 2020 22:00:06 GMT
88 | Location:
89 | - "/api/3.1/sites/fb50c166-f809-44e0-995e-2cf56ceffbc0/users/ce93aaeb-fb25-4fbd-a573-74b549f5e95b"
90 | P3p:
91 | - CP="NON"
92 | Server:
93 | - Tableau
94 | X-Content-Type-Options:
95 | - nosniff
96 | X-Tableau:
97 | - Tableau Server
98 | X-Ua-Compatible:
99 | - IE=Edge
100 | X-Xss-Protection:
101 | - 1; mode=block
102 | Content-Length:
103 | - '371'
104 | Connection:
105 | - keep-alive
106 | body:
107 | encoding: UTF-8
108 | string:
112 | http_version:
113 | recorded_at: Tue, 24 Nov 2020 22:00:06 GMT
114 | - request:
115 | method: get
116 | uri: http://TABLEAU_HOST/api/3.1/sites/fb50c166-f809-44e0-995e-2cf56ceffbc0/users?pageNumber=1&pageSize=100
117 | body:
118 | encoding: US-ASCII
119 | string: ''
120 | headers:
121 | X-Tableau-Auth:
122 | - DaJ94DvRTXG9DmnQtMxo_Q|dewvIZbNaq6Xg8pSWZndgbySnBJcV8jv
123 | Accept-Encoding:
124 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
125 | Accept:
126 | - "*/*"
127 | User-Agent:
128 | - Ruby
129 | response:
130 | status:
131 | code: 200
132 | message: OK
133 | headers:
134 | Content-Security-Policy-Report-Only:
135 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
136 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
137 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
138 | Content-Type:
139 | - application/xml;charset=utf-8
140 | Date:
141 | - Tue, 24 Nov 2020 22:00:22 GMT
142 | P3p:
143 | - CP="NON"
144 | Server:
145 | - Tableau
146 | Vary:
147 | - Accept-Encoding
148 | X-Content-Type-Options:
149 | - nosniff
150 | X-Tableau:
151 | - Tableau Server
152 | X-Ua-Compatible:
153 | - IE=Edge
154 | X-Xss-Protection:
155 | - 1; mode=block
156 | Content-Length:
157 | - '419'
158 | Connection:
159 | - keep-alive
160 | body:
161 | encoding: ASCII-8BIT
162 | string:
170 | http_version:
171 | recorded_at: Tue, 24 Nov 2020 22:00:22 GMT
172 | - request:
173 | method: get
174 | uri: http://TABLEAU_HOST/api/3.1/sites/fb50c166-f809-44e0-995e-2cf56ceffbc0/users/ce93aaeb-fb25-4fbd-a573-74b549f5e95b
175 | body:
176 | encoding: US-ASCII
177 | string: ''
178 | headers:
179 | X-Tableau-Auth:
180 | - DaJ94DvRTXG9DmnQtMxo_Q|dewvIZbNaq6Xg8pSWZndgbySnBJcV8jv
181 | Accept-Encoding:
182 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
183 | Accept:
184 | - "*/*"
185 | User-Agent:
186 | - Ruby
187 | response:
188 | status:
189 | code: 200
190 | message: OK
191 | headers:
192 | Content-Security-Policy-Report-Only:
193 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
194 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
195 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
196 | Content-Type:
197 | - application/xml;charset=utf-8
198 | Date:
199 | - Tue, 24 Nov 2020 22:00:22 GMT
200 | P3p:
201 | - CP="NON"
202 | Server:
203 | - Tableau
204 | Vary:
205 | - Accept-Encoding
206 | X-Content-Type-Options:
207 | - nosniff
208 | X-Tableau:
209 | - Tableau Server
210 | X-Ua-Compatible:
211 | - IE=Edge
212 | X-Xss-Protection:
213 | - 1; mode=block
214 | Content-Length:
215 | - '299'
216 | Connection:
217 | - keep-alive
218 | body:
219 | encoding: ASCII-8BIT
220 | string:
225 | http_version:
226 | recorded_at: Tue, 24 Nov 2020 22:00:22 GMT
227 | - request:
228 | method: put
229 | uri: http://TABLEAU_HOST/api/3.1/sites/fb50c166-f809-44e0-995e-2cf56ceffbc0/users/ce93aaeb-fb25-4fbd-a573-74b549f5e95b
230 | body:
231 | encoding: UTF-8
232 | string:
233 | headers:
234 | Content-Type:
235 | - application/xml
236 | X-Tableau-Auth:
237 | - DaJ94DvRTXG9DmnQtMxo_Q|dewvIZbNaq6Xg8pSWZndgbySnBJcV8jv
238 | Accept-Encoding:
239 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
240 | Accept:
241 | - "*/*"
242 | User-Agent:
243 | - Ruby
244 | response:
245 | status:
246 | code: 200
247 | message: OK
248 | headers:
249 | Content-Security-Policy-Report-Only:
250 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
251 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
252 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
253 | Content-Type:
254 | - application/xml;charset=utf-8
255 | Date:
256 | - Tue, 24 Nov 2020 22:00:22 GMT
257 | P3p:
258 | - CP="NON"
259 | Server:
260 | - Tableau
261 | Vary:
262 | - Accept-Encoding
263 | X-Content-Type-Options:
264 | - nosniff
265 | X-Tableau:
266 | - Tableau Server
267 | X-Ua-Compatible:
268 | - IE=Edge
269 | X-Xss-Protection:
270 | - 1; mode=block
271 | Content-Length:
272 | - '207'
273 | Connection:
274 | - keep-alive
275 | body:
276 | encoding: ASCII-8BIT
277 | string:
280 | http_version:
281 | recorded_at: Tue, 24 Nov 2020 22:00:22 GMT
282 | - request:
283 | method: get
284 | uri: http://TABLEAU_HOST/api/3.1/sites/fb50c166-f809-44e0-995e-2cf56ceffbc0/users/foo
285 | body:
286 | encoding: US-ASCII
287 | string: ''
288 | headers:
289 | X-Tableau-Auth:
290 | - DaJ94DvRTXG9DmnQtMxo_Q|dewvIZbNaq6Xg8pSWZndgbySnBJcV8jv
291 | Accept-Encoding:
292 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
293 | Accept:
294 | - "*/*"
295 | User-Agent:
296 | - Ruby
297 | response:
298 | status:
299 | code: 404
300 | message: Not Found
301 | headers:
302 | Content-Security-Policy-Report-Only:
303 | - 'connect-src * https://*.tiles.mapbox.com https://api.mapbox.com; default-src
304 | blob:; font-src * data:; frame-src * data:; img-src * data: blob:; object-src
305 | data:; report-uri /vizql/csp-report; script-src * blob:; style-src * ''unsafe-inline'''
306 | Content-Type:
307 | - application/xml;charset=utf-8
308 | Date:
309 | - Tue, 24 Nov 2020 22:00:22 GMT
310 | P3p:
311 | - CP="NON"
312 | Server:
313 | - Tableau
314 | X-Content-Type-Options:
315 | - nosniff
316 | X-Tableau:
317 | - Tableau Server
318 | X-Ua-Compatible:
319 | - IE=Edge
320 | X-Xss-Protection:
321 | - 1; mode=block
322 | Content-Length:
323 | - '343'
324 | Connection:
325 | - keep-alive
326 | body:
327 | encoding: UTF-8
328 | string: Resource
331 | Not FoundUser 'foo' could not be found.
332 | http_version:
333 | recorded_at: Tue, 24 Nov 2020 22:00:23 GMT
334 | recorded_with: VCR 3.0.3
335 |
--------------------------------------------------------------------------------
/spec/fixtures/workbooks/test.twbx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/civisanalytics/tableau_api/2e4b44381c3cb295ac933948733be3b8d7f6f02f/spec/fixtures/workbooks/test.twbx
--------------------------------------------------------------------------------
/spec/resources/auth_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'spec_helper'
4 |
5 | describe TableauApi::Resources::Auth, vcr: { cassette_name: 'auth' } do
6 | let(:client) do
7 | TableauApi.new(
8 | host: ENV.fetch('TABLEAU_HOST', nil),
9 | site_name: 'Default',
10 | username: ENV.fetch('TABLEAU_ADMIN_USERNAME', nil),
11 | password: ENV.fetch('TABLEAU_ADMIN_PASSWORD', nil)
12 | )
13 | end
14 |
15 | # http://onlinehelp.tableau.com/v9.0/api/rest_api/en-us/help.htm#REST/rest_api_concepts_auth.htm%3FTocPath%3DConcepts%7C_____3
16 | # http://onlinehelp.tableau.com/v9.0/api/rest_api/en-us/help.htm#REST/rest_api_ref.htm#Sign_In%3FTocPath%3DAPI%2520Reference%7C_____51
17 | it 'returns an instance of TableauApi::Resources::Auth' do
18 | client = TableauApi.new(host: 'tableau.domain.tld', site_name: 'Default', username: 'ExampleUsername', password: 'ExamplePassword')
19 | expect(client.auth).to be_an_instance_of(TableauApi::Resources::Auth)
20 | end
21 |
22 | it 'fails appropriately with a bad username or password' do
23 | client = TableauApi.new(host: ENV.fetch('TABLEAU_HOST', nil), site_name: 'Default', username: 'foo', password: 'bar')
24 | expect(client.auth.sign_in).to be false
25 | end
26 |
27 | it 'automatically signs in to get the token' do
28 | expect(client.auth.token).to be_a_token
29 | end
30 |
31 | it 'sucessfully signs in with a correct username and password' do
32 | expect(client.auth.sign_in).to be true
33 | expect(client.auth.token).to be_a_token
34 | expect(client.auth.site_id).to be_a_tableau_id
35 | expect(client.auth.user_id).to be_a_tableau_id
36 | end
37 |
38 | it 'signs into a different site' do
39 | client = TableauApi.new(
40 | host: ENV.fetch('TABLEAU_HOST', nil),
41 | site_name: 'TestSite',
42 | username: ENV.fetch('TABLEAU_ADMIN_USERNAME', nil),
43 | password: ENV.fetch('TABLEAU_ADMIN_PASSWORD', nil)
44 | )
45 | expect(client.auth.sign_in).to be true
46 | expect(client.auth.token).to be_a_token
47 | expect(client.auth.site_id).to be_a_tableau_id
48 | expect(client.auth.user_id).to be_a_tableau_id
49 | end
50 |
51 | it 'does not sign in if already signed in' do
52 | expect(client.auth.sign_in).to be true
53 | token = client.auth.token
54 |
55 | expect(client.auth.sign_in).to be true
56 | expect(client.auth.token).to eq token
57 | end
58 |
59 | describe '.sign_out' do
60 | # http://onlinehelp.tableau.com/v9.0/api/rest_api/en-us/help.htm#REST/rest_api_ref.htm#Sign_Out
61 | it 'can sign out' do
62 | expect(client.auth.sign_in).to be true
63 | expect(client.auth.token).to be_a_token
64 | expect(client.auth.site_id).to be_a_tableau_id
65 | expect(client.auth.user_id).to be_a_tableau_id
66 | expect(client.auth.sign_out).to be true
67 | # accessing the token automatically signs in
68 | expect(client.auth.instance_variable_get('@token')).to be nil
69 | expect(client.auth.instance_variable_get('@site_id')).to be nil
70 | expect(client.auth.instance_variable_get('@user_id')).to be nil
71 | end
72 |
73 | it 'can sign out with a bad token' do
74 | expect(client.auth.sign_in).to be true
75 | client.auth.instance_variable_set('@token', 'foo')
76 | expect(client.auth.sign_out).to be true
77 | expect(client.auth.instance_variable_get('@token')).to be nil
78 | expect(client.auth.instance_variable_get('@site_id')).to be nil
79 | expect(client.auth.instance_variable_get('@user_id')).to be nil
80 | end
81 | end
82 |
83 | describe '.trusted_ticket' do
84 | it 'can get a trusted ticket' do
85 | client = TableauApi.new(host: ENV.fetch('TABLEAU_HOST', nil), site_name: 'Default', username: 'test')
86 | expect(client.auth.trusted_ticket).to be_a_trusted_ticket
87 | end
88 |
89 | it 'can get a trusted ticket for a non default site' do
90 | client = TableauApi.new(host: ENV.fetch('TABLEAU_HOST', nil), site_name: 'test', username: 'test_test')
91 | expect(client.auth.trusted_ticket).to be_a_trusted_ticket
92 | end
93 |
94 | it 'fails with a user not in a site' do
95 | client = TableauApi.new(host: ENV.fetch('TABLEAU_HOST', nil), site_name: 'test', username: 'test')
96 | expect(client.auth.trusted_ticket).to be nil
97 | end
98 |
99 | it 'fails with a bad user' do
100 | client = TableauApi.new(host: ENV.fetch('TABLEAU_HOST', nil), site_name: 'Default', username: 'invalid_user')
101 | expect(client.auth.trusted_ticket).to be nil
102 | end
103 |
104 | it 'fails with a bad site' do
105 | client = TableauApi.new(host: ENV.fetch('TABLEAU_HOST', nil), site_name: 'invalid_site', username: 'test')
106 | expect(client.auth.trusted_ticket).to be nil
107 | end
108 | end
109 | end
110 |
--------------------------------------------------------------------------------
/spec/resources/datasources_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'spec_helper'
4 | describe TableauApi::Resources::Datasources, vcr: { cassette_name: 'datasources' } do
5 | include_context 'tableau client'
6 |
7 | # https://onlinehelp.tableau.com/current/api/rest_api/en-us/help.htm#REST/rest_api_ref.htm#Query_Datasources%3FTocPath%3DAPI%2520Reference%7C_____48
8 |
9 | it 'can list datasources' do
10 | datasource = client.datasources.list.find do |d|
11 | d['name'] == 'test'
12 | end
13 | expect(datasource['id']).to be_a_tableau_id
14 | expect(datasource).to eq(
15 | 'id' => datasource['id'],
16 | 'name' => 'test',
17 | 'contentUrl' => 'test',
18 | 'createdAt' => datasource['createdAt'],
19 | 'updatedAt' => datasource['updatedAt'],
20 | 'owner' => { 'id' => datasource['owner']['id'] },
21 | 'project' => {
22 | 'id' => datasource['project']['id'],
23 | 'name' => datasource['project']['name']
24 | },
25 | 'type' => 'textscan',
26 | 'tags' => nil,
27 | 'isCertified' => 'false',
28 | 'webpageUrl' => datasource['webpageUrl']
29 | )
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/spec/resources/groups_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'spec_helper'
4 |
5 | describe TableauApi::Resources::Groups, vcr: { cassette_name: 'groups' } do
6 | include_context 'tableau client'
7 |
8 | let(:test_user_id) do
9 | user = client.users.list.find do |u|
10 | u['name'] == 'test'
11 | end
12 | user['id']
13 | end
14 |
15 | describe '#create' do
16 | # http://onlinehelp.tableau.com/v9.0/api/rest_api/en-us/help.htm#REST/rest_api_ref.htm#Create_Group
17 | it 'fails with a bad site_role' do
18 | expect { client.groups.create(name: 'test', default_site_role: 'foo') }.to raise_error('invalid default_site_role')
19 | end
20 |
21 | it 'can create a group in a site' do
22 | group = client.groups.create(name: 'testgroup')
23 | expect(group['id']).to be_a_tableau_id
24 | expect(group).to eq('id' => group['id'], 'name' => 'testgroup')
25 | end
26 | end
27 |
28 | describe '#list' do
29 | # http://onlinehelp.tableau.com/v9.0/api/rest_api/en-us/help.htm#REST/rest_api_ref.htm#Query_Groups
30 | it 'can list groups in a site' do
31 | sleep(15) if VCR.current_cassette.recording?
32 | group = find_group_by_name('testgroup')
33 | expect(group).to eq('id' => group['id'], 'name' => 'testgroup', 'domain' => { 'name' => 'local' })
34 | expect(group['id']).to be_a_tableau_id
35 | end
36 | end
37 |
38 | describe '#add_user' do
39 | it 'can add a user to a group' do
40 | group = find_group_by_name('testgroup')
41 | expect(client.groups.add_user(group_id: group['id'], user_id: test_user_id)).to be true
42 | end
43 | end
44 |
45 | describe '#users' do
46 | it 'can get the users in a group' do
47 | group = find_group_by_name('testgroup')
48 | users = client.groups.users(group_id: group['id'])
49 | expect(users.map { |u| u['id'] }.include?(test_user_id)).to be true
50 | end
51 | end
52 |
53 | describe '#remove_user' do
54 | it 'can remove a user from a group' do
55 | group = find_group_by_name('testgroup')
56 | expect(client.groups.remove_user(group_id: group['id'], user_id: test_user_id)).to be true
57 | end
58 | end
59 |
60 | def find_group_by_name(name)
61 | client.groups.list.find do |g|
62 | g['name'] == name
63 | end
64 | end
65 | end
66 |
--------------------------------------------------------------------------------
/spec/resources/jobs_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'spec_helper'
4 |
5 | describe TableauApi::Resources::Jobs, vcr: { cassette_name: 'jobs' } do
6 | include_context 'tableau client'
7 |
8 | def find_or_publish_workbook(name)
9 | project = client.projects.list.find { |p| p['name'] == 'test' }
10 | workbook = client.workbooks.list.find { |w| w['name'] == name }
11 |
12 | if workbook
13 | workbook = client.workbooks.find(workbook['id'])
14 | return workbook
15 | end
16 |
17 | client.workbooks.publish(
18 | name: name,
19 | project_id: project['id'],
20 | file: 'spec/fixtures/workbooks/test.twbx',
21 | overwrite: true
22 | )
23 | end
24 |
25 | describe '#list' do
26 | # https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_jobstasksschedules.htm#query_jobs
27 | it 'can list jobs in a site' do
28 | workbook = find_or_publish_workbook('test')
29 | client.workbooks.refresh(workbook_id: workbook['id'])
30 | jobs = client.jobs.list
31 | expect(jobs.to_a.last.keys).to include('createdAt', 'id', 'jobType', 'priority', 'status')
32 | end
33 |
34 | it 'can filter jobs in a site' do
35 | workbook = find_or_publish_workbook('test')
36 | client.workbooks.refresh(workbook_id: workbook['id'])
37 | jobs = client.jobs.list(query: 'filter=jobType:eq:refresh_extracts,status:eq:Success')
38 | expect(jobs.to_a).to be_empty
39 | end
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/spec/resources/projects_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'spec_helper'
4 |
5 | describe TableauApi::Resources::Projects, vcr: { cassette_name: 'projects' } do
6 | include_context 'tableau client'
7 |
8 | # http://onlinehelp.tableau.com/v9.0/api/rest_api/en-us/help.htm#REST/rest_api_ref.htm#Create_Project%3FTocPath%3DAPI%2520Reference%7C_____13
9 | it 'can create a project in a site' do
10 | project = client.projects.create(name: 'test_project')
11 | expect(project['id']).to be_a_tableau_id
12 | expect(project).to eq(
13 | 'id' => project['id'],
14 | 'name' => 'test_project',
15 | 'description' => '',
16 | 'contentPermissions' => 'ManagedByOwner',
17 | 'owner' => project['owner'],
18 | 'updatedAt' => project['updatedAt'],
19 | 'createdAt' => project['createdAt']
20 | )
21 | end
22 |
23 | it 'can list projects' do
24 | sleep(15) if VCR.current_cassette.recording?
25 | project = client.projects.list.find do |p|
26 | p['name'] == 'test_project'
27 | end
28 | expect(project['id']).to be_a_tableau_id
29 | expect(project).to eq(
30 | 'id' => project['id'],
31 | 'name' => 'test_project',
32 | 'description' => '',
33 | 'contentPermissions' => 'ManagedByOwner',
34 | 'owner' => project['owner'],
35 | 'updatedAt' => project['updatedAt'],
36 | 'createdAt' => project['createdAt']
37 | )
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/spec/resources/sites_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'spec_helper'
4 |
5 | describe TableauApi::Resources::Sites, vcr: { cassette_name: 'sites' } do
6 | include_context 'tableau client'
7 |
8 | let(:default_client) do
9 | TableauApi.new(
10 | host: ENV.fetch('TABLEAU_HOST', nil),
11 | site_name: 'Default',
12 | username: ENV.fetch('TABLEAU_ADMIN_USERNAME', nil),
13 | password: ENV.fetch('TABLEAU_ADMIN_PASSWORD', nil)
14 | )
15 | end
16 |
17 | describe '#create' do
18 | it 'can create a site' do
19 | site = default_client.sites.create(name: 'Test Site 2', content_url: 'TestSite2', admin_mode: 'ContentAndUsers')
20 | expect(site['id']).to be_a_tableau_id
21 | expect(site).to eq(
22 | 'id' => site['id'],
23 | 'name' => 'Test Site 2',
24 | 'contentUrl' => 'TestSite2',
25 | 'adminMode' => 'ContentAndUsers',
26 | 'state' => 'Active',
27 | 'disableSubscriptions' => 'false',
28 | 'cacheWarmupEnabled' => 'true',
29 | 'commentingEnabled' => 'true',
30 | 'guestAccessEnabled' => 'true',
31 | 'revisionHistoryEnabled' => 'true',
32 | 'revisionLimit' => '25',
33 | 'subscribeOthersEnabled' => 'true'
34 | )
35 | end
36 |
37 | it 'raises an error if fails to create a site' do
38 | expect do
39 | default_client.sites.create(name: 'Test Site 3', content_url: 'TestSite2', admin_mode: 'ContentAndUsers')
40 | end.to raise_error(TableauApi::TableauError)
41 | end
42 | end
43 |
44 | describe '#list' do
45 | # http://onlinehelp.tableau.com/v9.0/api/rest_api/en-us/help.htm#REST/rest_api_ref.htm#Query_Sites%3FTocPath%3DAPI%2520Reference%7C_____40
46 | it 'can list sites' do
47 | sites = client.sites.list.to_a
48 |
49 | site = sites.find do |s|
50 | s['contentUrl'] == 'TestSite'
51 | end
52 |
53 | expect(site['id']).to be_a_tableau_id
54 | expect(site).to eq(
55 | 'id' => site['id'],
56 | 'name' => 'TestSite',
57 | 'contentUrl' => 'TestSite',
58 | 'adminMode' => 'ContentAndUsers',
59 | 'state' => 'Active',
60 | 'cacheWarmupEnabled' => 'true',
61 | 'commentingEnabled' => 'true',
62 | 'guestAccessEnabled' => 'false',
63 | 'revisionHistoryEnabled' => 'true',
64 | 'revisionLimit' => '25',
65 | 'subscribeOthersEnabled' => 'true',
66 | 'disableSubscriptions' => 'true'
67 | )
68 | end
69 | end
70 |
71 | describe '#delete' do
72 | let(:site_client) do
73 | TableauApi.new(
74 | host: ENV.fetch('TABLEAU_HOST', nil),
75 | site_name: 'TestSite2',
76 | username: ENV.fetch('TABLEAU_ADMIN_USERNAME', nil),
77 | password: ENV.fetch('TABLEAU_ADMIN_PASSWORD', nil)
78 | )
79 | end
80 |
81 | it 'can delete a site' do
82 | site = site_client.sites.list.to_a.find do |s|
83 | s['name'] == 'Test Site 2'
84 | end
85 | expect(site_client.sites.delete(site_id: site['id'])).to be true
86 | end
87 |
88 | it 'raises an error if fails to delete a site' do
89 | expect do
90 | default_client.sites.delete(site_id: 'does-not-exist')
91 | end.to raise_error(TableauApi::TableauError)
92 | end
93 | end
94 | end
95 |
--------------------------------------------------------------------------------
/spec/resources/users_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'spec_helper'
4 |
5 | describe TableauApi::Resources::Users, vcr: { cassette_name: 'users' } do
6 | include_context 'tableau client'
7 |
8 | describe '#create' do
9 | # http://onlinehelp.tableau.com/v9.0/api/rest_api/en-us/help.htm#REST/rest_api_ref.htm#Add_User_to_Site%3FTocPath%3DAPI%2520Reference%7C_____7
10 | it 'can create a user in a site' do
11 | user = client.users.create(username: 'test', site_role: 'ExplorerCanPublish')
12 | expect(user['id']).to be_a_tableau_id
13 | expect(user).to eq(
14 | 'id' => user['id'],
15 | 'name' => 'test',
16 | 'siteRole' => 'ExplorerCanPublish',
17 | 'externalAuthUserId' => '',
18 | 'authSetting' => 'ServerDefault'
19 | )
20 | end
21 |
22 | it 'fails with a bad site_role' do
23 | expect { client.users.create(username: 'test', site_role: 'foo') }.to raise_error('invalid site_role')
24 | end
25 | end
26 |
27 | describe '#list' do
28 | # http://onlinehelp.tableau.com/v9.0/api/rest_api/en-us/help.htm#REST/rest_api_ref.htm#Get_Users_on_Site
29 | it 'can list users in a site' do
30 | sleep(15) if VCR.current_cassette.recording?
31 | user = client.users.list.find do |u|
32 | u['name'] == 'test'
33 | end
34 | expect(user['id']).to be_a_tableau_id
35 | expect(user).to eq('id' => user['id'], 'name' => 'test', 'siteRole' => 'ExplorerCanPublish', 'externalAuthUserId' => '')
36 | end
37 | end
38 |
39 | describe '#update_user' do
40 | # https://onlinehelp.tableau.com/current/api/rest_api/en-us/help.htm#REST/rest_api_ref.htm#Update_User
41 | it 'can change the site role of a user in a site' do
42 | user = client.users.list.find do |u|
43 | u['name'] == 'test'
44 | end
45 | expect(user['siteRole']).to eq('ExplorerCanPublish')
46 | user_after_change = client.users.update_user(user_id: user['id'], site_role: 'Explorer')
47 | expect(user_after_change['siteRole']).to eq('Explorer')
48 | end
49 |
50 | it 'raises an error if the site role is not valid' do
51 | user = client.users.list.find do |u|
52 | u['name'] == 'test'
53 | end
54 | expect do
55 | client.users.update_user(user_id: user['id'], site_role: 'foo')
56 | end.to raise_error(RuntimeError, 'invalid site_role')
57 | end
58 |
59 | it 'raises an error if the user cannot be found' do
60 | expect do
61 | client.users.update_user(user_id: 'foo', site_role: 'Viewer')
62 | end.to raise_error(RuntimeError, 'failed to find user')
63 | end
64 | end
65 | end
66 |
--------------------------------------------------------------------------------
/spec/resources/workbooks_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'spec_helper'
4 | require 'tempfile'
5 | require 'chunky_png'
6 |
7 | describe TableauApi::Resources::Workbooks, vcr: { cassette_name: 'workbooks' } do
8 | include_context 'tableau client'
9 |
10 | def find_or_publish_workbook(name)
11 | project = client.projects.list.find { |p| p['name'] == 'test' }
12 | workbook = client.workbooks.list.find { |w| w['name'] == name }
13 |
14 | if workbook
15 | workbook = client.workbooks.find(workbook['id'])
16 | return workbook
17 | end
18 |
19 | client.workbooks.publish(
20 | name: name,
21 | project_id: project['id'],
22 | file: 'spec/fixtures/workbooks/test.twbx',
23 | overwrite: true
24 | )
25 | end
26 |
27 | # http://onlinehelp.tableau.com/v9.0/api/rest_api/en-us/help.htm#REST/rest_api_ref.htm#Publish_Workbook%3FTocPath%3DAPI%2520Reference%7C_____31
28 | # Workbooks created in a later version of Tableau Desktop cannot be published to earlier versions of Tableau Server.
29 | # - http://kb.tableau.com/articles/knowledgebase/desktop-and-server-compatibility
30 | describe '#publish' do
31 | it 'can publish a twbx workbook in project in a site' do
32 | workbook_name = 'testpublish'
33 | workbook = find_or_publish_workbook(workbook_name)
34 |
35 | expect(workbook['id']).to be_a_tableau_id
36 | expect(workbook['owner']['id']).to be_a_tableau_id
37 |
38 | expect(workbook).to eq(
39 | 'id' => workbook['id'],
40 | 'name' => workbook_name,
41 | 'contentUrl' => workbook_name,
42 | 'showTabs' => 'false',
43 | 'project' => workbook['project'],
44 | 'owner' => workbook['owner'],
45 | 'tags' => nil,
46 | 'views' => workbook['views'],
47 | 'size' => '1',
48 | 'createdAt' => workbook['createdAt'],
49 | 'updatedAt' => workbook['updatedAt'],
50 | 'webpageUrl' => workbook['webpageUrl']
51 | )
52 | end
53 |
54 | it 'raises an exception', vcr: { cassette_name: 'workbooks', match_requests_on: %i[path query] } do
55 | ex = expect do
56 | client.workbooks.publish(
57 | name: 'tableau_api test',
58 | project_id: 'foo',
59 | file: 'spec/fixtures/workbooks/test.twbx',
60 | overwrite: true
61 | )
62 | end
63 |
64 | ex.to raise_error(TableauApi::TableauError) do |e|
65 | expect(e.message).to eq "404005: Resource Not Found; Project 'foo' could not be found."
66 | expect(e.http_response_code).to eq '404'
67 | expect(e.error_code).to eq '404005'
68 | expect(e.summary).to eq 'Resource Not Found'
69 | expect(e.detail).to eq "Project 'foo' could not be found."
70 | end
71 | end
72 | end
73 |
74 | describe '#add_permissions' do
75 | # http://onlinehelp.tableau.com/v9.0/api/rest_api/en-us/help.htm#REST/rest_api_ref.htm#Add_Workbook_Permissions%3FTocPath%3DAPI%2520Reference%7C_____9
76 | it 'can add user permissions to a workbook' do
77 | workbook = find_or_publish_workbook('testpublish')
78 | expect(client.workbooks.add_permissions(
79 | workbook_id: workbook['id'],
80 | user_id: admin_user['id'],
81 | capabilities: { Read: true, ChangePermissions: false }
82 | )).to be true
83 | end
84 |
85 | # http://onlinehelp.tableau.com/v9.0/api/rest_api/en-us/help.htm#REST/rest_api_ref.htm#Add_Workbook_Permissions%3FTocPath%3DAPI%2520Reference%7C_____9
86 | it 'can add group permissions to a workbook' do
87 | workbook = find_or_publish_workbook('testpublish')
88 | expect(client.workbooks.add_permissions(
89 | workbook_id: workbook['id'],
90 | group_id: test_group['id'],
91 | capabilities: { Read: true, ChangePermissions: false }
92 | )).to be true
93 | end
94 |
95 | it 'requires a user or a group id' do
96 | expect do
97 | client.workbooks.add_permissions(
98 | workbook_id: '1',
99 | capabilities: { Read: true }
100 | )
101 | end.to raise_error(/must specify user_id or group_id/)
102 | end
103 |
104 | it 'does not accept both a user and a group id' do
105 | expect do
106 | client.workbooks.add_permissions(
107 | workbook_id: '1',
108 | user_id: '1',
109 | group_id: '2',
110 | capabilities: { Read: true }
111 | )
112 | end.to raise_error(/cannot specify user_id and group_id simultaneously/)
113 | end
114 | end
115 |
116 | describe '#delete_permissions' do
117 | it 'can delete a user permission' do
118 | workbook = find_or_publish_workbook('testpublish')
119 | client.workbooks.add_permissions(
120 | workbook_id: workbook['id'],
121 | user_id: admin_user['id'],
122 | capabilities: { Read: true }
123 | )
124 | expect(client.workbooks.delete_permissions(
125 | workbook_id: workbook['id'],
126 | user_id: admin_user['id'],
127 | capability: 'Read',
128 | capability_mode: 'ALLOW'
129 | )).to be true
130 | end
131 |
132 | it 'can delete a group permission' do
133 | workbook = find_or_publish_workbook('testpublish')
134 | expect(client.workbooks.delete_permissions(
135 | workbook_id: workbook['id'],
136 | group_id: test_group['id'],
137 | capability: 'Read',
138 | capability_mode: 'ALLOW'
139 | )).to be true
140 | end
141 |
142 | it 'accepts a symbol as a permission' do
143 | workbook = find_or_publish_workbook('testpublish')
144 | client.workbooks.add_permissions(
145 | workbook_id: workbook['id'],
146 | group_id: all_users_group['id'],
147 | capabilities: { ExportImage: true }
148 | )
149 | expect(client.workbooks.delete_permissions(
150 | workbook_id: workbook['id'],
151 | group_id: all_users_group['id'],
152 | capability: :ExportImage,
153 | capability_mode: :ALLOW
154 | )).to be true
155 | end
156 |
157 | it 'requires a user or a group id' do
158 | expect do
159 | client.workbooks.delete_permissions(
160 | workbook_id: '1',
161 | capability: 'READ',
162 | capability_mode: 'ALLOW'
163 | )
164 | end.to raise_error(/must specify user_id or group_id/)
165 | end
166 |
167 | it 'does not accept both a user and a group id' do
168 | expect do
169 | client.workbooks.delete_permissions(
170 | workbook_id: '1',
171 | user_id: '1',
172 | group_id: '2',
173 | capability: 'READ',
174 | capability_mode: 'ALLOW'
175 | )
176 | end.to raise_error(/cannot specify user_id and group_id simultaneously/)
177 | end
178 | end
179 |
180 | describe '#permissions' do
181 | it 'can retrieve multiple permissions for a workbook' do
182 | workbook = find_or_publish_workbook('testpublish')
183 | expected = [{
184 | grantee_type: 'group',
185 | grantee_id: all_users_group['id'],
186 | capabilities: {
187 | Read: true,
188 | ShareView: true,
189 | ViewUnderlyingData: true,
190 | Filter: true,
191 | Write: true
192 | }
193 | }, {
194 | grantee_type: 'group',
195 | grantee_id: test_group['id'],
196 | capabilities: {
197 | ChangePermissions: false
198 | }
199 | }, {
200 | grantee_type: 'user',
201 | grantee_id: admin_user['id'],
202 | capabilities: {
203 | ChangePermissions: false
204 | }
205 | }]
206 | actual = client.workbooks.permissions(workbook_id: workbook['id'])
207 | expect(actual).to eq expected
208 | end
209 |
210 | it 'returns an array for even a single permission' do
211 | workbook = find_or_publish_workbook('testpublish')
212 |
213 | # use up the VCR recording from the previous spec
214 | client.workbooks.permissions(workbook_id: workbook['id'])
215 |
216 | # remove most of the remaining permissions
217 | %w[ExportData ViewComments AddComment].each do |p|
218 | client.workbooks.delete_permissions(
219 | workbook_id: workbook['id'],
220 | group_id: all_users_group['id'],
221 | capability: p,
222 | capability_mode: 'ALLOW'
223 | )
224 | end
225 | client.workbooks.delete_permissions(
226 | workbook_id: workbook['id'],
227 | group_id: test_group['id'],
228 | capability: :ChangePermissions,
229 | capability_mode: 'DENY'
230 | )
231 | client.workbooks.delete_permissions(
232 | workbook_id: workbook['id'],
233 | user_id: admin_user['id'],
234 | capability: 'ChangePermissions',
235 | capability_mode: 'DENY'
236 | )
237 | client.workbooks.delete_permissions(
238 | workbook_id: workbook['id'],
239 | group_id: test_group['id'],
240 | capability: 'ExportImage',
241 | capability_mode: 'ALLOW'
242 | )
243 |
244 | expected = [{
245 | grantee_type: 'group',
246 | grantee_id: all_users_group['id'],
247 | capabilities: {
248 | Read: true,
249 | Filter: true,
250 | ShareView: true,
251 | ViewUnderlyingData: true,
252 | Write: true
253 | }
254 | }]
255 | expect(client.workbooks.permissions(workbook_id: workbook['id'])).to eq expected
256 | end
257 | end
258 |
259 | describe '#list' do
260 | it 'can list workbooks' do
261 | workbook = find_or_publish_workbook('testpublish')
262 |
263 | found_workbook = client.workbooks.list.find do |w|
264 | w['contentUrl'] == workbook['contentUrl']
265 | end
266 |
267 | expect(found_workbook['id']).to be_a_tableau_id
268 | expect(found_workbook).to eq(
269 | 'id' => workbook['id'],
270 | 'name' => workbook['name'],
271 | 'contentUrl' => workbook['contentUrl'],
272 | 'showTabs' => 'false',
273 | 'project' => workbook['project'],
274 | 'owner' => workbook['owner'],
275 | 'tags' => nil,
276 | 'size' => '1',
277 | 'updatedAt' => workbook['updatedAt'],
278 | 'createdAt' => workbook['createdAt'],
279 | 'webpageUrl' => workbook['webpageUrl']
280 | )
281 |
282 | same_workbook = client.workbooks.list.find do |w|
283 | w['name'] == workbook['name']
284 | end
285 |
286 | expect(found_workbook).to eq same_workbook
287 | end
288 |
289 | it 'can paginate workbooks' do
290 | find_or_publish_workbook('test pagination1')
291 | find_or_publish_workbook('test pagination2')
292 | find_or_publish_workbook('test pagination3')
293 | workbooks = client.workbooks.list
294 |
295 | url = "sites/#{client.auth.site_id}/users/#{client.auth.user_id}/workbooks"
296 | expect(client.connection.api_get_collection(url, 'workbooks.workbook', page_size: 2).count).to eq workbooks.count
297 | end
298 | end
299 |
300 | # http://onlinehelp.tableau.com/v9.0/api/rest_api/en-us/help.htm#REST/rest_api_ref.htm#Update_Workbook%3FTocPath%3DAPI%2520Reference%7C_____59
301 | describe '#update' do
302 | it 'can update the workbook' do
303 | workbook = find_or_publish_workbook('testpublish')
304 | expect(client.workbooks.update(
305 | workbook_id: workbook['id'],
306 | owner_user_id: admin_user['id']
307 | )).to be true
308 | end
309 | end
310 |
311 | describe '#views' do
312 | it 'can get the list of views for a workbook' do
313 | workbook = find_or_publish_workbook('testpublish')
314 | views = client.workbooks.views(workbook['id']).to_a
315 | expect(views).to eq([workbook['views']['view']])
316 | end
317 | end
318 |
319 | describe '#find' do
320 | it 'can find a workbook by workbook_id' do
321 | workbook = find_or_publish_workbook('testpublish')
322 | found_workbook = client.workbooks.find(workbook['id'])
323 |
324 | expect(workbook).to eq found_workbook
325 | end
326 | end
327 |
328 | describe '#preview_image' do
329 | it 'can download a preview image' do
330 | workbook = find_or_publish_workbook('testpublish')
331 | res = client.workbooks.preview_image(workbook_id: workbook['id'])
332 | f = Tempfile.new('png')
333 | f.write(res)
334 | f.close
335 | # will raise an error if PNG parsing fails
336 | ChunkyPNG::Image.from_file(f)
337 | end
338 | end
339 |
340 | describe '#refresh' do
341 | it 'can refresh a workbook' do
342 | workbook = find_or_publish_workbook('testpublish')
343 | expect(
344 | client.workbooks.refresh(workbook_id: workbook['id'])
345 | ).to be true
346 | end
347 | end
348 |
349 | describe '#version' do
350 | it 'can get the version of a twbx file' do
351 | expect(client.workbooks.version('spec/fixtures/workbooks/test.twbx')).to eq '10.5'
352 | end
353 |
354 | it 'returns nil if file not found' do
355 | expect(client.workbooks.version('spec/fixtures/workbooks/foo.twbx')).to be_nil
356 | end
357 |
358 | it 'returns nil if file not twbx' do
359 | expect(client.workbooks.version('spec/fixtures/workbooks/foo.bar')).to be_nil
360 | end
361 | end
362 |
363 | def test_group
364 | @test_group ||= client.groups.list.find { |g| g['name'] == 'testgroup' }
365 | end
366 |
367 | def all_users_group
368 | @all_users_group ||= client.groups.list.find { |g| g['name'] == 'All Users' }
369 | end
370 |
371 | def admin_user
372 | @admin_user ||= client.users.list.find { |g| g['name'] == ENV['TABLEAU_ADMIN_USERNAME'] }
373 | end
374 | end
375 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | $LOAD_PATH.unshift File.expand_path('../lib', __dir__)
4 | require 'tableau_api'
5 |
6 | require 'pry'
7 |
8 | require 'vcr'
9 |
10 | if ENV['TABLEAU_ADMIN_USERNAME'].nil? || ENV['TABLEAU_ADMIN_PASSWORD'].nil?
11 | puts 'TABLEAU_ADMIN_USERNAME and TABLEAU_ADMIN_PASSWORD must be set to record new VCR cassettes'
12 |
13 | ENV['TABLEAU_ADMIN_USERNAME'] = 'FakeTableauAdminUsername'
14 | ENV['TABLEAU_ADMIN_PASSWORD'] = 'FakeTableauAdminPassword'
15 | end
16 |
17 | ENV['TABLEAU_HOST'] = 'http://localhost:2000' if ENV['TABLEAU_HOST'].nil?
18 |
19 | VCR.configure do |config|
20 | config.cassette_library_dir = 'spec/fixtures/vcr_cassettes'
21 | config.hook_into :webmock
22 |
23 | config.default_cassette_options = { record: :new_episodes, match_requests_on: %i[path method body query] }
24 |
25 | config.filter_sensitive_data('TABLEAU_ADMIN_USERNAME') { ENV.fetch('TABLEAU_ADMIN_USERNAME', nil) }
26 | config.filter_sensitive_data('TABLEAU_ADMIN_PASSWORD') { ENV['TABLEAU_ADMIN_PASSWORD'].encode(xml: :text) }
27 | config.filter_sensitive_data('http://TABLEAU_HOST') { ENV.fetch('TABLEAU_HOST', nil) }
28 |
29 | config.allow_http_connections_when_no_cassette = false
30 |
31 | # only record a whitelist of test site and user elements
32 | config.before_record do |interaction|
33 | response = interaction.response
34 | elements = response.body.scan(/<(?:site|user)\s[^>]+name[^>]+>/)
35 | sensitive_elements = elements.reject { |e| e.match(/"(Default|TestSite|Test Site 2|test|test_test|TABLEAU_ADMIN_USERNAME)"/) }
36 | unless sensitive_elements.empty?
37 | sensitive_elements.each { |e| response.body.gsub! e, '' }
38 | response.body.gsub!(/totalAvailable="\d+"/, "totalAvailable=\"#{elements.length - sensitive_elements.length}\"")
39 | end
40 | raise 'Cassette might contain sensitive data; does a regex need to be updated?' if response.body =~ /civis/i
41 | end
42 | config.configure_rspec_metadata!
43 | end
44 |
45 | RSpec.configure do |config|
46 | # Turn deprecation warnings into errors.
47 | config.raise_errors_for_deprecations!
48 |
49 | # Persist example state. Enables --only-failures:
50 | # http://rspec.info/blog/2015/06/rspec-3-3-has-been-released/#core-new---only-failures-option
51 | config.example_status_persistence_file_path = 'tmp/examples.txt'
52 | config.run_all_when_everything_filtered = true
53 | end
54 |
55 | RSpec::Matchers.define :be_a_tableau_id do
56 | match do |actual|
57 | actual.match(/\A\w{8}-\w{4}-\w{4}-\w{4}-\w{12}\z/)
58 | end
59 | end
60 |
61 | RSpec::Matchers.define :be_a_token do
62 | match do |actual|
63 | actual.match(/\A\w{32}\z/) || actual.match(/\A[\w-]{22}\|\w{32}\z/)
64 | end
65 | end
66 |
67 | RSpec::Matchers.define :be_a_trusted_ticket do
68 | match do |actual|
69 | actual.match(/\A[\w-]{24}\z/) || actual.match(/\A[\w\-=]{24}:[\w-]{24}\z/)
70 | end
71 | end
72 |
73 | RSpec.shared_context 'tableau client' do
74 | let(:client) do
75 | TableauApi.new(
76 | host: ENV.fetch('TABLEAU_HOST', nil),
77 | site_name: 'TestSite',
78 | username: ENV.fetch('TABLEAU_ADMIN_USERNAME', nil),
79 | password: ENV.fetch('TABLEAU_ADMIN_PASSWORD', nil)
80 | )
81 | end
82 | end
83 |
--------------------------------------------------------------------------------
/spec/tableau_api_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'spec_helper'
4 |
5 | describe TableauApi do
6 | it 'has a version number' do
7 | expect(TableauApi::VERSION).not_to be nil
8 | end
9 |
10 | it 'can create a client from TableauApi' do
11 | expect(TableauApi.new(host: 'tableau.domain.tld', site_name: 'Default', username: 'ExampleUsername')).to be_an_instance_of(TableauApi::Client)
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/tableau_api.gemspec:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | lib = File.expand_path('lib', __dir__)
4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5 | require 'tableau_api/version'
6 |
7 | Gem::Specification.new do |spec|
8 | spec.name = 'tableau_api'
9 | spec.version = TableauApi::VERSION
10 | spec.authors = ['Christopher Manning', 'Matt Brennan',
11 | 'Jonathan Cobian']
12 | spec.email = ['opensource@civisanalytics.com']
13 |
14 | spec.summary = 'Ruby interface to the Tableau API.'
15 | spec.homepage = 'https://github.com/civisanalytics/tableau_api'
16 | spec.license = 'BSD-3-Clause'
17 |
18 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
19 | spec.bindir = 'exe'
20 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21 | spec.require_paths = ['lib']
22 | spec.required_ruby_version = '>= 2.7'
23 |
24 | spec.add_dependency 'builder', '~> 3.2'
25 | spec.add_dependency 'httparty', '~> 0.13'
26 | spec.add_dependency 'multipart-post', '~> 2.0'
27 | spec.add_dependency 'rubyzip', '~> 1.0'
28 |
29 | spec.add_development_dependency 'chunky_png', '~> 1.3.11'
30 | spec.add_development_dependency 'pry', '~> 0.10'
31 | spec.add_development_dependency 'pry-byebug', '~> 3.4'
32 | spec.add_development_dependency 'rake', '~> 12.3.3'
33 | spec.add_development_dependency 'rspec', '~> 3.6'
34 | spec.add_development_dependency 'rubocop', '~> 1.48.1'
35 | spec.add_development_dependency 'vcr', '~> 6.0'
36 | spec.add_development_dependency 'webmock', '~> 3.0'
37 | spec.metadata['rubygems_mfa_required'] = 'true'
38 | end
39 |
--------------------------------------------------------------------------------