keboola-gooddata-writer 2.0.0.pre1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +74 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/keboola-gooddata-writer.gemspec +30 -0
- data/lib/keboola/core_ext.rb +3 -0
- data/lib/keboola/core_ext/hash.rb +22 -0
- data/lib/keboola/gooddata_writer.rb +12 -0
- data/lib/keboola/gooddata_writer/api.rb +365 -0
- data/lib/keboola/gooddata_writer/client.rb +18 -0
- data/lib/keboola/gooddata_writer/errors.rb +46 -0
- data/lib/keboola/gooddata_writer/job.rb +25 -0
- data/lib/keboola/gooddata_writer/parser.rb +21 -0
- data/lib/keboola/gooddata_writer/version.rb +5 -0
- metadata +175 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 85346de59e1ea53f6e4ba5e15629accc9ca519d5
|
4
|
+
data.tar.gz: 11eed840a3b3f0d4dcf741fb289b988f4183e853
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 58927fffa6fb30be7bc722dfd76ab0e37e544f5f81d3967170ab7ed0268c380b1750eea3b895e58b97f9df6f252eb2a5bd45d0a550afedc88ac647c204f025b4
|
7
|
+
data.tar.gz: f7409cd5e031b9d94f30e478e5a4e6d6d8bec50d35726f2bd9e315852a07813ea7cb1d860469f6d569c239e553cb0b4aa797dbaf3354bca9d534c97c75f1988c
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Roman Sklenář
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Keboola GoodData Writer API Ruby client
|
2
|
+
|
3
|
+
[](https://codeclimate.com/github/romansklenar/gooddata-writer-ruby-client)
|
4
|
+
[](https://codeclimate.com/github/romansklenar/gooddata-writer-ruby-client)
|
5
|
+
[](https://travis-ci.org/romansklenar/gooddata-writer-ruby-client)
|
6
|
+
[](https://gemnasium.com/romansklenar/gooddata-writer-ruby-client)
|
7
|
+
[](https://rubygems.org/gems/gooddata-writer-ruby-client)
|
8
|
+
[](http://github.com/romansklenar/gooddata-writer-ruby-client/tags)
|
9
|
+
[](http://github.com/romansklenar/gooddata-writer-ruby-client/releases)
|
10
|
+
[](http://github.com/romansklenar/gooddata-writer-ruby-client/issues)
|
11
|
+
[](http://opensource.org/licenses/MIT)
|
12
|
+
[](https://rubygems.org/gems/gooddata-writer-ruby-client)
|
13
|
+
|
14
|
+
|
15
|
+
Simple Ruby wrapper library for [Keboola GoodData Writer REST API](http://docs.keboolagooddatawriter.apiary.io/).
|
16
|
+
|
17
|
+
|
18
|
+
## Installation
|
19
|
+
|
20
|
+
Add this line to your application's Gemfile:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
gem 'keboola-gooddata-writer'
|
24
|
+
```
|
25
|
+
|
26
|
+
And then execute:
|
27
|
+
|
28
|
+
$ bundle
|
29
|
+
|
30
|
+
Or install it yourself as:
|
31
|
+
|
32
|
+
$ gem install keboola-gooddata-writer
|
33
|
+
|
34
|
+
## Usage
|
35
|
+
|
36
|
+
Obtain your Keboola Storage API token and use to initialize client.
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
api = Keboola::GoodDataWriter::API.new(token: '123-abcdefghjklmnopqrstuvxyz')
|
40
|
+
|
41
|
+
# get details of specific writer
|
42
|
+
api.writer('MyWriter') # {"bucket"=>"sys.c-wr-gooddata-writer1", "writer"=>"gooddata", "writerId"=>"writer1", …, "status"=>"ready"}
|
43
|
+
|
44
|
+
# create GoodData project
|
45
|
+
api.create_project('MyWriter', optionals: { name: 'KBC - MyProject - MyWriter' }) # <Keboola::GoodDataWriter::Job url="https://syrup.keboola.com/queue/jobs/123456", id="123456">
|
46
|
+
|
47
|
+
# create user
|
48
|
+
api.create_user('MyWriter', 'john.snow@test.keboola.com', 't0pS3cr3t', 'John', 'Snow') # <Keboola::GoodDataWriter::Job url="https://syrup.keboola.com/queue/jobs/123456", id="123456">
|
49
|
+
|
50
|
+
# assign user to existing GoodData project
|
51
|
+
api.add_project_users('MyWriter', 'xjywplmhejceb6j3ezzlxiganmjavqio', 'john.snow@test.keboola.com', 'editor') #<Keboola::GoodDataWriter::Job url="https://syrup.keboola.com/queue/jobs/123456", id="123456">
|
52
|
+
|
53
|
+
# getretreive GoodData SSO link
|
54
|
+
api.sso('MyWriter', 'xjywplmhejceb6j3ezzlxiganmjavqio', 'john.snow@test.keboola.com') # "https://secure.gooddata.com/gdc/account/customerlogin?sessionId=-----BEGIN+PGP+MESSAGE-----s0m3_l0000n6_h4sh"
|
55
|
+
```
|
56
|
+
|
57
|
+
## Development
|
58
|
+
|
59
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
|
60
|
+
|
61
|
+
Tests are runned against [mock API server](https://private-anon-cf7bb7f95-keboolagooddatawriter.apiary-mock.com). If you want to run tests against production API server you must provide valid Keboola Storage API token and set it as environment variable called `KEBOOLA_STORAGE_API_TOKEN`.
|
62
|
+
|
63
|
+
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` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
64
|
+
|
65
|
+
## Contributing
|
66
|
+
Get familiar with [Github Flow](https://guides.github.com/introduction/flow/index.html) and stick with it on this project.
|
67
|
+
We're using [Github Issues](https://github.com/slowpath/rails-insights/issues) as an issue tracker. All related tasks are there.
|
68
|
+
|
69
|
+
It's simple!
|
70
|
+
1. Fork it ( https://github.com/romansklenar/keboola-gooddata-writer/fork )
|
71
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
72
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
73
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
74
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "keboola/gooddata_writer"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'keboola/gooddata_writer/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "keboola-gooddata-writer"
|
8
|
+
spec.version = Keboola::GoodDataWriter::VERSION
|
9
|
+
spec.date = Date.today
|
10
|
+
spec.authors = ["Roman Sklenář"]
|
11
|
+
spec.email = ["mail@romansklenar.cz"]
|
12
|
+
|
13
|
+
spec.summary = "A convenient Ruby wrapper around the Keboola GoodData Writer API."
|
14
|
+
spec.description = "Use the Keboola::GoodDataWriter class to integrate Keboola GoodData Writer into your own application."
|
15
|
+
spec.homepage = "https://github.com/romansklenar/gooddata-writer-ruby-client"
|
16
|
+
spec.license = "MIT"
|
17
|
+
|
18
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.8"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
spec.add_development_dependency "minitest", "~> 5.7"
|
24
|
+
spec.add_development_dependency "webmock"
|
25
|
+
spec.add_development_dependency "vcr", "~> 2.9"
|
26
|
+
spec.add_development_dependency "codeclimate-test-reporter"
|
27
|
+
|
28
|
+
spec.add_dependency "json"
|
29
|
+
spec.add_dependency "hurley", "~> 0.1"
|
30
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Hash
|
2
|
+
# Merges the caller into +other_hash+. For example,
|
3
|
+
#
|
4
|
+
# options = options.reverse_merge(size: 25, velocity: 10)
|
5
|
+
#
|
6
|
+
# is equivalent to
|
7
|
+
#
|
8
|
+
# options = { size: 25, velocity: 10 }.merge(options)
|
9
|
+
#
|
10
|
+
# This is particularly useful for initializing an options hash
|
11
|
+
# with default values.
|
12
|
+
def reverse_merge(other_hash)
|
13
|
+
other_hash.merge(self)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Destructive +reverse_merge+.
|
17
|
+
def reverse_merge!(other_hash)
|
18
|
+
# right wins if there is no left
|
19
|
+
merge!( other_hash ){|key,left,right| left }
|
20
|
+
end
|
21
|
+
alias_method :reverse_update, :reverse_merge!
|
22
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require "keboola/gooddata_writer/version"
|
2
|
+
require "keboola/gooddata_writer/errors"
|
3
|
+
require "keboola/gooddata_writer/job"
|
4
|
+
require "keboola/gooddata_writer/parser"
|
5
|
+
require "keboola/gooddata_writer/client"
|
6
|
+
require "keboola/gooddata_writer/api"
|
7
|
+
require "keboola/core_ext"
|
8
|
+
|
9
|
+
module Keboola
|
10
|
+
module GoodDataWriter
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,365 @@
|
|
1
|
+
module Keboola
|
2
|
+
module GoodDataWriter
|
3
|
+
|
4
|
+
class API
|
5
|
+
QUEUE_ENDPOINT_URL = 'https://syrup.keboola.com/queue/'
|
6
|
+
WRITER_ENDPOINT_URL = 'https://syrup.keboola.com/gooddata-writer/'
|
7
|
+
QUEUE_MOCK_ENDPOINT_URL = 'https://private-anon-c1bf53b9c-syrupqueue.apiary-mock.com/queue/'
|
8
|
+
WRITER_MOCK_ENDPOINT_URL = 'https://private-anon-df256c5fb-keboolagooddatawriter.apiary-mock.com/gooddata-writer/'
|
9
|
+
|
10
|
+
attr_accessor :token, :client, :queue, :parser
|
11
|
+
|
12
|
+
|
13
|
+
def initialize(token:, endpoint: nil, client: nil, queue: nil, parser: nil)
|
14
|
+
@token = token
|
15
|
+
@client = client || self.class.build_client(endpoint: endpoint || WRITER_ENDPOINT_URL, token: token)
|
16
|
+
@queue = queue || self.class.build_client(endpoint: QUEUE_ENDPOINT_URL, token: token)
|
17
|
+
@parser = parser || self.class.build_parser
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.build_client(token:, endpoint:)
|
21
|
+
Client.factory(token: token, endpoint: endpoint)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.build_parser
|
25
|
+
Parser.new
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
# === Writer resource commands
|
30
|
+
|
31
|
+
# Returns list of available writers and their buckets
|
32
|
+
def writers
|
33
|
+
handle @client.get("writers") do |result|
|
34
|
+
result["writers"]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns attributes of the writer
|
39
|
+
def writer(writer_id)
|
40
|
+
handle @client.get("writers", { writerId: writer_id }) do |result|
|
41
|
+
result["writers"].first rescue result["writer"]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Creates new configuration bucket and either uses existing GoodData
|
46
|
+
# project or creates new one along with dedicated GoodData user.
|
47
|
+
def create_writer(writer_id, optionals: {}, async: true)
|
48
|
+
handle @client.post("writers", { writerId: writer_id }.reverse_merge(optionals).to_json) do |result|
|
49
|
+
async ? Job.new(result) : wait_for_job(result["id"])
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Sets attributes to writer's configuration
|
54
|
+
def update_writer(writer_id, attributes: {})
|
55
|
+
handle @client.post("writers/#{writer_id}", attributes.to_json) do |result|
|
56
|
+
Job.new(result).ok?
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Deletes configuration bucket and enqueues GoodData project and dedicated GoodData user for removal
|
61
|
+
def delete_writer(writer_id, async: true)
|
62
|
+
handle @client.delete("writers", { writerId: writer_id }) do |result|
|
63
|
+
async ? Job.new(result) : wait_for_job(result["id"])
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
# === Project resource commands
|
69
|
+
|
70
|
+
# Returns list of project clones including main project marked with main: 1 field
|
71
|
+
def projects(writer_id)
|
72
|
+
handle @client.get("projects", { writerId: writer_id }) do |result|
|
73
|
+
result["projects"]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Creates new configuration bucket and either uses existing GoodData
|
78
|
+
# project or creates new one along with dedicated GoodData user.
|
79
|
+
def create_project(writer_id, optionals: {}, async: true)
|
80
|
+
handle @client.post("projects", { writerId: writer_id }.reverse_merge(optionals).to_json) do |result|
|
81
|
+
async ? Job.new(result) : wait_for_job(result["id"])
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Creates new GoodData project for the writer and enqueues the old for deletion
|
86
|
+
def reset_project(writer_id, optionals: {}, async: true)
|
87
|
+
handle @client.post("reset-project", { writerId: writer_id }.reverse_merge(optionals).to_json) do |result|
|
88
|
+
async ? Job.new(result) : wait_for_job(result["id"])
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Upload project to GoodData
|
93
|
+
def upload_project(writer_id, optionals: {}, async: true)
|
94
|
+
handle @client.post("upload-project", { writerId: writer_id }.reverse_merge(optionals).to_json) do |result|
|
95
|
+
async ? Job.new(result) : wait_for_job(result["id"])
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
# === User resource commands
|
101
|
+
|
102
|
+
# Get users list
|
103
|
+
def users(writer_id)
|
104
|
+
handle @client.get("users", { writerId: writer_id }) do |result|
|
105
|
+
result["users"]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Creates new GoodData user in Keboola domain.
|
110
|
+
def create_user(writer_id, email, password, first_name, last_name, optionals: {}, async: true)
|
111
|
+
required = { writerId: writer_id, email: email, password: password, firstName: first_name, lastName: last_name }
|
112
|
+
handle @client.post("users", required.reverse_merge(optionals).to_json) do |result|
|
113
|
+
async ? Job.new(result) : wait_for_job(result["id"])
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
# === Project & User nested resource commands
|
119
|
+
|
120
|
+
# Get list of users in project
|
121
|
+
def project_users(writer_id, project_id)
|
122
|
+
handle @client.get("project-users", { writerId: writer_id, pid: project_id }) do |result|
|
123
|
+
result["users"]
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Adds GoodData user to specified project.
|
128
|
+
def add_project_users(writer_id, project_id, email, role, optionals: {}, async: true)
|
129
|
+
required = { writerId: writer_id, pid: project_id, email: email, role: role }
|
130
|
+
handle @client.post("project-users", required.reverse_merge(optionals).to_json) do |result|
|
131
|
+
async ? Job.new(result) : wait_for_job(result["id"])
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Remove user from specified project.
|
136
|
+
def remove_project_users(writer_id, project_id, email, async: true)
|
137
|
+
handle @client.delete("project-users", { writerId: writer_id, pid: project_id, email: email }) do |result|
|
138
|
+
async ? Job.new(result) : wait_for_job(result["id"])
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
|
143
|
+
# === GoodData project structure commands
|
144
|
+
|
145
|
+
# Get tables list
|
146
|
+
def tables(writer_id)
|
147
|
+
handle @client.get("tables", { writerId: writer_id }) do |result|
|
148
|
+
result["tables"]
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Get table detail
|
153
|
+
def table(writer_id, table_id)
|
154
|
+
handle @client.get("tables", { writerId: writer_id, tableId: table_id }) do |result|
|
155
|
+
result["tables"].first rescue result["table"]
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# Update table configuration
|
160
|
+
def update_table(writer_id, table_id, optionals: {})
|
161
|
+
handle @client.post("tables", { writerId: writer_id, tableId: table_id }.reverse_merge(optionals).to_json) do |result|
|
162
|
+
Job.new(result).ok?
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# Update table column configuration
|
167
|
+
def update_table_column(writer_id, table_id, column, optionals: {})
|
168
|
+
handle @client.post("tables", { writerId: writer_id, tableId: table_id, column: column }.reverse_merge(optionals).to_json) do |result|
|
169
|
+
Job.new(result).ok?
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# Bulk update table column configuration
|
174
|
+
def bulk_update_table_column(writer_id, table_id, columns = [])
|
175
|
+
handle @client.post("tables", { writerId: writer_id, tableId: table_id, columns: columns }.to_json) do |result|
|
176
|
+
Job.new(result).ok?
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# Upload selected table to GoodData
|
181
|
+
def upload_table(writer_id, table_id, optionals: {}, async: true)
|
182
|
+
handle @client.post("upload-table", { writerId: writer_id, tableId: table_id }.reverse_merge(optionals).to_json) do |result|
|
183
|
+
async ? Job.new(result) : wait_for_job(result["id"])
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
# Update model of selected table in GoodData
|
188
|
+
def update_table_model(writer_id, optionals: {}, async: true)
|
189
|
+
handle @client.post("update-model", { writerId: writer_id }.reverse_merge(optionals).to_json) do |result|
|
190
|
+
async ? Job.new(result) : wait_for_job(result["id"])
|
191
|
+
end
|
192
|
+
end
|
193
|
+
alias_method :update_model, :update_table_model
|
194
|
+
|
195
|
+
# Remove dataset in GoodData project belonging to the table and reset it's export status
|
196
|
+
def reset_table(writer_id, table_id, optionals: {}, async: true)
|
197
|
+
handle @client.post("reset-table", { writerId: writer_id, tableId: table_id }.reverse_merge(optionals).to_json) do |result|
|
198
|
+
async ? Job.new(result) : wait_for_job(result["id"])
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
# Selectively upload date dimension (must be already configured in Writer)
|
203
|
+
def upload_date_dimension(writer_id, name, optionals: {}, async: true)
|
204
|
+
handle @client.post("reset-table", { writerId: writer_id, name: name }.reverse_merge(optionals).to_json) do |result|
|
205
|
+
async ? Job.new(result) : wait_for_job(result["id"])
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
|
210
|
+
# === Commands for loading data into GoodData
|
211
|
+
|
212
|
+
# Load data to selected tables in GoodData
|
213
|
+
def load_data(writer_id, optionals: {}, async: true)
|
214
|
+
handle @client.post("load-data", { writerId: writer_id }.reverse_merge(optionals).to_json) do |result|
|
215
|
+
async ? Job.new(result) : wait_for_job(result["id"])
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
# Load data to selected tables in GoodData concurrently
|
220
|
+
def load_data_multi(writer_id, optionals: {}, async: true)
|
221
|
+
handle @client.post("load-data-multi", { writerId: writer_id }.reverse_merge(optionals).to_json) do |result|
|
222
|
+
async ? Job.new(result) : wait_for_job(result["id"])
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
# === Filter resource
|
227
|
+
|
228
|
+
def filters(writer_id, optionals: {})
|
229
|
+
handle @client.get("filters", { writerId: writer_id }.reverse_merge(optionals)) do |result|
|
230
|
+
result["filters"]
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
def create_filter(writer_id, project_id, name, attribute, value, operator = '=', optionals: {}, async: true)
|
235
|
+
required = { writerId: writer_id, pid: project_id, name: name, attribute: attribute, operator: operator, value: value }
|
236
|
+
handle @client.post("filters", required.reverse_merge(optionals).to_json) do |result|
|
237
|
+
async ? Job.new(result) : wait_for_job(result["id"])
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def delete_filter(writer_id, name, async: true)
|
242
|
+
handle @client.delete("filters", { writerId: writer_id, name: name }) do |result|
|
243
|
+
async ? Job.new(result) : wait_for_job(result["id"])
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
|
248
|
+
# === Commands for manipulation with GoodData's Mandatory User Filters
|
249
|
+
|
250
|
+
# Get Filters for Projects
|
251
|
+
def filters_projects(writer_id, optionals: {})
|
252
|
+
handle @client.get("filters-projects", { writerId: writer_id }.reverse_merge(optionals)) do |result|
|
253
|
+
result["filters"]
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
# Get Filters for Users
|
258
|
+
def filters_users(writer_id, optionals: {})
|
259
|
+
handle @client.get("filters-users", { writerId: writer_id }.reverse_merge(optionals)) do |result|
|
260
|
+
result["filters"]
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
# Assign Filter to User
|
265
|
+
def assign_filters_users(writer_id, email, filters = [], optionals: {}, async: true)
|
266
|
+
required = { writerId: writer_id, email: email, filters: filters }
|
267
|
+
handle @client.post("filters-users", required.reverse_merge(optionals).to_json) do |result|
|
268
|
+
async ? Job.new(result) : wait_for_job(result["id"])
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
# Synchronizes filters in GoodData project according to writer's configuration
|
273
|
+
def sync_filters(writer_id, optionals: {}, async: true)
|
274
|
+
handle @client.post("sync-filters", { writerId: writer_id }.reverse_merge(optionals).to_json) do |result|
|
275
|
+
async ? Job.new(result) : wait_for_job(result["id"])
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
|
280
|
+
# === Various GoodData commands
|
281
|
+
|
282
|
+
# Execute selected reports in GoodData
|
283
|
+
def execute_reports(writer_id, project_id, optionals: {}, async: true)
|
284
|
+
handle @client.post("execute-reports", { writerId: writer_id, pid: project_id }.reverse_merge(optionals).to_json) do |result|
|
285
|
+
async ? Job.new(result) : wait_for_job(result["id"])
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
# Call to obtain an SSO link for user
|
290
|
+
def sso(writer_id, project_id, email, optionals: {})
|
291
|
+
handle @client.get("sso", { writerId: writer_id, pid: project_id, email: email }.reverse_merge(optionals)) do |result|
|
292
|
+
result["ssoLink"]
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
# Simple proxy for direct calls to GoodData API
|
297
|
+
def proxy(method, writer_id, query, optionals: {}, async: true)
|
298
|
+
required = { writerId: writer_id, query: query }
|
299
|
+
case method.to_sym
|
300
|
+
when :get
|
301
|
+
handle @client.get("proxy", required.reverse_merge(optionals))
|
302
|
+
when :post
|
303
|
+
handle @client.post("proxy", required.reverse_merge(optionals).to_json) do |result|
|
304
|
+
async ? Job.new(result) : wait_for_job(result["id"])
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
|
310
|
+
# === Commands for jobs handling in Syrup Queue API
|
311
|
+
|
312
|
+
# Return list of jobs for given writer
|
313
|
+
def jobs(writer_id)
|
314
|
+
handle @queue.get("jobs", { q: "+params.writerId:#{writer_id}", limit: 50 }) do |result|
|
315
|
+
result.map { |hash| Job.new(hash) }
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
# Return detail of given job
|
320
|
+
def job(job_id)
|
321
|
+
handle @queue.get("jobs/#{job_id}") do |result|
|
322
|
+
Job.new(result)
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
# Ask repeatedly for job status until it is finished
|
327
|
+
def wait_for_job(job_id)
|
328
|
+
begin
|
329
|
+
job = job(job_id)
|
330
|
+
sleep 5 unless job.finished?
|
331
|
+
end until job.finished?
|
332
|
+
job
|
333
|
+
end
|
334
|
+
|
335
|
+
|
336
|
+
private
|
337
|
+
|
338
|
+
# Properly handle response of API call request
|
339
|
+
def handle(response)
|
340
|
+
result = case response.status_type
|
341
|
+
when :success # Is this a 2xx response?
|
342
|
+
@parser.parse(response.body)
|
343
|
+
|
344
|
+
when :redirection # Is this a 3xx redirect?
|
345
|
+
@parser.parse(response.body)
|
346
|
+
|
347
|
+
when :client_error # Is this is a 4xx response?
|
348
|
+
raise ClientError.new(response)
|
349
|
+
|
350
|
+
when :server_error # Is this a 5xx response?
|
351
|
+
raise ServerError.new(response)
|
352
|
+
|
353
|
+
else
|
354
|
+
raise ResponseError.new(response)
|
355
|
+
end
|
356
|
+
|
357
|
+
block_given? ? yield(result) : result
|
358
|
+
|
359
|
+
rescue ::Hurley::Error, ::Hurley::Timeout => e
|
360
|
+
raise ResponseError.new(e)
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
end
|
365
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require "hurley"
|
2
|
+
|
3
|
+
module Keboola
|
4
|
+
module GoodDataWriter
|
5
|
+
|
6
|
+
class Client < ::Hurley::Client
|
7
|
+
def self.factory(endpoint:, token:)
|
8
|
+
client = new(endpoint)
|
9
|
+
client.header[:x_storageapi_token] = token
|
10
|
+
client.header[:accept] = "application/json"
|
11
|
+
client.request_options.redirection_limit = 5
|
12
|
+
client.request_options.timeout = 5
|
13
|
+
client
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Keboola
|
2
|
+
module GoodDataWriter
|
3
|
+
|
4
|
+
class Error < RuntimeError; end
|
5
|
+
class ParsingError < Error; end
|
6
|
+
|
7
|
+
class ResponseError < Error
|
8
|
+
attr_reader :response
|
9
|
+
|
10
|
+
def initialize(ex, response = nil)
|
11
|
+
@wrapped_exception, @response = nil, response
|
12
|
+
|
13
|
+
if ex.respond_to?(:backtrace)
|
14
|
+
super(ex.message)
|
15
|
+
@wrapped_exception = ex
|
16
|
+
|
17
|
+
elsif ex.respond_to?(:status_code)
|
18
|
+
begin
|
19
|
+
# try to parse response body to retreive more detailed API error message
|
20
|
+
result = Parser.parse(ex.body)
|
21
|
+
super("the server responded with status #{ex.status_code} and message '#{result['message']}'")
|
22
|
+
rescue ParsingError
|
23
|
+
super("the server responded with status #{ex.status_code}")
|
24
|
+
ensure
|
25
|
+
@response = ex
|
26
|
+
end
|
27
|
+
|
28
|
+
else
|
29
|
+
super(ex.to_s)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def backtrace
|
34
|
+
@wrapped_exception ? @wrapped_exception.backtrace : super
|
35
|
+
end
|
36
|
+
|
37
|
+
def inspect
|
38
|
+
%(#<#{self.class}: #{@wrapped_exception.class}>)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class ClientError < ResponseError; end
|
43
|
+
class ServerError < ResponseError; end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "ostruct"
|
2
|
+
|
3
|
+
module Keboola
|
4
|
+
module GoodDataWriter
|
5
|
+
|
6
|
+
class Job < OpenStruct
|
7
|
+
def ok?
|
8
|
+
%w[ok].include?(status)
|
9
|
+
end
|
10
|
+
|
11
|
+
def success?
|
12
|
+
%w[success].include?(status)
|
13
|
+
end
|
14
|
+
|
15
|
+
def finished?
|
16
|
+
%w[cancelled success error warning terminated].include?(status)
|
17
|
+
end
|
18
|
+
|
19
|
+
def pending?
|
20
|
+
%w[waiting processing terminating].include?(status)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "json"
|
2
|
+
|
3
|
+
module Keboola
|
4
|
+
module GoodDataWriter
|
5
|
+
|
6
|
+
class Parser
|
7
|
+
def self.parse(string)
|
8
|
+
begin
|
9
|
+
JSON.parse(string)
|
10
|
+
rescue JSON::ParserError, TypeError => e
|
11
|
+
raise ParsingError.new(e.message)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def parse(string)
|
16
|
+
self.class.parse(string)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: keboola-gooddata-writer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 2.0.0.pre1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Roman Sklenář
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-06-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.8'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.8'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.7'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.7'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: webmock
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: vcr
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '2.9'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '2.9'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: codeclimate-test-reporter
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: json
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: hurley
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0.1'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0.1'
|
125
|
+
description: Use the Keboola::GoodDataWriter class to integrate Keboola GoodData Writer
|
126
|
+
into your own application.
|
127
|
+
email:
|
128
|
+
- mail@romansklenar.cz
|
129
|
+
executables: []
|
130
|
+
extensions: []
|
131
|
+
extra_rdoc_files: []
|
132
|
+
files:
|
133
|
+
- ".gitignore"
|
134
|
+
- ".travis.yml"
|
135
|
+
- Gemfile
|
136
|
+
- LICENSE.txt
|
137
|
+
- README.md
|
138
|
+
- Rakefile
|
139
|
+
- bin/console
|
140
|
+
- bin/setup
|
141
|
+
- keboola-gooddata-writer.gemspec
|
142
|
+
- lib/keboola/core_ext.rb
|
143
|
+
- lib/keboola/core_ext/hash.rb
|
144
|
+
- lib/keboola/gooddata_writer.rb
|
145
|
+
- lib/keboola/gooddata_writer/api.rb
|
146
|
+
- lib/keboola/gooddata_writer/client.rb
|
147
|
+
- lib/keboola/gooddata_writer/errors.rb
|
148
|
+
- lib/keboola/gooddata_writer/job.rb
|
149
|
+
- lib/keboola/gooddata_writer/parser.rb
|
150
|
+
- lib/keboola/gooddata_writer/version.rb
|
151
|
+
homepage: https://github.com/romansklenar/gooddata-writer-ruby-client
|
152
|
+
licenses:
|
153
|
+
- MIT
|
154
|
+
metadata: {}
|
155
|
+
post_install_message:
|
156
|
+
rdoc_options: []
|
157
|
+
require_paths:
|
158
|
+
- lib
|
159
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
160
|
+
requirements:
|
161
|
+
- - ">="
|
162
|
+
- !ruby/object:Gem::Version
|
163
|
+
version: '0'
|
164
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
165
|
+
requirements:
|
166
|
+
- - ">"
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
version: 1.3.1
|
169
|
+
requirements: []
|
170
|
+
rubyforge_project:
|
171
|
+
rubygems_version: 2.2.2
|
172
|
+
signing_key:
|
173
|
+
specification_version: 4
|
174
|
+
summary: A convenient Ruby wrapper around the Keboola GoodData Writer API.
|
175
|
+
test_files: []
|