gooddata 0.6.16 → 0.6.17
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 +4 -4
- data/.rubocop.yml +4 -1
- data/lib/gooddata/cli/commands/project_cmd.rb +1 -1
- data/lib/gooddata/core/logging.rb +15 -5
- data/lib/gooddata/core/rest.rb +4 -28
- data/lib/gooddata/helpers/global_helpers.rb +14 -138
- data/lib/gooddata/helpers/global_helpers_params.rb +145 -0
- data/lib/gooddata/mixins/md_object_indexer.rb +2 -2
- data/lib/gooddata/models/domain.rb +1 -1
- data/lib/gooddata/models/execution.rb +29 -1
- data/lib/gooddata/models/from_wire.rb +6 -0
- data/lib/gooddata/models/from_wire_parse.rb +125 -0
- data/lib/gooddata/models/metadata/attribute.rb +1 -1
- data/lib/gooddata/models/metadata/label.rb +11 -10
- data/lib/gooddata/models/model.rb +4 -0
- data/lib/gooddata/models/profile.rb +12 -2
- data/lib/gooddata/models/project.rb +6 -3
- data/lib/gooddata/models/project_blueprint.rb +4 -4
- data/lib/gooddata/models/project_creator.rb +8 -10
- data/lib/gooddata/models/report_data_result.rb +4 -2
- data/lib/gooddata/models/schedule.rb +121 -66
- data/lib/gooddata/models/to_wire.rb +12 -3
- data/lib/gooddata/models/user_filters/user_filter_builder.rb +3 -234
- data/lib/gooddata/models/user_filters/user_filter_builder_create.rb +115 -0
- data/lib/gooddata/models/user_filters/user_filter_builder_execute.rb +133 -0
- data/lib/gooddata/rest/client.rb +27 -13
- data/lib/gooddata/rest/connection.rb +102 -23
- data/lib/gooddata/version.rb +1 -1
- data/spec/data/gd_gse_data_blueprint.json +1 -0
- data/spec/data/test_project_model_spec.json +5 -2
- data/spec/data/wire_models/model_view.json +3 -0
- data/spec/data/wire_test_project.json +8 -1
- data/spec/integration/full_project_spec.rb +1 -1
- data/spec/unit/core/connection_spec.rb +16 -0
- data/spec/unit/core/logging_spec.rb +54 -6
- data/spec/unit/models/domain_spec.rb +10 -4
- data/spec/unit/models/execution_spec.rb +102 -0
- data/spec/unit/models/from_wire_spec.rb +11 -2
- data/spec/unit/models/model_spec.rb +2 -2
- data/spec/unit/models/project_blueprint_spec.rb +1 -1
- data/spec/unit/models/schedule_spec.rb +34 -24
- data/spec/unit/models/to_wire_spec.rb +9 -1
- metadata +8 -3
data/lib/gooddata/rest/client.rb
CHANGED
@@ -20,6 +20,8 @@ module GoodData
|
|
20
20
|
# Constants
|
21
21
|
#################################
|
22
22
|
DEFAULT_CONNECTION_IMPLEMENTATION = GoodData::Rest::Connection
|
23
|
+
DEFAULT_SLEEP_INTERVAL = 10
|
24
|
+
DEFAULT_POLL_TIME_LIMIT = 5 * 60 * 60 # 5 hours
|
23
25
|
|
24
26
|
#################################
|
25
27
|
# Class variables
|
@@ -207,6 +209,10 @@ module GoodData
|
|
207
209
|
@stats
|
208
210
|
end
|
209
211
|
|
212
|
+
def generate_request_id
|
213
|
+
@connection.generate_request_id
|
214
|
+
end
|
215
|
+
|
210
216
|
#######################
|
211
217
|
# Rest
|
212
218
|
#######################
|
@@ -231,8 +237,9 @@ module GoodData
|
|
231
237
|
project = GoodData::Project[p, opts]
|
232
238
|
fail ArgumentError, 'Wrong :project specified' if project.nil?
|
233
239
|
|
234
|
-
|
235
|
-
|
240
|
+
url = project.links['uploads']
|
241
|
+
fail 'Project WebDAV not supported in this Data Center' unless url
|
242
|
+
url
|
236
243
|
end
|
237
244
|
|
238
245
|
def user_webdav_path(opts = { :project => GoodData.project })
|
@@ -257,17 +264,13 @@ module GoodData
|
|
257
264
|
# @return [Hash] Result of polling
|
258
265
|
def poll_on_code(link, options = {})
|
259
266
|
code = options[:code] || 202
|
260
|
-
|
261
|
-
response = get(link, :process => false)
|
267
|
+
process = options[:process]
|
262
268
|
|
263
|
-
|
264
|
-
|
265
|
-
GoodData::Rest::Client.retryable(:tries => 3, :refresh_token => proc { connection.refresh_token }) do
|
266
|
-
sleep sleep_interval
|
267
|
-
response = get(link, :process => false)
|
268
|
-
end
|
269
|
+
response = poll_on_response(link, options.merge(:process => false)) do |resp|
|
270
|
+
resp.code == code
|
269
271
|
end
|
270
|
-
|
272
|
+
|
273
|
+
if process == false
|
271
274
|
response
|
272
275
|
else
|
273
276
|
get(link)
|
@@ -285,12 +288,23 @@ module GoodData
|
|
285
288
|
# @return [Hash] Result of polling
|
286
289
|
def poll_on_response(link, options = {}, &bl)
|
287
290
|
sleep_interval = options[:sleep_interval] || DEFAULT_SLEEP_INTERVAL
|
288
|
-
|
291
|
+
time_limit = options[:time_limit] || DEFAULT_POLL_TIME_LIMIT
|
292
|
+
|
293
|
+
# by default the response is processed
|
294
|
+
process = options[:process]
|
295
|
+
|
296
|
+
# get the first status and start the timer
|
297
|
+
response = get(link, :process => process)
|
298
|
+
poll_start = Time.now
|
299
|
+
|
289
300
|
while bl.call(response)
|
301
|
+
if time_limit && (Time.now - poll_start > time_limit)
|
302
|
+
fail "The time limit #{time_limit} secs for polling on #{link} is over"
|
303
|
+
end
|
290
304
|
sleep sleep_interval
|
291
305
|
GoodData::Rest::Client.retryable(:tries => 3, :refresh_token => proc { connection.refresh_token }) do
|
292
306
|
sleep sleep_interval
|
293
|
-
response = get(link)
|
307
|
+
response = get(link, :process => process)
|
294
308
|
end
|
295
309
|
end
|
296
310
|
response
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
require 'terminal-table'
|
4
|
+
require 'securerandom'
|
4
5
|
|
5
6
|
require_relative '../version'
|
6
7
|
require_relative '../exceptions/exceptions'
|
@@ -9,7 +10,7 @@ module GoodData
|
|
9
10
|
module Rest
|
10
11
|
# Wrapper of low-level HTTP/REST client/library
|
11
12
|
class Connection
|
12
|
-
DEFAULT_URL = 'https://secure.gooddata.com'
|
13
|
+
DEFAULT_URL = ENV['GD_SERVER'] || 'https://secure.gooddata.com'
|
13
14
|
LOGIN_PATH = '/gdc/account/login'
|
14
15
|
TOKEN_PATH = '/gdc/account/token'
|
15
16
|
KEYS_TO_SCRUB = [:password, :verifyPassword, :authorizationToken]
|
@@ -20,9 +21,13 @@ module GoodData
|
|
20
21
|
:user_agent => GoodData.gem_version_string
|
21
22
|
}
|
22
23
|
|
24
|
+
DEFAULT_WEBDAV_HEADERS = {
|
25
|
+
:user_agent => GoodData.gem_version_string
|
26
|
+
}
|
27
|
+
|
23
28
|
DEFAULT_LOGIN_PAYLOAD = {
|
24
29
|
:headers => DEFAULT_HEADERS,
|
25
|
-
:verify_ssl =>
|
30
|
+
:verify_ssl => true
|
26
31
|
}
|
27
32
|
|
28
33
|
RETRYABLE_ERRORS = [
|
@@ -51,7 +56,8 @@ module GoodData
|
|
51
56
|
def retryable(options = {}, &_block)
|
52
57
|
opts = { :tries => 1, :on => RETRYABLE_ERRORS }.merge(options)
|
53
58
|
|
54
|
-
retry_exception
|
59
|
+
retry_exception = opts[:on]
|
60
|
+
retries = opts[:tries]
|
55
61
|
|
56
62
|
unless retry_exception.is_a?(Array)
|
57
63
|
retry_exception = [retry_exception]
|
@@ -79,7 +85,10 @@ module GoodData
|
|
79
85
|
end
|
80
86
|
end
|
81
87
|
|
82
|
-
attr_reader :
|
88
|
+
attr_reader :request_params
|
89
|
+
|
90
|
+
# backward compatibility
|
91
|
+
alias_method :cookies, :request_params
|
83
92
|
attr_reader :stats
|
84
93
|
attr_reader :user
|
85
94
|
|
@@ -87,13 +96,12 @@ module GoodData
|
|
87
96
|
@stats = {}
|
88
97
|
@opts = opts
|
89
98
|
|
90
|
-
|
99
|
+
headers = opts[:headers] || {}
|
100
|
+
@webdav_headers = DEFAULT_WEBDAV_HEADERS.merge(headers)
|
101
|
+
|
91
102
|
@user = nil
|
92
103
|
@server = nil
|
93
|
-
|
94
104
|
@opts = opts
|
95
|
-
headers = opts[:headers] || {}
|
96
|
-
@headers.merge! headers
|
97
105
|
|
98
106
|
# Initialize cookies
|
99
107
|
reset_cookies!
|
@@ -104,7 +112,11 @@ module GoodData
|
|
104
112
|
# Connect using username and password
|
105
113
|
def connect(username, password, options = {})
|
106
114
|
server = options[:server] || DEFAULT_URL
|
107
|
-
|
115
|
+
|
116
|
+
options = DEFAULT_LOGIN_PAYLOAD.merge(options)
|
117
|
+
headers = options[:headers] || {}
|
118
|
+
|
119
|
+
@server = RestClient::Resource.new server, DEFAULT_LOGIN_PAYLOAD.merge(headers)
|
108
120
|
|
109
121
|
# Install at_exit handler first
|
110
122
|
unless @at_exit_handler_installed
|
@@ -125,6 +137,7 @@ module GoodData
|
|
125
137
|
refresh_token :dont_reauth => true
|
126
138
|
else
|
127
139
|
credentials = Connection.construct_login_payload(username, password)
|
140
|
+
generate_session_id
|
128
141
|
@auth = post(LOGIN_PATH, credentials)['userLogin']
|
129
142
|
|
130
143
|
refresh_token :dont_reauth => true
|
@@ -138,6 +151,7 @@ module GoodData
|
|
138
151
|
url = @auth['state']
|
139
152
|
|
140
153
|
begin
|
154
|
+
clear_session_id
|
141
155
|
delete url if url
|
142
156
|
rescue RestClient::Unauthorized
|
143
157
|
GoodData.logger.info 'Already disconnected'
|
@@ -189,7 +203,7 @@ module GoodData
|
|
189
203
|
:url => url
|
190
204
|
}.merge(cookies)
|
191
205
|
|
192
|
-
if where.is_a?(IO)
|
206
|
+
if where.is_a?(IO) || where.is_a?(StringIO)
|
193
207
|
RestClient::Request.execute(raw) do |chunk, _x, response|
|
194
208
|
if response.code.to_s != '200'
|
195
209
|
fail ArgumentError, "Error downloading #{url}. Got response: #{response.code} #{response} #{response.body}"
|
@@ -238,7 +252,15 @@ module GoodData
|
|
238
252
|
def delete(uri, options = {})
|
239
253
|
GoodData.logger.debug "DELETE: #{@server.url}#{uri}"
|
240
254
|
profile "DELETE #{uri}" do
|
241
|
-
b = proc
|
255
|
+
b = proc do
|
256
|
+
begin
|
257
|
+
@server[uri].delete(fresh_request_params(options[:request_id]))
|
258
|
+
rescue RestClient::Exception => e
|
259
|
+
# log the error if it happens
|
260
|
+
GoodData.logger.error(e.inspect)
|
261
|
+
raise e
|
262
|
+
end
|
263
|
+
end
|
242
264
|
process_response(options, &b)
|
243
265
|
end
|
244
266
|
end
|
@@ -249,7 +271,15 @@ module GoodData
|
|
249
271
|
def get(uri, options = {}, &user_block)
|
250
272
|
GoodData.logger.debug "GET: #{@server.url}#{uri}"
|
251
273
|
profile "GET #{uri}" do
|
252
|
-
b = proc
|
274
|
+
b = proc do
|
275
|
+
begin
|
276
|
+
@server[uri].get(fresh_request_params(options[:request_id]), &user_block)
|
277
|
+
rescue RestClient::Exception => e
|
278
|
+
# log the error if it happens
|
279
|
+
GoodData.logger.error(e.inspect)
|
280
|
+
raise e
|
281
|
+
end
|
282
|
+
end
|
253
283
|
process_response(options, &b)
|
254
284
|
end
|
255
285
|
end
|
@@ -261,7 +291,15 @@ module GoodData
|
|
261
291
|
payload = data.is_a?(Hash) ? data.to_json : data
|
262
292
|
GoodData.logger.debug "PUT: #{@server.url}#{uri}, #{scrub_params(data, KEYS_TO_SCRUB)}"
|
263
293
|
profile "PUT #{uri}" do
|
264
|
-
b = proc
|
294
|
+
b = proc do
|
295
|
+
begin
|
296
|
+
@server[uri].put(payload, fresh_request_params(options[:request_id]))
|
297
|
+
rescue RestClient::Exception => e
|
298
|
+
# log the error if it happens
|
299
|
+
GoodData.logger.error(e.inspect)
|
300
|
+
raise e
|
301
|
+
end
|
302
|
+
end
|
265
303
|
process_response(options, &b)
|
266
304
|
end
|
267
305
|
end
|
@@ -273,7 +311,15 @@ module GoodData
|
|
273
311
|
GoodData.logger.debug "POST: #{@server.url}#{uri}, #{scrub_params(data, KEYS_TO_SCRUB)}"
|
274
312
|
profile "POST #{uri}" do
|
275
313
|
payload = data.is_a?(Hash) ? data.to_json : data
|
276
|
-
b = proc
|
314
|
+
b = proc do
|
315
|
+
begin
|
316
|
+
@server[uri].post(payload, fresh_request_params(options[:request_id]))
|
317
|
+
rescue RestClient::Exception => e
|
318
|
+
# log the error if it happens
|
319
|
+
GoodData.logger.error(e.inspect)
|
320
|
+
raise e
|
321
|
+
end
|
322
|
+
end
|
277
323
|
process_response(options, &b)
|
278
324
|
end
|
279
325
|
end
|
@@ -282,7 +328,7 @@ module GoodData
|
|
282
328
|
#
|
283
329
|
# @return uri [String] SST token
|
284
330
|
def sst_token
|
285
|
-
|
331
|
+
request_params[:cookies]['GDCAuthSST']
|
286
332
|
end
|
287
333
|
|
288
334
|
def stats_table(values = stats)
|
@@ -306,7 +352,7 @@ module GoodData
|
|
306
352
|
#
|
307
353
|
# @return uri [String] TT token
|
308
354
|
def tt_token
|
309
|
-
|
355
|
+
request_params[:cookies]['GDCAuthTT']
|
310
356
|
end
|
311
357
|
|
312
358
|
# Uploads a file to GoodData server
|
@@ -315,7 +361,7 @@ module GoodData
|
|
315
361
|
puts "uploading the file #{uri}"
|
316
362
|
|
317
363
|
to_upload = File.new(filename)
|
318
|
-
cookies_str =
|
364
|
+
cookies_str = request_params[:cookies].map { |cookie| "#{cookie[0]}=#{cookie[1]}" }.join(';')
|
319
365
|
req = Net::HTTP::Put.new(uri.path, 'User-Agent' => GoodData.gem_version_string, 'Cookie' => cookies_str)
|
320
366
|
req.content_length = to_upload.size
|
321
367
|
req.body_stream = to_upload
|
@@ -338,10 +384,10 @@ module GoodData
|
|
338
384
|
raw = {
|
339
385
|
:method => method,
|
340
386
|
:url => url,
|
341
|
-
:headers => @
|
342
|
-
}
|
387
|
+
:headers => @webdav_headers
|
388
|
+
}.merge(cookies)
|
343
389
|
begin
|
344
|
-
RestClient::Request.execute(raw
|
390
|
+
RestClient::Request.execute(raw)
|
345
391
|
rescue RestClient::Exception => e
|
346
392
|
false if e.http_code == 404
|
347
393
|
end
|
@@ -363,7 +409,7 @@ module GoodData
|
|
363
409
|
raw = {
|
364
410
|
:method => method,
|
365
411
|
:url => url,
|
366
|
-
:headers => @
|
412
|
+
:headers => @webdav_headers
|
367
413
|
}.merge(cookies)
|
368
414
|
RestClient::Request.execute(raw)
|
369
415
|
end
|
@@ -384,10 +430,43 @@ module GoodData
|
|
384
430
|
do_stream_file URI.join(url, CGI.escape(webdav_filename)), file
|
385
431
|
end
|
386
432
|
|
433
|
+
def generate_request_id
|
434
|
+
"#{session_id}:#{call_id}"
|
435
|
+
end
|
436
|
+
|
387
437
|
private
|
388
438
|
|
439
|
+
ID_LENGTH = 16
|
440
|
+
|
441
|
+
def generate_string
|
442
|
+
SecureRandom.urlsafe_base64(ID_LENGTH)
|
443
|
+
end
|
444
|
+
|
445
|
+
# generate session id to be passed as the first part to
|
446
|
+
# x_gdc_request header
|
447
|
+
def session_id
|
448
|
+
@session_id ||= generate_string
|
449
|
+
end
|
450
|
+
|
451
|
+
def call_id
|
452
|
+
generate_string
|
453
|
+
end
|
454
|
+
|
455
|
+
def generate_session_id
|
456
|
+
@session_id = generate_string
|
457
|
+
end
|
458
|
+
|
459
|
+
def clear_session_id
|
460
|
+
@session_id = nil
|
461
|
+
end
|
462
|
+
|
463
|
+
# request heders with freshly generated request id
|
464
|
+
def fresh_request_params(request_id = nil)
|
465
|
+
@request_params.merge(:x_gdc_request => request_id || generate_request_id)
|
466
|
+
end
|
467
|
+
|
389
468
|
def merge_cookies!(cookies)
|
390
|
-
@
|
469
|
+
@request_params[:cookies].merge! cookies
|
391
470
|
end
|
392
471
|
|
393
472
|
def process_response(options = {}, &block)
|
@@ -447,7 +526,7 @@ module GoodData
|
|
447
526
|
end
|
448
527
|
|
449
528
|
def reset_cookies!
|
450
|
-
@
|
529
|
+
@request_params = { :cookies => {} }
|
451
530
|
end
|
452
531
|
|
453
532
|
def scrub_params(params, keys)
|
data/lib/gooddata/version.rb
CHANGED
@@ -4,10 +4,12 @@
|
|
4
4
|
{
|
5
5
|
"type": "dataset",
|
6
6
|
"name": "repos",
|
7
|
+
"folder": "Foldered Repos",
|
7
8
|
"columns": [
|
8
9
|
{
|
9
10
|
"type": "anchor",
|
10
|
-
"name": "repo_id"
|
11
|
+
"name": "repo_id",
|
12
|
+
"description": "This is anchor description"
|
11
13
|
},
|
12
14
|
{
|
13
15
|
"type": "label",
|
@@ -42,7 +44,8 @@
|
|
42
44
|
"columns": [
|
43
45
|
{
|
44
46
|
"type": "fact",
|
45
|
-
"name": "lines_changed"
|
47
|
+
"name": "lines_changed",
|
48
|
+
"description": "Fact description"
|
46
49
|
},
|
47
50
|
{
|
48
51
|
"type": "date",
|
@@ -31,6 +31,7 @@
|
|
31
31
|
"fact": {
|
32
32
|
"identifier": "fact.stage_history.stage_velocity",
|
33
33
|
"title": "Stage Velocity",
|
34
|
+
"description": "Velocity description",
|
34
35
|
"dataType": "DECIMAL(12,2)"
|
35
36
|
}
|
36
37
|
}, {
|
@@ -1498,6 +1499,7 @@
|
|
1498
1499
|
"attribute": {
|
1499
1500
|
"identifier": "attr.opp_owner.region",
|
1500
1501
|
"title": "Opp. Owner Region",
|
1502
|
+
"description": "Owner Region description",
|
1501
1503
|
"folder": "Opp. Owner",
|
1502
1504
|
"labels": [{
|
1503
1505
|
"label": {
|
@@ -1601,6 +1603,7 @@
|
|
1601
1603
|
"attribute": {
|
1602
1604
|
"identifier": "attr.opportunity.id",
|
1603
1605
|
"title": "Opportunity",
|
1606
|
+
"description": "This is opportunity attribute description",
|
1604
1607
|
"folder": "Opportunity",
|
1605
1608
|
"labels": [{
|
1606
1609
|
"label": {
|
@@ -11,6 +11,8 @@
|
|
11
11
|
"attribute": {
|
12
12
|
"identifier": "attr.repos.repo_id",
|
13
13
|
"title": "Repo",
|
14
|
+
"description": "This is anchor description",
|
15
|
+
"folder": "Foldered Repos",
|
14
16
|
"labels": [
|
15
17
|
{
|
16
18
|
"label": {
|
@@ -37,6 +39,7 @@
|
|
37
39
|
"attribute": {
|
38
40
|
"identifier": "attr.repos.department",
|
39
41
|
"title": "Department",
|
42
|
+
"folder": "Foldered Repos",
|
40
43
|
"labels": [
|
41
44
|
{
|
42
45
|
"label": {
|
@@ -67,6 +70,7 @@
|
|
67
70
|
"attribute": {
|
68
71
|
"identifier": "attr.devs.dev_id",
|
69
72
|
"title": "Dev",
|
73
|
+
"folder": "Devs",
|
70
74
|
"labels": [
|
71
75
|
{
|
72
76
|
"label": {
|
@@ -106,7 +110,8 @@
|
|
106
110
|
"anchor": {
|
107
111
|
"attribute": {
|
108
112
|
"identifier": "attr.commits.factsof",
|
109
|
-
"title": "Records of Commits"
|
113
|
+
"title": "Records of Commits",
|
114
|
+
"folder": "Commits"
|
110
115
|
}
|
111
116
|
},
|
112
117
|
"attributes": [
|
@@ -117,6 +122,8 @@
|
|
117
122
|
"fact": {
|
118
123
|
"identifier": "fact.commits.lines_changed",
|
119
124
|
"title": "Lines Changed",
|
125
|
+
"description": "Fact description",
|
126
|
+
"folder": "Commits",
|
120
127
|
"dataType": "INT"
|
121
128
|
}
|
122
129
|
}
|