gooddata 0.6.10 → 0.6.11
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +9 -0
- data/.travis.yml +4 -0
- data/CHANGELOG.md +6 -0
- data/README.md +1 -0
- data/gooddata.gemspec +38 -40
- data/lib/gooddata/bricks/base_downloader.rb +1 -1
- data/lib/gooddata/bricks/middleware/base_middleware.rb +36 -0
- data/lib/gooddata/bricks/middleware/bulk_salesforce_middleware.rb +8 -38
- data/lib/gooddata/bricks/middleware/decode_params_middleware.rb +14 -0
- data/lib/gooddata/bricks/middleware/fs_upload_middleware.rb +7 -6
- data/lib/gooddata/bricks/middleware/gooddata_middleware.rb +7 -5
- data/lib/gooddata/bricks/middleware/logger_middleware.rb +1 -1
- data/lib/gooddata/bricks/middleware/restforce_middleware.rb +20 -14
- data/lib/gooddata/bricks/middleware/undot_params_middleware.rb +33 -0
- data/lib/gooddata/cli/commands/api_cmd.rb +1 -1
- data/lib/gooddata/cli/commands/auth_cmd.rb +1 -1
- data/lib/gooddata/cli/commands/console_cmd.rb +2 -2
- data/lib/gooddata/cli/commands/process_cmd.rb +54 -7
- data/lib/gooddata/cli/commands/project_cmd.rb +9 -9
- data/lib/gooddata/cli/commands/projects_cmd.rb +1 -1
- data/lib/gooddata/cli/commands/run_ruby_cmd.rb +24 -7
- data/lib/gooddata/cli/commands/scaffold_cmd.rb +2 -2
- data/lib/gooddata/cli/commands/user_cmd.rb +1 -1
- data/lib/gooddata/cli/hooks.rb +3 -3
- data/lib/gooddata/commands/datasets.rb +1 -1
- data/lib/gooddata/commands/project.rb +2 -2
- data/lib/gooddata/commands/role.rb +1 -1
- data/lib/gooddata/commands/runners.rb +2 -2
- data/lib/gooddata/connection.rb +2 -2
- data/lib/gooddata/core/nil_logger.rb +1 -1
- data/lib/gooddata/core/rest.rb +12 -8
- data/lib/gooddata/data/guesser.rb +1 -1
- data/lib/gooddata/exceptions/attr_element_not_found.rb +1 -1
- data/lib/gooddata/extensions/enumerable.rb +1 -1
- data/lib/gooddata/extensions/hash.rb +20 -0
- data/lib/gooddata/helpers/csv_helper.rb +1 -1
- data/lib/gooddata/helpers/global_helpers.rb +59 -1
- data/lib/gooddata/mixins/md_lock.rb +83 -0
- data/lib/gooddata/mixins/md_object_indexer.rb +1 -1
- data/lib/gooddata/mixins/md_object_query.rb +1 -1
- data/lib/gooddata/mixins/md_relations.rb +0 -9
- data/lib/gooddata/models/dashboard_builder.rb +1 -1
- data/lib/gooddata/models/domain.rb +2 -2
- data/lib/gooddata/models/empty_result.rb +5 -5
- data/lib/gooddata/models/execution.rb +74 -0
- data/lib/gooddata/models/execution_detail.rb +74 -0
- data/lib/gooddata/models/membership.rb +1 -1
- data/lib/gooddata/models/metadata/attribute.rb +4 -6
- data/lib/gooddata/models/metadata/dashboard.rb +2 -0
- data/lib/gooddata/models/metadata/fact.rb +2 -2
- data/lib/gooddata/models/metadata/metric.rb +4 -1
- data/lib/gooddata/models/metadata/report.rb +84 -34
- data/lib/gooddata/models/metadata/report_definition.rb +28 -17
- data/lib/gooddata/models/metadata.rb +1 -1
- data/lib/gooddata/models/model.rb +1 -1
- data/lib/gooddata/models/process.rb +70 -54
- data/lib/gooddata/models/profile.rb +47 -10
- data/lib/gooddata/models/project.rb +74 -30
- data/lib/gooddata/models/project_blueprint.rb +9 -10
- data/lib/gooddata/models/project_builder.rb +2 -2
- data/lib/gooddata/models/project_creator.rb +4 -4
- data/lib/gooddata/models/report_data_result.rb +1 -1
- data/lib/gooddata/models/schedule.rb +39 -32
- data/lib/gooddata/models/to_manifest.rb +5 -5
- data/lib/gooddata/models/to_wire.rb +3 -3
- data/lib/gooddata/rest/client.rb +64 -31
- data/lib/gooddata/rest/connection.rb +7 -7
- data/lib/gooddata/rest/connections/dummy_connection.rb +5 -5
- data/lib/gooddata/rest/connections/rest_client_connection.rb +106 -44
- data/lib/gooddata/rest/object.rb +1 -1
- data/lib/gooddata/version.rb +1 -1
- data/spec/bricks/bricks_spec.rb +67 -0
- data/spec/bricks/default-config.json +8 -0
- data/spec/data/gooddata_version_process/gooddata_version.rb +3 -0
- data/spec/data/gooddata_version_process/gooddata_version.zip +0 -0
- data/spec/data/ruby_params_process/ruby_params.rb +3 -0
- data/spec/helpers/process_helper.rb +12 -0
- data/spec/helpers/schedule_helper.rb +21 -0
- data/spec/integration/create_project_spec.rb +19 -0
- data/spec/integration/full_process_schedule_spec.rb +129 -8
- data/spec/integration/full_project_spec.rb +52 -2
- data/spec/spec_helper.rb +1 -0
- data/spec/unit/core/rest_spec.rb +76 -3
- data/spec/unit/helpers_spec.rb +43 -0
- data/spec/unit/models/metric_spec.rb +33 -0
- data/spec/unit/models/params_spec.rb +96 -0
- data/spec/unit/models/profile_spec.rb +16 -0
- data/spec/unit/models/schedule_spec.rb +12 -3
- metadata +350 -187
- data/lib/gooddata/helper/class_helper.rb +0 -1
- data/lib/gooddata/helper/helpers.rb +0 -8
@@ -2,6 +2,10 @@
|
|
2
2
|
|
3
3
|
require_relative '../rest/resource'
|
4
4
|
require_relative '../extensions/hash'
|
5
|
+
require_relative '../mixins/rest_resource'
|
6
|
+
require_relative '../helpers/global_helpers'
|
7
|
+
|
8
|
+
require_relative 'execution'
|
5
9
|
|
6
10
|
module GoodData
|
7
11
|
class Schedule < Rest::Resource
|
@@ -11,6 +15,9 @@ module GoodData
|
|
11
15
|
alias_method :raw_data, :json
|
12
16
|
alias_method :to_hash, :json
|
13
17
|
|
18
|
+
include GoodData::Mixin::RestResource
|
19
|
+
root_key :schedule
|
20
|
+
|
14
21
|
class << self
|
15
22
|
# Looks for schedule
|
16
23
|
# @param id [String] URL, ID of schedule or :all
|
@@ -58,11 +65,11 @@ module GoodData
|
|
58
65
|
# Creates new schedules from parameters passed
|
59
66
|
#
|
60
67
|
# @param process_id [String] Process ID
|
61
|
-
# @param
|
68
|
+
# @param trigger [String|GoodData::Schedule] Trigger of schedule. Can be cron string or reference to another schedule.
|
62
69
|
# @param executable [String] Execution executable
|
63
70
|
# @param options [Hash] Optional options
|
64
71
|
# @return [GoodData::Schedule] New GoodData::Schedule instance
|
65
|
-
def create(process_id,
|
72
|
+
def create(process_id, trigger, executable, options = {})
|
66
73
|
c = client(options)
|
67
74
|
fail ArgumentError, 'No :client specified' if c.nil?
|
68
75
|
|
@@ -75,50 +82,39 @@ module GoodData
|
|
75
82
|
default_opts = {
|
76
83
|
:type => 'MSETL',
|
77
84
|
:timezone => 'UTC',
|
78
|
-
:cron => cron,
|
79
85
|
:params => {
|
80
|
-
:
|
81
|
-
:
|
86
|
+
:PROCESS_ID => process_id,
|
87
|
+
:EXECUTABLE => executable
|
82
88
|
},
|
83
|
-
:
|
89
|
+
:hiddenParams => {},
|
84
90
|
:reschedule => options[:reschedule] || 0
|
85
91
|
}
|
86
92
|
|
87
|
-
|
88
|
-
:
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
:
|
93
|
-
:executable => 'EXECUTABLE'
|
94
|
-
}
|
95
|
-
|
96
|
-
default_params = default_opts[:params].reduce({}) do |new_hash, (k, v)|
|
97
|
-
key = inject_params[k] || k
|
98
|
-
new_hash[key] = v
|
99
|
-
new_hash
|
93
|
+
if trigger =~ /[a-fA-Z0-9]{24}/
|
94
|
+
default_opts[:triggerScheduleId] = trigger
|
95
|
+
elsif trigger.is_a?(GoodData::Schedule)
|
96
|
+
default_opts[:triggerScheduleId] = trigger.obj_id
|
97
|
+
else
|
98
|
+
default_opts[:cron] = trigger
|
100
99
|
end
|
101
100
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
new_hash
|
101
|
+
if options.key?(:hidden_params)
|
102
|
+
options[:hiddenParams] = options[:hidden_params]
|
103
|
+
options.delete :hidden_params
|
106
104
|
end
|
107
105
|
|
108
|
-
default[:params] = default_params
|
109
|
-
|
110
106
|
json = {
|
111
|
-
'schedule' =>
|
107
|
+
'schedule' => default_opts.deep_merge(options.except(:project, :client))
|
112
108
|
}
|
113
109
|
|
114
|
-
tmp = json['schedule'][:params][
|
110
|
+
tmp = json['schedule'][:params][:PROCESS_ID]
|
115
111
|
fail 'Process ID has to be provided' if tmp.nil? || tmp.empty?
|
116
112
|
|
117
|
-
tmp = json['schedule'][:params][
|
113
|
+
tmp = json['schedule'][:params][:EXECUTABLE]
|
118
114
|
fail 'Executable has to be provided' if tmp.nil? || tmp.empty?
|
119
115
|
|
120
|
-
tmp = json['schedule'][:cron]
|
121
|
-
fail '
|
116
|
+
tmp = json['schedule'][:cron] || json['schedule'][:triggerScheduleId]
|
117
|
+
fail 'trigger schedule has to be provided' if !tmp || tmp.nil? || tmp.empty?
|
122
118
|
|
123
119
|
tmp = json['schedule'][:timezone]
|
124
120
|
fail 'A timezone has to be provided' if tmp.nil? || tmp.empty?
|
@@ -126,6 +122,16 @@ module GoodData
|
|
126
122
|
tmp = json['schedule'][:type]
|
127
123
|
fail 'Schedule type has to be provided' if tmp.nil? || tmp.empty?
|
128
124
|
|
125
|
+
params = json['schedule'][:params]
|
126
|
+
params = GoodData::Helpers.encode_params(params, false)
|
127
|
+
json['schedule'][:params] = params
|
128
|
+
|
129
|
+
hidden_params = json['schedule'][:hiddenParams]
|
130
|
+
if hidden_params && !hidden_params.empty?
|
131
|
+
hidden_params = GoodData::Helpers.encode_params(json['schedule'][:hiddenParams], true)
|
132
|
+
json['schedule'][:hiddenParams] = hidden_params
|
133
|
+
end
|
134
|
+
|
129
135
|
url = "/gdc/projects/#{project.pid}/schedules"
|
130
136
|
res = c.post url, json
|
131
137
|
|
@@ -190,9 +196,10 @@ module GoodData
|
|
190
196
|
:execution => {}
|
191
197
|
}
|
192
198
|
execution = client.post(execution_url, data)
|
193
|
-
client.poll_on_response(execution['execution']['links']['self']) do |body|
|
199
|
+
res = client.poll_on_response(execution['execution']['links']['self']) do |body|
|
194
200
|
body['execution'] && (body['execution']['status'] == 'RUNNING' || body['execution']['status'] == 'SCHEDULED')
|
195
201
|
end
|
202
|
+
client.create(GoodData::Execution, res, client: client, project: project)
|
196
203
|
end
|
197
204
|
|
198
205
|
# Returns execution URL
|
@@ -300,7 +307,7 @@ module GoodData
|
|
300
307
|
#
|
301
308
|
# @return [Array] Raw Executions JSON
|
302
309
|
def executions
|
303
|
-
if @json
|
310
|
+
if @json # rubocop:disable Style/GuardClause
|
304
311
|
url = @json['schedule']['links']['executions']
|
305
312
|
res = client.get url
|
306
313
|
res['executions']['items']
|
@@ -10,7 +10,7 @@ module GoodData
|
|
10
10
|
# @param attribute [Hash] Attribute or Anchor
|
11
11
|
# @param mode [String] Mode of the load. Either FULL or INCREMENTAL
|
12
12
|
# @return [Hash] Manifest for a particular reference
|
13
|
-
def self.attribute_to_manifest(
|
13
|
+
def self.attribute_to_manifest(_project, dataset, a, mode)
|
14
14
|
[{
|
15
15
|
'referenceKey' => 1,
|
16
16
|
'populates' => [GoodData::Model.identifier_for(dataset, a.merge(type: :primary_label))],
|
@@ -71,7 +71,7 @@ module GoodData
|
|
71
71
|
# @param reference [Hash] Reference
|
72
72
|
# @param mode [String] Mode of the load. Either FULL or INCREMENTAL
|
73
73
|
# @return [Hash] Manifest for a particular date reference
|
74
|
-
def self.date_ref_to_manifest(project,
|
74
|
+
def self.date_ref_to_manifest(project, _dataset, reference, mode)
|
75
75
|
referenced_dataset = ProjectBlueprint.find_date_dimension(project, reference[:dataset])
|
76
76
|
[{
|
77
77
|
'populates' => [GoodData::Model.identifier_for(referenced_dataset, type: :date_ref)],
|
@@ -89,7 +89,7 @@ module GoodData
|
|
89
89
|
# @param fact [Hash] Fact
|
90
90
|
# @param mode [String] Mode of the load. Either FULL or INCREMENTAL
|
91
91
|
# @return [Hash] Manifest for a particular fact
|
92
|
-
def self.fact_to_manifest(
|
92
|
+
def self.fact_to_manifest(_project, dataset, fact, mode)
|
93
93
|
[{
|
94
94
|
'populates' => [GoodData::Model.identifier_for(dataset, fact)],
|
95
95
|
'mode' => mode,
|
@@ -104,7 +104,7 @@ module GoodData
|
|
104
104
|
# @param label [Hash] Label
|
105
105
|
# @param mode [String] Mode of the load. Either FULL or INCREMENTAL
|
106
106
|
# @return [Hash] Manifest for a particular label
|
107
|
-
def self.label_to_manifest(
|
107
|
+
def self.label_to_manifest(_project, dataset, label, mode)
|
108
108
|
a = DatasetBlueprint.attribute_for_label(dataset, label)
|
109
109
|
[{
|
110
110
|
'populates' => [GoodData::Model.identifier_for(dataset, label, a)],
|
@@ -145,7 +145,7 @@ module GoodData
|
|
145
145
|
# @param reference [Hash] Reference
|
146
146
|
# @param mode [String] Mode of the load. Either FULL or INCREMENTAL
|
147
147
|
# @return [Hash] Manifest for a particular reference
|
148
|
-
def self.reference_to_manifest(project,
|
148
|
+
def self.reference_to_manifest(project, _dataset, reference, mode)
|
149
149
|
referenced_dataset = ProjectBlueprint.find_dataset(project, reference[:dataset])
|
150
150
|
anchor = DatasetBlueprint.anchor(referenced_dataset)
|
151
151
|
[{
|
@@ -11,7 +11,7 @@ module GoodData
|
|
11
11
|
# @param project [Hash] Project blueprint hash represenation
|
12
12
|
# @param dataset [Hash] Dataset blueprint hash represenation
|
13
13
|
# @return [Hash] Manifest for a particular reference
|
14
|
-
def self.anchor_to_wire(
|
14
|
+
def self.anchor_to_wire(_project, dataset)
|
15
15
|
if DatasetBlueprint.anchor(dataset)
|
16
16
|
attribute_to_wire(dataset, DatasetBlueprint.anchor(dataset))
|
17
17
|
else
|
@@ -29,7 +29,7 @@ module GoodData
|
|
29
29
|
# @param project [Hash] Project blueprint hash represenation
|
30
30
|
# @param dataset [Hash] Dataset blueprint hash represenation
|
31
31
|
# @return [Hash] Manifest for a particular reference
|
32
|
-
def self.attributes_to_wire(
|
32
|
+
def self.attributes_to_wire(_project, dataset)
|
33
33
|
DatasetBlueprint.attributes(dataset).map do |a|
|
34
34
|
attribute_to_wire(dataset, a)
|
35
35
|
end
|
@@ -85,7 +85,7 @@ module GoodData
|
|
85
85
|
# @param project [Hash] Project blueprint hash represenation
|
86
86
|
# @param dataset [Hash] Dataset blueprint hash represenation
|
87
87
|
# @return [Hash] Manifest for a particular reference
|
88
|
-
def self.date_dimensions_to_wire(
|
88
|
+
def self.date_dimensions_to_wire(_project, dataset)
|
89
89
|
{
|
90
90
|
dateDimension: {
|
91
91
|
name: dataset[:name],
|
data/lib/gooddata/rest/client.rb
CHANGED
@@ -35,6 +35,7 @@ module GoodData
|
|
35
35
|
|
36
36
|
# TODO: Decide if we need provide direct access to factory
|
37
37
|
attr_reader :factory
|
38
|
+
attr_reader :opts
|
38
39
|
|
39
40
|
include Mixin::Inspector
|
40
41
|
inspector :object_id
|
@@ -58,7 +59,7 @@ module GoodData
|
|
58
59
|
# @param username [String] Username to be used for authentication
|
59
60
|
# @param password [String] Password to be used for authentication
|
60
61
|
# @return [GoodData::Rest::Client] Client
|
61
|
-
def connect(username, password, opts = {})
|
62
|
+
def connect(username, password, opts = { :verify_ssl => true })
|
62
63
|
new_opts = opts.dup
|
63
64
|
if username.is_a?(Hash) && username.key?(:sst_token)
|
64
65
|
new_opts[:sst_token] = username[:sst_token]
|
@@ -92,7 +93,7 @@ module GoodData
|
|
92
93
|
end
|
93
94
|
|
94
95
|
def disconnect
|
95
|
-
if @@instance # rubocop:disable ClassVars
|
96
|
+
if @@instance # rubocop:disable ClassVars, Style/GuardClause
|
96
97
|
@@instance.disconnect # rubocop:disable ClassVars
|
97
98
|
@@instance = nil # rubocop:disable ClassVars
|
98
99
|
end
|
@@ -102,6 +103,21 @@ module GoodData
|
|
102
103
|
@@instance # rubocop:disable ClassVars
|
103
104
|
end
|
104
105
|
|
106
|
+
# Retry block if exception thrown
|
107
|
+
def retryable(options = {}, &_block)
|
108
|
+
opts = { :tries => 1, :on => Exception }.merge(options)
|
109
|
+
|
110
|
+
retry_exception, retries = opts[:on], opts[:tries]
|
111
|
+
|
112
|
+
begin
|
113
|
+
return yield
|
114
|
+
rescue retry_exception
|
115
|
+
retry if (retries -= 1) > 0
|
116
|
+
end
|
117
|
+
|
118
|
+
yield
|
119
|
+
end
|
120
|
+
|
105
121
|
alias_method :client, :connection
|
106
122
|
end
|
107
123
|
|
@@ -128,8 +144,8 @@ module GoodData
|
|
128
144
|
@factory = ObjectFactory.new(self)
|
129
145
|
end
|
130
146
|
|
131
|
-
def create_project(options = {})
|
132
|
-
GoodData::Project.create(
|
147
|
+
def create_project(options = { title: 'Project', auth_token: ENV['GD_PROJECT_TOKEN'] })
|
148
|
+
GoodData::Project.create(options.merge(client: self))
|
133
149
|
end
|
134
150
|
|
135
151
|
def create_project_from_blueprint(blueprint, options = {})
|
@@ -193,8 +209,8 @@ module GoodData
|
|
193
209
|
# HTTP GET
|
194
210
|
#
|
195
211
|
# @param uri [String] Target URI
|
196
|
-
def get(uri, opts = {})
|
197
|
-
@connection.get uri, opts
|
212
|
+
def get(uri, opts = {}, & block)
|
213
|
+
@connection.get uri, opts, & block
|
198
214
|
end
|
199
215
|
|
200
216
|
# FIXME: Invstigate _file argument
|
@@ -237,7 +253,7 @@ module GoodData
|
|
237
253
|
|
238
254
|
while response.code == code
|
239
255
|
sleep sleep_interval
|
240
|
-
retryable(:tries => 3, :on => RestClient::InternalServerError) do
|
256
|
+
GoodData::Rest::Client.retryable(:tries => 3, :on => RestClient::InternalServerError) do
|
241
257
|
sleep sleep_interval
|
242
258
|
response = get(link, :process => false)
|
243
259
|
end
|
@@ -263,7 +279,7 @@ module GoodData
|
|
263
279
|
response = get(link)
|
264
280
|
while bl.call(response)
|
265
281
|
sleep sleep_interval
|
266
|
-
retryable(:tries => 3, :on => RestClient::InternalServerError) do
|
282
|
+
GoodData::Rest::Client.retryable(:tries => 3, :on => RestClient::InternalServerError) do
|
267
283
|
sleep sleep_interval
|
268
284
|
response = get(link)
|
269
285
|
end
|
@@ -285,43 +301,60 @@ module GoodData
|
|
285
301
|
@connection.post uri, data, opts
|
286
302
|
end
|
287
303
|
|
288
|
-
#
|
289
|
-
|
290
|
-
|
304
|
+
# Uploads file to staging
|
305
|
+
#
|
306
|
+
# @param file [String] file to be uploaded
|
307
|
+
# @param options [Hash] must contain :staging_url key (file will be uploaded to :staging_url + File.basename(file))
|
308
|
+
def upload(file, options = {})
|
309
|
+
@connection.upload file, options
|
310
|
+
end
|
291
311
|
|
292
|
-
|
312
|
+
# Downloads file from staging
|
313
|
+
#
|
314
|
+
# @param source_relative_path [String] path relative to @param options[:staging_url]
|
315
|
+
# @param target_file_path [String] path to be downloaded to
|
316
|
+
# @param options [Hash] must contain :staging_url key (file will be downloaded from :staging_url + source_relative_path)
|
317
|
+
def download(source_relative_path, target_file_path, options = {})
|
318
|
+
@connection.download source_relative_path, target_file_path, options
|
319
|
+
end
|
293
320
|
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
321
|
+
def download_from_user_webdav(source_relative_path, target_file_path, options = {})
|
322
|
+
download(source_relative_path, target_file_path, options.merge(
|
323
|
+
:directory => options[:directory],
|
324
|
+
:staging_url => get_user_webdav_url(options)
|
325
|
+
))
|
326
|
+
end
|
299
327
|
|
300
|
-
|
328
|
+
def upload_to_user_webdav(file, options = {})
|
329
|
+
upload(file, options.merge(
|
330
|
+
:directory => options[:directory],
|
331
|
+
:staging_url => get_user_webdav_url(options)
|
332
|
+
))
|
301
333
|
end
|
302
334
|
|
303
|
-
|
304
|
-
|
305
|
-
@connection.upload file, options
|
335
|
+
def with_project(pid, &block)
|
336
|
+
GoodData.with_project(pid, client: self, &block)
|
306
337
|
end
|
307
338
|
|
308
|
-
|
339
|
+
###################### PRIVATE ######################
|
340
|
+
|
341
|
+
private
|
342
|
+
|
343
|
+
def get_user_webdav_url(options = {})
|
309
344
|
p = options[:project]
|
310
345
|
fail ArgumentError, 'No :project specified' if p.nil?
|
311
346
|
|
312
|
-
project = GoodData::Project[p, options]
|
347
|
+
project = options[:project] || GoodData::Project[p, options]
|
313
348
|
fail ArgumentError, 'Wrong :project specified' if project.nil?
|
314
349
|
|
315
350
|
u = URI(project.links['uploads'])
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
end
|
351
|
+
us = u.to_s
|
352
|
+
ws = options[:client].opts[:webdav_server]
|
353
|
+
if !us.empty? && !us.downcase.start_with?('http') && !ws.empty?
|
354
|
+
u = URI.join(ws, us)
|
355
|
+
end
|
322
356
|
|
323
|
-
|
324
|
-
GoodData.with_project(pid, client: self, &block)
|
357
|
+
URI.join(u.to_s.chomp(u.path.to_s), '/uploads/')
|
325
358
|
end
|
326
359
|
end
|
327
360
|
end
|
@@ -80,7 +80,7 @@ module GoodData
|
|
80
80
|
reset_cookies!
|
81
81
|
end
|
82
82
|
|
83
|
-
def refresh_token(
|
83
|
+
def refresh_token(_options = {})
|
84
84
|
begin # rubocop:disable RedundantBegin
|
85
85
|
get TOKEN_PATH, :dont_reauth => true # avoid infinite loop GET fails with 401
|
86
86
|
rescue Exception => e # rubocop:disable RescueException
|
@@ -99,28 +99,28 @@ module GoodData
|
|
99
99
|
# HTTP DELETE
|
100
100
|
#
|
101
101
|
# @param uri [String] Target URI
|
102
|
-
def delete(uri,
|
102
|
+
def delete(uri, _options = {})
|
103
103
|
fail NotImplementedError "DELETE #{uri}"
|
104
104
|
end
|
105
105
|
|
106
106
|
# HTTP GET
|
107
107
|
#
|
108
108
|
# @param uri [String] Target URI
|
109
|
-
def get(uri,
|
109
|
+
def get(uri, _options = {})
|
110
110
|
fail NotImplementedError "GET #{uri}"
|
111
111
|
end
|
112
112
|
|
113
113
|
# HTTP PUT
|
114
114
|
#
|
115
115
|
# @param uri [String] Target URI
|
116
|
-
def put(uri,
|
116
|
+
def put(uri, _data, _options = {})
|
117
117
|
fail NotImplementedError "PUT #{uri}"
|
118
118
|
end
|
119
119
|
|
120
120
|
# HTTP POST
|
121
121
|
#
|
122
122
|
# @param uri [String] Target URI
|
123
|
-
def post(uri,
|
123
|
+
def post(uri, _data, _options = {})
|
124
124
|
fail NotImplementedError "POST #{uri}"
|
125
125
|
end
|
126
126
|
|
@@ -132,7 +132,7 @@ module GoodData
|
|
132
132
|
end
|
133
133
|
|
134
134
|
def stats_table(values = stats)
|
135
|
-
sorted = values.sort_by { |
|
135
|
+
sorted = values.sort_by { |_k, v| v[:avg] }
|
136
136
|
Terminal::Table.new :headings => %w(title avg min max total calls) do |t|
|
137
137
|
sorted.each do |l|
|
138
138
|
row = [
|
@@ -179,7 +179,7 @@ module GoodData
|
|
179
179
|
keys = keys.reduce([]) { |a, e| a.concat([e.to_s, e.to_sym]) }
|
180
180
|
|
181
181
|
new_params = params.deep_dup
|
182
|
-
GoodData::Helpers.hash_dfs(new_params) do |k,
|
182
|
+
GoodData::Helpers.hash_dfs(new_params) do |k, _key|
|
183
183
|
keys.each do |key_to_scrub|
|
184
184
|
k[key_to_scrub] = ('*' * k[key_to_scrub].length) if k && k.key?(key_to_scrub) && k[key_to_scrub]
|
185
185
|
end
|
@@ -12,7 +12,7 @@ module GoodData
|
|
12
12
|
end
|
13
13
|
|
14
14
|
# Connect using username and password
|
15
|
-
def connect(
|
15
|
+
def connect(_username, _password)
|
16
16
|
end
|
17
17
|
|
18
18
|
# Disconnect
|
@@ -22,28 +22,28 @@ module GoodData
|
|
22
22
|
# HTTP DELETE
|
23
23
|
#
|
24
24
|
# @param uri [String] Target URI
|
25
|
-
def delete(uri,
|
25
|
+
def delete(uri, _options = {})
|
26
26
|
puts "DELETE #{uri}"
|
27
27
|
end
|
28
28
|
|
29
29
|
# HTTP GET
|
30
30
|
#
|
31
31
|
# @param uri [String] Target URI
|
32
|
-
def get(uri,
|
32
|
+
def get(uri, _options = {})
|
33
33
|
puts "GET #{uri}"
|
34
34
|
end
|
35
35
|
|
36
36
|
# HTTP PUT
|
37
37
|
#
|
38
38
|
# @param uri [String] Target URI
|
39
|
-
def put(uri,
|
39
|
+
def put(uri, _data, _options = {})
|
40
40
|
puts "PUT #{uri}"
|
41
41
|
end
|
42
42
|
|
43
43
|
# HTTP POST
|
44
44
|
#
|
45
45
|
# @param uri [String] Target URI
|
46
|
-
def post(uri,
|
46
|
+
def post(uri, _data, _options = {})
|
47
47
|
puts "POST #{uri}"
|
48
48
|
end
|
49
49
|
end
|
@@ -44,10 +44,10 @@ module GoodData
|
|
44
44
|
# HTTP GET
|
45
45
|
#
|
46
46
|
# @param uri [String] Target URI
|
47
|
-
def get(uri, options = {})
|
47
|
+
def get(uri, options = {}, &user_block)
|
48
48
|
GoodData.logger.debug "GET: #{@server.url}#{uri}"
|
49
49
|
profile "GET #{uri}" do
|
50
|
-
b = proc { @server[uri].get
|
50
|
+
b = proc { @server[uri].get(cookies, &user_block) }
|
51
51
|
process_response(options, &b)
|
52
52
|
end
|
53
53
|
end
|
@@ -77,67 +77,129 @@ module GoodData
|
|
77
77
|
end
|
78
78
|
|
79
79
|
# Uploads a file to GoodData server
|
80
|
-
# /uploads/ resources are special in that they use a different
|
81
|
-
# host and a basic authentication.
|
82
80
|
def upload(file, options = {})
|
83
|
-
|
84
|
-
|
85
|
-
|
81
|
+
def do_stream_file(uri, filename, options = {})
|
82
|
+
puts "uploading the file #{uri}"
|
83
|
+
|
84
|
+
to_upload = File.new(filename)
|
85
|
+
cookies_str = cookies[:cookies].map { |cookie| "#{cookie[0]}=#{cookie[1]}" }.join(';')
|
86
|
+
req = Net::HTTP::Put.new(uri.path, 'User-Agent' => GoodData.gem_version_string, 'Cookie' => cookies_str)
|
87
|
+
req.content_length = to_upload.size
|
88
|
+
req.body_stream = to_upload
|
89
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
90
|
+
http.use_ssl = true
|
91
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
92
|
+
response = http.start { |client| client.request(req) }
|
93
|
+
case response
|
94
|
+
when Net::HTTPSuccess then
|
95
|
+
true
|
96
|
+
when Net::HTTPUnauthorized
|
97
|
+
refresh_token
|
98
|
+
do_stream_file uri, filename, options
|
99
|
+
else
|
100
|
+
fail "Can't upload file to webdav. Path: #{uri}, response code: #{response.code}, response body: #{response.body}"
|
101
|
+
end
|
102
|
+
end
|
86
103
|
|
87
|
-
|
88
|
-
unless dir.empty?
|
104
|
+
def webdav_dir_exists?(url)
|
89
105
|
method = :get
|
90
106
|
GoodData.logger.debug "#{method}: #{url}"
|
91
|
-
|
92
|
-
|
107
|
+
|
108
|
+
b = proc do
|
109
|
+
raw = {
|
110
|
+
:method => method,
|
111
|
+
:url => url,
|
112
|
+
:headers => @headers
|
113
|
+
}
|
114
|
+
begin
|
115
|
+
RestClient::Request.execute(raw.merge(cookies))
|
116
|
+
rescue RestClient::Exception => e
|
117
|
+
false if e.http_code == 404
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
process_with_tt_refresh(&b)
|
122
|
+
end
|
123
|
+
|
124
|
+
def create_webdav_dir_if_needed(url)
|
125
|
+
return if webdav_dir_exists?(url)
|
126
|
+
|
127
|
+
method = :mkcol
|
128
|
+
GoodData.logger.debug "#{method}: #{url}"
|
129
|
+
b = proc do
|
93
130
|
raw = {
|
94
131
|
:method => method,
|
95
132
|
:url => url,
|
96
|
-
# :timeout => @options[:timeout],
|
97
133
|
:headers => @headers
|
98
134
|
}.merge(cookies)
|
99
135
|
RestClient::Request.execute(raw)
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
136
|
+
end
|
137
|
+
|
138
|
+
process_with_tt_refresh(&b)
|
139
|
+
end
|
140
|
+
|
141
|
+
dir = options[:directory] || ''
|
142
|
+
staging_uri = options[:staging_url].to_s
|
143
|
+
url = dir.empty? ? staging_uri : URI.join(staging_uri, "#{dir}/").to_s
|
144
|
+
|
145
|
+
# Make a directory, if needed
|
146
|
+
create_webdav_dir_if_needed url unless dir.empty?
|
147
|
+
|
148
|
+
webdav_filename = options[:filename] || File.basename(file)
|
149
|
+
do_stream_file URI.join(url, CGI.escape(webdav_filename)), file
|
150
|
+
end
|
151
|
+
|
152
|
+
def download(what, where, options = {})
|
153
|
+
dir = options[:directory] || ''
|
154
|
+
staging_uri = options[:staging_url].to_s
|
155
|
+
|
156
|
+
base_url = dir.empty? ? staging_uri : URI.join(staging_uri, "#{dir}/").to_s
|
157
|
+
url = URI.join(base_url, CGI.escape(what)).to_s
|
158
|
+
|
159
|
+
b = proc do
|
160
|
+
raw = {
|
161
|
+
:headers => {
|
162
|
+
:user_agent => GoodData.gem_version_string
|
163
|
+
},
|
164
|
+
:method => :get,
|
165
|
+
:url => url
|
166
|
+
}.merge(cookies)
|
167
|
+
|
168
|
+
if where.is_a?(String)
|
169
|
+
File.open(where, 'w') do |f|
|
170
|
+
RestClient::Request.execute(raw) do |chunk, _x, _y|
|
171
|
+
f.write chunk
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
else
|
176
|
+
# Assume it is a IO stream
|
177
|
+
RestClient::Request.execute(raw) do |chunk, _x, _y|
|
178
|
+
where.write chunk
|
111
179
|
end
|
112
180
|
end
|
113
181
|
end
|
114
182
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
:user_agent => GoodData.gem_version_string
|
126
|
-
},
|
127
|
-
:payload => payload,
|
128
|
-
:raw_response => true,
|
129
|
-
# :user => @username,
|
130
|
-
# :password => @password
|
131
|
-
}.merge(cookies)
|
132
|
-
RestClient::Request.execute(raw)
|
133
|
-
true
|
183
|
+
process_with_tt_refresh(&b)
|
184
|
+
end
|
185
|
+
|
186
|
+
private
|
187
|
+
|
188
|
+
def process_with_tt_refresh(&block)
|
189
|
+
block.call
|
190
|
+
rescue RestClient::Unauthorized
|
191
|
+
refresh_token
|
192
|
+
block.call
|
134
193
|
end
|
135
194
|
|
136
195
|
private
|
137
196
|
|
138
197
|
def process_response(options = {}, &block)
|
139
198
|
begin
|
140
|
-
|
199
|
+
# Simply try again when ConnectionReset, ConnectionRefused etc.. (see e.g. MSF-7591)
|
200
|
+
response = GoodData::Rest::Client.retryable(:tries => 2, :on => SystemCallError) do
|
201
|
+
block.call
|
202
|
+
end
|
141
203
|
rescue RestClient::Unauthorized
|
142
204
|
raise $ERROR_INFO if options[:dont_reauth]
|
143
205
|
refresh_token
|
@@ -150,7 +212,7 @@ module GoodData
|
|
150
212
|
|
151
213
|
if content_type == 'application/json' || content_type == 'application/json;charset=UTF-8'
|
152
214
|
result = response.to_str == '""' ? {} : MultiJson.load(response.to_str)
|
153
|
-
GoodData.logger.debug "Response: #{result.inspect}"
|
215
|
+
GoodData.logger.debug "Request ID: #{response.headers[:x_gdc_request]} - Response: #{result.inspect}"
|
154
216
|
elsif ['text/plain;charset=UTF-8', 'text/plain; charset=UTF-8', 'text/plain'].include?(content_type)
|
155
217
|
result = response
|
156
218
|
GoodData.logger.debug 'Response: plain text'
|