gooddata 0.6.16 → 0.6.17
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
}
|