gooddata 0.6.10 → 0.6.11
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 +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'
|