tableau_api 1.0.0 → 1.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/.ruby-version +1 -1
- data/.travis.yml +4 -3
- data/CHANGELOG.md +59 -2
- data/README.md +4 -0
- data/lib/tableau_api.rb +1 -0
- data/lib/tableau_api/client.rb +5 -0
- data/lib/tableau_api/connection.rb +6 -2
- data/lib/tableau_api/resources/groups.rb +43 -0
- data/lib/tableau_api/resources/sites.rb +27 -0
- data/lib/tableau_api/resources/users.rb +17 -0
- data/lib/tableau_api/resources/workbooks.rb +69 -15
- data/lib/tableau_api/version.rb +1 -1
- data/tableau_api.gemspec +5 -4
- metadata +12 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 79d15e26e8441669e7c04c13794a049dc968d69e
|
4
|
+
data.tar.gz: 05e49453e17574474a356d20cb21fc0ce094e6e0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8e69aba0b041e655e0e93b1fc0de62f069c966148675b755365bac2572a01dd78fbc322ce1020e1e588bef1600a554e77b8e54089c5936390573d3bb0137d018
|
7
|
+
data.tar.gz: ca5ebe0289ac4af7c5ccf6c0e1945ce61f01d693b31f027cf1ed8baafcfbf7e980042afb09167c48befbf074a1a46e1d003367d1565fc30ede71cee7f2884957
|
data/.rubocop.yml
CHANGED
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
2.4.1
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,12 +1,69 @@
|
|
1
1
|
# Change Log
|
2
|
+
|
2
3
|
All notable changes to this project will be documented in this file.
|
3
4
|
This project adheres to [Semantic Versioning](http://semver.org/).
|
4
5
|
|
5
6
|
## [Unreleased]
|
6
7
|
|
7
|
-
## [1.
|
8
|
+
## [1.1.2] - 2017-05-24
|
9
|
+
|
10
|
+
### Fixed
|
11
|
+
|
12
|
+
- Replaced corrupt `.gem` upload to RubyGems
|
13
|
+
|
14
|
+
## [1.1.1] - 2017-05-24 - [YANKED]
|
15
|
+
|
16
|
+
### Added
|
17
|
+
|
18
|
+
- Added Ruby 2.4.1 to the Travis matrix build
|
19
|
+
|
20
|
+
### Changed
|
21
|
+
|
22
|
+
- Bumped the Ruby versions in the Travis matrix build to 2.3.4, 2.2.7, and
|
23
|
+
2.1.10
|
24
|
+
- Bumped the Ruby version for development to 2.4.1
|
25
|
+
- Bumped the RSpec version for development to 3.6
|
26
|
+
- Bumped the WebMock version for development to 3.0
|
27
|
+
- Updated the authors
|
28
|
+
|
29
|
+
### Fixed
|
30
|
+
|
31
|
+
- Updated `TableauApi::VERSION`
|
32
|
+
|
33
|
+
## [1.1.0] - 2017-03-02
|
34
|
+
|
8
35
|
### Added
|
36
|
+
|
37
|
+
- [#2](https://github.com/civisanalytics/tableau_api/pull/2)
|
38
|
+
- `TableauApi::Resources::Groups` added to support API calls for
|
39
|
+
adding/deleting/updating groups.
|
40
|
+
- `TableauApi::Resources::Workbook#remove_permissions` added, including
|
41
|
+
support for user and group permissions.
|
42
|
+
- `TableauApi::Resources::Workbook#add_permissions` supports group
|
43
|
+
permissions.
|
44
|
+
- [#6](https://github.com/civisanalytics/tableau_api/pull/6)
|
45
|
+
Added `Users#update_user`
|
46
|
+
- [#7](https://github.com/civisanalytics/tableau_api/pull/7)
|
47
|
+
Added `Sites#create` and `Sites#delete`
|
48
|
+
|
49
|
+
### Changed
|
50
|
+
|
51
|
+
- [#3](https://github.com/civisanalytics/tableau_api/pull/3)
|
52
|
+
`TableauApi::Resources::Workbook#permissions` now returns existing permissions
|
53
|
+
instead of adding new permissions. New permissions can be added with
|
54
|
+
`TableauApi::Resources::Workbook#add_permissions`.
|
55
|
+
|
56
|
+
### Fixed
|
57
|
+
|
58
|
+
- [#4](https://github.com/civisanalytics/tableau_api/pull/4)
|
59
|
+
Always parse/return workbook permissions as an array
|
60
|
+
|
61
|
+
## [1.0.0] - 2016-06-06
|
62
|
+
|
9
63
|
- Initial Release
|
10
64
|
|
11
|
-
[Unreleased]: https://github.com/civisanalytics/tableau_api/compare/v1.
|
65
|
+
[Unreleased]: https://github.com/civisanalytics/tableau_api/compare/v1.1.2...HEAD
|
66
|
+
[1.1.2]: https://github.com/civisanalytics/tableau_api/compare/v1.1.1...v1.1.2
|
67
|
+
[1.1.1]: https://github.com/civisanalytics/tableau_api/compare/v1.1.0...v1.1.1
|
68
|
+
[1.1.0]: https://github.com/civisanalytics/tableau_api/compare/v1.0.0...v1.1.0
|
12
69
|
[1.0.0]: https://github.com/civisanalytics/tableau_api/tree/v1.0.0
|
data/README.md
CHANGED
@@ -60,6 +60,10 @@ docker exec -it CONTAINER_ID /bin/bash -c "cd /src && bundle && rake"
|
|
60
60
|
|
61
61
|
### Creating New VCR Cassettes
|
62
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.
|
66
|
+
|
63
67
|
Set the environment variables below to an administrator account, create a tunnel
|
64
68
|
to your tableau server on port 2000, then run the commands below.
|
65
69
|
|
data/lib/tableau_api.rb
CHANGED
@@ -10,6 +10,7 @@ require 'tableau_api/resources/auth'
|
|
10
10
|
require 'tableau_api/resources/projects'
|
11
11
|
require 'tableau_api/resources/sites'
|
12
12
|
require 'tableau_api/resources/users'
|
13
|
+
require 'tableau_api/resources/groups'
|
13
14
|
require 'tableau_api/resources/workbooks'
|
14
15
|
require 'tableau_api/version'
|
15
16
|
|
data/lib/tableau_api/client.rb
CHANGED
@@ -27,6 +27,7 @@ module TableauApi
|
|
27
27
|
projects: TableauApi::Resources::Projects,
|
28
28
|
sites: TableauApi::Resources::Sites,
|
29
29
|
users: TableauApi::Resources::Users,
|
30
|
+
groups: TableauApi::Resources::Groups,
|
30
31
|
workbooks: TableauApi::Resources::Workbooks
|
31
32
|
}
|
32
33
|
end
|
@@ -39,5 +40,9 @@ module TableauApi
|
|
39
40
|
super
|
40
41
|
end
|
41
42
|
end
|
43
|
+
|
44
|
+
def respond_to_missing?(name, include_private = false)
|
45
|
+
self.class.resources.keys.include?(name) || super
|
46
|
+
end
|
42
47
|
end
|
43
48
|
end
|
@@ -28,7 +28,7 @@ module TableauApi
|
|
28
28
|
raise TableauError, res if res.code.to_s != '200'
|
29
29
|
|
30
30
|
# ensure the result is an array because it will not be an array if there is only one element
|
31
|
-
[collection.split('.').reduce(res['tsResponse']) { |
|
31
|
+
[collection.split('.').reduce(res['tsResponse']) { |acc, elem| acc && acc[elem] }].flatten.compact.each do |obj|
|
32
32
|
enum.yield obj
|
33
33
|
end
|
34
34
|
|
@@ -52,6 +52,10 @@ module TableauApi
|
|
52
52
|
api_method(:put, path, *args)
|
53
53
|
end
|
54
54
|
|
55
|
+
def api_delete(path, *args)
|
56
|
+
api_method(:delete, path, *args)
|
57
|
+
end
|
58
|
+
|
55
59
|
def api_post_multipart(path, parts, headers)
|
56
60
|
headers = auth_headers(headers)
|
57
61
|
headers['Content-Type'] = 'multipart/mixed'
|
@@ -60,7 +64,7 @@ module TableauApi
|
|
60
64
|
|
61
65
|
req = Net::HTTP::Post::Multipart.new(uri.to_s, parts, headers)
|
62
66
|
|
63
|
-
Net::HTTP.start(uri.host, uri.port) do |http|
|
67
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
|
64
68
|
http.request(req)
|
65
69
|
end
|
66
70
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module TableauApi
|
2
|
+
module Resources
|
3
|
+
class Groups < Base
|
4
|
+
def create(name:, default_site_role: 'Viewer')
|
5
|
+
raise 'invalid default_site_role' unless Users::SITE_ROLES.include? default_site_role
|
6
|
+
|
7
|
+
request = Builder::XmlMarkup.new.tsRequest do |ts|
|
8
|
+
ts.group(name: name, defaultSiteRole: default_site_role)
|
9
|
+
end
|
10
|
+
|
11
|
+
res = @client.connection.api_post("sites/#{@client.auth.site_id}/groups", body: request)
|
12
|
+
|
13
|
+
res['tsResponse']['group'] if res.code == 201
|
14
|
+
end
|
15
|
+
|
16
|
+
def list
|
17
|
+
url = "sites/#{@client.auth.site_id}/groups"
|
18
|
+
@client.connection.api_get_collection(url, 'groups.group')
|
19
|
+
end
|
20
|
+
|
21
|
+
def users(group_id:)
|
22
|
+
url = "sites/#{@client.auth.site_id}/groups/#{group_id}/users"
|
23
|
+
@client.connection.api_get_collection(url, 'users.user')
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_user(group_id:, user_id:)
|
27
|
+
request = Builder::XmlMarkup.new.tsRequest do |ts|
|
28
|
+
ts.user(id: user_id)
|
29
|
+
end
|
30
|
+
|
31
|
+
res = @client.connection.api_post("sites/#{@client.auth.site_id}/groups/#{group_id}/users", body: request)
|
32
|
+
|
33
|
+
res.code == 200
|
34
|
+
end
|
35
|
+
|
36
|
+
def remove_user(group_id:, user_id:)
|
37
|
+
res = @client.connection.api_delete("sites/#{@client.auth.site_id}/groups/#{group_id}/users/#{user_id}")
|
38
|
+
|
39
|
+
res.code == 204
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -4,6 +4,33 @@ module TableauApi
|
|
4
4
|
def list
|
5
5
|
@client.connection.api_get_collection('sites', 'sites.site')
|
6
6
|
end
|
7
|
+
|
8
|
+
def create(name:, content_url:, admin_mode: nil, num_users: nil, storage_quota: nil)
|
9
|
+
# required parameters
|
10
|
+
request_hash = {
|
11
|
+
name: name,
|
12
|
+
contentUrl: content_url
|
13
|
+
}
|
14
|
+
# optional parameters
|
15
|
+
request_hash[:admin_mode] = admin_mode if admin_mode
|
16
|
+
request_hash[:num_users] = num_users if num_users
|
17
|
+
request_hash[:storage_quota] = storage_quota if storage_quota
|
18
|
+
|
19
|
+
request = Builder::XmlMarkup.new.tsRequest do |ts|
|
20
|
+
ts.site(request_hash)
|
21
|
+
end
|
22
|
+
|
23
|
+
res = @client.connection.api_post('sites', body: request)
|
24
|
+
|
25
|
+
return res['tsResponse']['site'] if res.code == 201
|
26
|
+
raise TableauError, res
|
27
|
+
end
|
28
|
+
|
29
|
+
def delete(site_id:)
|
30
|
+
res = @client.connection.api_delete("sites/#{site_id}")
|
31
|
+
return true if res.code == 204
|
32
|
+
raise TableauError, res
|
33
|
+
end
|
7
34
|
end
|
8
35
|
end
|
9
36
|
end
|
@@ -27,6 +27,23 @@ module TableauApi
|
|
27
27
|
url = "sites/#{@client.auth.site_id}/users"
|
28
28
|
@client.connection.api_get_collection(url, 'users.user')
|
29
29
|
end
|
30
|
+
|
31
|
+
def update_user(user_id:, site_role:)
|
32
|
+
raise 'invalid site_role' unless SITE_ROLES.include? site_role
|
33
|
+
|
34
|
+
res = @client.connection.api_get("sites/#{@client.auth.site_id}/users/#{user_id}")
|
35
|
+
|
36
|
+
raise 'failed to find user' if res.code != 200
|
37
|
+
user = res['tsResponse']['user']
|
38
|
+
|
39
|
+
request = Builder::XmlMarkup.new.tsRequest do |ts|
|
40
|
+
ts.user(name: user['name'], siteRole: site_role)
|
41
|
+
end
|
42
|
+
|
43
|
+
res = @client.connection.api_put("sites/#{@client.auth.site_id}/users/#{user_id}", body: request)
|
44
|
+
|
45
|
+
res['tsResponse']['user'] if res.code == 200
|
46
|
+
end
|
30
47
|
end
|
31
48
|
end
|
32
49
|
end
|
@@ -53,29 +53,57 @@ module TableauApi
|
|
53
53
|
Filter Read ShareView ViewComments ViewUnderlyingData WebAuthoring Write
|
54
54
|
).freeze
|
55
55
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
56
|
+
CAPABILITY_MODES = %w(ALLOW DENY).freeze
|
57
|
+
|
58
|
+
def permissions(workbook_id:)
|
59
|
+
res = @client.connection.api_get("sites/#{@client.auth.site_id}/workbooks/#{workbook_id}/permissions")
|
60
|
+
|
61
|
+
raise TableauError, res if res.code != 200
|
62
|
+
permissions = HTTParty::Parser.new(res.body, :xml).parse['tsResponse']['permissions']['granteeCapabilities']
|
63
|
+
return [] if permissions.nil?
|
64
|
+
|
65
|
+
permissions = [permissions] unless permissions.is_a? Array
|
66
|
+
permissions.map do |p|
|
67
|
+
grantee_type = p['group'].nil? ? 'user' : 'group'
|
68
|
+
|
69
|
+
capabilities = {}
|
70
|
+
capabilities_list = p['capabilities']['capability']
|
71
|
+
capabilities_list = [capabilities_list] unless capabilities_list.is_a? Array
|
72
|
+
|
73
|
+
capabilities_list.each do |c|
|
74
|
+
capabilities[c['name'].to_sym] = c['mode'] == 'Allow'
|
71
75
|
end
|
76
|
+
|
77
|
+
{
|
78
|
+
grantee_type: grantee_type,
|
79
|
+
grantee_id: p[grantee_type]['id'],
|
80
|
+
capabilities: capabilities
|
81
|
+
}
|
72
82
|
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# capabilities is a hash of symbol keys to booleans { Read: true, ChangePermissions: false }
|
86
|
+
def add_permissions(workbook_id:, user_id: nil, group_id: nil, capabilities:)
|
87
|
+
validate_user_group_exclusivity(user_id, group_id)
|
73
88
|
|
89
|
+
request = permissions_request(workbook_id, user_id, group_id, capabilities)
|
74
90
|
res = @client.connection.api_put("sites/#{@client.auth.site_id}/workbooks/#{workbook_id}/permissions", body: request)
|
75
91
|
|
76
92
|
res.code == 200
|
77
93
|
end
|
78
94
|
|
95
|
+
def delete_permissions(workbook_id:, user_id: nil, group_id: nil, capability:, capability_mode:)
|
96
|
+
validate_user_group_exclusivity(user_id, group_id)
|
97
|
+
raise 'invalid capability' unless CAPABILITIES.include? capability.to_s
|
98
|
+
raise 'invalid mode' unless CAPABILITY_MODES.include? capability_mode.to_s
|
99
|
+
|
100
|
+
subpath = user_id ? "users/#{user_id}" : "groups/#{group_id}"
|
101
|
+
subpath += "/#{capability}/#{capability_mode}"
|
102
|
+
res = @client.connection.api_delete("sites/#{@client.auth.site_id}/workbooks/#{workbook_id}/permissions/#{subpath}")
|
103
|
+
|
104
|
+
res.code == 204
|
105
|
+
end
|
106
|
+
|
79
107
|
def update(workbook_id:, owner_user_id:)
|
80
108
|
request = Builder::XmlMarkup.new.tsRequest do |ts|
|
81
109
|
ts.workbook(id: workbook_id) do |w|
|
@@ -102,6 +130,32 @@ module TableauApi
|
|
102
130
|
url = "sites/#{@client.auth.site_id}/users/#{@client.auth.user_id}/workbooks"
|
103
131
|
@client.connection.api_get_collection(url, 'workbooks.workbook')
|
104
132
|
end
|
133
|
+
|
134
|
+
private
|
135
|
+
|
136
|
+
def permissions_request(workbook_id, user_id, group_id, capabilities)
|
137
|
+
Builder::XmlMarkup.new.tsRequest do |ts|
|
138
|
+
ts.permissions do |p|
|
139
|
+
p.workbook(id: workbook_id)
|
140
|
+
p.granteeCapabilities do |gc|
|
141
|
+
gc.user(id: user_id) if user_id
|
142
|
+
gc.group(id: group_id) if group_id
|
143
|
+
gc.capabilities do |c|
|
144
|
+
capabilities.each do |k, v|
|
145
|
+
k = k.to_s
|
146
|
+
raise "invalid capability #{k}" unless CAPABILITIES.include? k
|
147
|
+
c.capability(name: k, mode: v ? 'Allow' : 'Deny')
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def validate_user_group_exclusivity(user_id, group_id)
|
156
|
+
raise 'cannot specify user_id and group_id simultaneously' if user_id && group_id
|
157
|
+
raise 'must specify user_id or group_id' unless user_id || group_id
|
158
|
+
end
|
105
159
|
end
|
106
160
|
end
|
107
161
|
end
|
data/lib/tableau_api/version.rb
CHANGED
data/tableau_api.gemspec
CHANGED
@@ -6,7 +6,8 @@ require 'tableau_api/version'
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = 'tableau_api'
|
8
8
|
spec.version = TableauApi::VERSION
|
9
|
-
spec.authors = ['Christopher Manning'
|
9
|
+
spec.authors = ['Christopher Manning', 'Matt Brennan',
|
10
|
+
'Jonathan Cobian']
|
10
11
|
spec.email = ['opensource@civisanalytics.com']
|
11
12
|
|
12
13
|
spec.summary = 'Ruby interface to the Tableau 9.0 API.'
|
@@ -26,10 +27,10 @@ Gem::Specification.new do |spec|
|
|
26
27
|
|
27
28
|
spec.add_development_dependency 'bundler', '~> 1.12'
|
28
29
|
spec.add_development_dependency 'rake', '~> 11.0'
|
29
|
-
spec.add_development_dependency 'rspec', '~> 3.
|
30
|
+
spec.add_development_dependency 'rspec', '~> 3.6'
|
30
31
|
spec.add_development_dependency 'vcr', '~> 3.0'
|
31
|
-
spec.add_development_dependency 'webmock', '~>
|
32
|
+
spec.add_development_dependency 'webmock', '~> 3.0'
|
32
33
|
spec.add_development_dependency 'pry', '~> 0.10'
|
33
34
|
spec.add_development_dependency 'pry-byebug', '~> 3.4'
|
34
|
-
spec.add_development_dependency 'rubocop', '~> 0.
|
35
|
+
spec.add_development_dependency 'rubocop', '~> 0.46.0'
|
35
36
|
end
|
metadata
CHANGED
@@ -1,14 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tableau_api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Christopher Manning
|
8
|
+
- Matt Brennan
|
9
|
+
- Jonathan Cobian
|
8
10
|
autorequire:
|
9
11
|
bindir: exe
|
10
12
|
cert_chain: []
|
11
|
-
date:
|
13
|
+
date: 2017-05-24 00:00:00.000000000 Z
|
12
14
|
dependencies:
|
13
15
|
- !ruby/object:Gem::Dependency
|
14
16
|
name: httparty
|
@@ -100,14 +102,14 @@ dependencies:
|
|
100
102
|
requirements:
|
101
103
|
- - "~>"
|
102
104
|
- !ruby/object:Gem::Version
|
103
|
-
version: '3.
|
105
|
+
version: '3.6'
|
104
106
|
type: :development
|
105
107
|
prerelease: false
|
106
108
|
version_requirements: !ruby/object:Gem::Requirement
|
107
109
|
requirements:
|
108
110
|
- - "~>"
|
109
111
|
- !ruby/object:Gem::Version
|
110
|
-
version: '3.
|
112
|
+
version: '3.6'
|
111
113
|
- !ruby/object:Gem::Dependency
|
112
114
|
name: vcr
|
113
115
|
requirement: !ruby/object:Gem::Requirement
|
@@ -128,14 +130,14 @@ dependencies:
|
|
128
130
|
requirements:
|
129
131
|
- - "~>"
|
130
132
|
- !ruby/object:Gem::Version
|
131
|
-
version: '
|
133
|
+
version: '3.0'
|
132
134
|
type: :development
|
133
135
|
prerelease: false
|
134
136
|
version_requirements: !ruby/object:Gem::Requirement
|
135
137
|
requirements:
|
136
138
|
- - "~>"
|
137
139
|
- !ruby/object:Gem::Version
|
138
|
-
version: '
|
140
|
+
version: '3.0'
|
139
141
|
- !ruby/object:Gem::Dependency
|
140
142
|
name: pry
|
141
143
|
requirement: !ruby/object:Gem::Requirement
|
@@ -170,14 +172,14 @@ dependencies:
|
|
170
172
|
requirements:
|
171
173
|
- - "~>"
|
172
174
|
- !ruby/object:Gem::Version
|
173
|
-
version:
|
175
|
+
version: 0.46.0
|
174
176
|
type: :development
|
175
177
|
prerelease: false
|
176
178
|
version_requirements: !ruby/object:Gem::Requirement
|
177
179
|
requirements:
|
178
180
|
- - "~>"
|
179
181
|
- !ruby/object:Gem::Version
|
180
|
-
version:
|
182
|
+
version: 0.46.0
|
181
183
|
description:
|
182
184
|
email:
|
183
185
|
- opensource@civisanalytics.com
|
@@ -204,6 +206,7 @@ files:
|
|
204
206
|
- lib/tableau_api/error.rb
|
205
207
|
- lib/tableau_api/resources/auth.rb
|
206
208
|
- lib/tableau_api/resources/base.rb
|
209
|
+
- lib/tableau_api/resources/groups.rb
|
207
210
|
- lib/tableau_api/resources/projects.rb
|
208
211
|
- lib/tableau_api/resources/sites.rb
|
209
212
|
- lib/tableau_api/resources/users.rb
|
@@ -230,7 +233,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
230
233
|
version: '0'
|
231
234
|
requirements: []
|
232
235
|
rubyforge_project:
|
233
|
-
rubygems_version: 2.
|
236
|
+
rubygems_version: 2.6.11
|
234
237
|
signing_key:
|
235
238
|
specification_version: 4
|
236
239
|
summary: Ruby interface to the Tableau 9.0 API.
|