gooddata 0.6.0.pre11 → 0.6.0
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/.travis.yml +12 -1
- data/.yardopts +2 -0
- data/README.md +6 -3
- data/Rakefile +24 -7
- data/gooddata +2 -2
- data/gooddata.gemspec +4 -3
- data/lib/gooddata.rb +17 -12
- data/lib/gooddata/bricks/base_downloader.rb +7 -7
- data/lib/gooddata/bricks/brick.rb +7 -8
- data/lib/gooddata/bricks/bricks.rb +4 -1
- data/lib/gooddata/bricks/middleware/base_middleware.rb +2 -2
- data/lib/gooddata/bricks/middleware/bench_middleware.rb +5 -6
- data/lib/gooddata/bricks/middleware/bulk_salesforce_middleware.rb +21 -22
- data/lib/gooddata/bricks/middleware/fs_upload_middleware.rb +3 -4
- data/lib/gooddata/bricks/middleware/gooddata_middleware.rb +14 -14
- data/lib/gooddata/bricks/middleware/logger_middleware.rb +6 -6
- data/lib/gooddata/bricks/middleware/middleware.rb +4 -1
- data/lib/gooddata/bricks/middleware/restforce_middleware.rb +29 -32
- data/lib/gooddata/bricks/middleware/stdout_middleware.rb +5 -5
- data/lib/gooddata/bricks/middleware/twitter_middleware.rb +6 -8
- data/lib/gooddata/bricks/utils.rb +3 -3
- data/lib/gooddata/cli/cli.rb +4 -2
- data/lib/gooddata/cli/commands/api_cmd.rb +6 -4
- data/lib/gooddata/cli/commands/auth_cmd.rb +5 -3
- data/lib/gooddata/cli/commands/console_cmd.rb +1 -1
- data/lib/gooddata/cli/commands/process_cmd.rb +6 -4
- data/lib/gooddata/cli/commands/profile_cmd.rb +5 -3
- data/lib/gooddata/cli/commands/project_cmd.rb +24 -22
- data/lib/gooddata/cli/commands/run_ruby_cmd.rb +12 -10
- data/lib/gooddata/cli/commands/scaffold_cmd.rb +8 -6
- data/lib/gooddata/cli/hooks.rb +4 -2
- data/lib/gooddata/cli/shared.rb +3 -1
- data/lib/gooddata/cli/terminal.rb +16 -0
- data/lib/gooddata/client.rb +28 -22
- data/lib/gooddata/commands/api.rb +43 -26
- data/lib/gooddata/commands/auth.rb +22 -53
- data/lib/gooddata/commands/base.rb +2 -0
- data/lib/gooddata/commands/commands.rb +3 -0
- data/lib/gooddata/commands/datasets.rb +39 -136
- data/lib/gooddata/commands/process.rb +134 -130
- data/lib/gooddata/commands/profile.rb +2 -0
- data/lib/gooddata/commands/projects.rb +91 -129
- data/lib/gooddata/commands/runners.rb +11 -11
- data/lib/gooddata/commands/scaffold.rb +28 -26
- data/lib/gooddata/connection.rb +61 -68
- data/lib/gooddata/core/core.rb +1 -2
- data/lib/gooddata/data/data.rb +7 -0
- data/lib/gooddata/data/guesser.rb +114 -0
- data/lib/gooddata/exceptions/command_failed.rb +7 -0
- data/lib/gooddata/exceptions/exceptions.rb +7 -0
- data/lib/gooddata/{exceptions.rb → exceptions/project_not_found.rb} +2 -2
- data/lib/gooddata/extensions/big_decimal.rb +5 -0
- data/lib/gooddata/extract.rb +2 -0
- data/lib/gooddata/goodzilla/goodzilla.rb +11 -12
- data/lib/gooddata/helpers.rb +49 -35
- data/lib/gooddata/models/attribute.rb +7 -5
- data/lib/gooddata/models/dashboard.rb +44 -45
- data/lib/gooddata/models/data_result.rb +10 -13
- data/lib/gooddata/models/data_set.rb +6 -6
- data/lib/gooddata/models/display_form.rb +4 -4
- data/lib/gooddata/models/empty_result.rb +4 -3
- data/lib/gooddata/models/fact.rb +5 -5
- data/lib/gooddata/models/links.rb +3 -1
- data/lib/gooddata/models/metadata.rb +34 -32
- data/lib/gooddata/models/metric.rb +33 -34
- data/lib/gooddata/models/model.rb +165 -173
- data/lib/gooddata/models/models.rb +3 -0
- data/lib/gooddata/models/process.rb +18 -17
- data/lib/gooddata/models/profile.rb +3 -1
- data/lib/gooddata/models/project.rb +107 -35
- data/lib/gooddata/models/project_metadata.rb +12 -12
- data/lib/gooddata/models/report.rb +31 -30
- data/lib/gooddata/models/report_data_result.rb +22 -19
- data/lib/gooddata/models/report_definition.rb +101 -80
- data/lib/gooddata/version.rb +5 -3
- data/lib/templates/bricks/brick.rb.erb +3 -3
- data/lib/templates/bricks/main.rb.erb +3 -2
- data/lib/templates/project/Goodfile.erb +2 -2
- data/lib/templates/project/model/model.rb.erb +19 -19
- data/spec/data/.gooddata +4 -0
- data/spec/helpers/blueprint_helper.rb +2 -2
- data/spec/helpers/cli_helper.rb +28 -0
- data/spec/helpers/connection_helper.rb +2 -2
- data/spec/integration/command_projects_spec.rb +1 -1
- data/spec/integration/create_from_template_spec.rb +12 -0
- data/spec/integration/full_project_spec.rb +2 -2
- data/spec/integration/partial_md_export_import_spec.rb +36 -0
- data/spec/logging_in_logging_out_spec.rb +1 -1
- data/spec/spec_helper.rb +29 -2
- data/spec/unit/cli/cli_spec.rb +3 -3
- data/spec/unit/cli/commands/cmd_api_spec.rb +21 -4
- data/spec/unit/cli/commands/cmd_auth_spec.rb +2 -4
- data/spec/unit/cli/commands/cmd_process_spec.rb +20 -4
- data/spec/unit/cli/commands/cmd_profile_spec.rb +9 -4
- data/spec/unit/cli/commands/cmd_project_spec.rb +53 -4
- data/spec/unit/cli/commands/cmd_run_ruby_spec.rb +2 -4
- data/spec/unit/cli/commands/cmd_scaffold_spec.rb +14 -4
- data/spec/unit/commands/command_api_spec.rb +21 -2
- data/spec/unit/commands/command_auth_spec.rb +62 -1
- data/spec/unit/commands/command_dataset_spec.rb +31 -3
- data/spec/unit/commands/command_process_spec.rb +75 -1
- data/spec/unit/commands/command_profile_spec.rb +7 -1
- data/spec/unit/commands/command_projects_spec.rb +1 -1
- data/spec/unit/commands/command_scaffold_spec.rb +46 -1
- data/spec/unit/core/connection_spec.rb +1 -0
- data/spec/unit/data/guesser_spec.rb +54 -0
- data/spec/unit/helpers_spec.rb +47 -0
- data/spec/unit/model/schema_builder_spec.rb +2 -0
- data/spec/unit/model/tools_spec.rb +89 -0
- data/test/test_upload.rb +39 -15
- metadata +98 -75
- data/test/test_commands.rb +0 -85
- data/test/test_guessing.rb +0 -46
- data/test/test_model.rb +0 -81
- data/test/test_rest_api_basic.rb +0 -41
|
@@ -1,24 +1,25 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
1
3
|
module GoodData::Command
|
|
2
4
|
class Runners
|
|
3
|
-
|
|
4
5
|
def self.run_ruby_locally(brick_dir, options={})
|
|
5
6
|
pid = options[:project_id]
|
|
6
|
-
fail
|
|
7
|
-
fail
|
|
8
|
-
fail
|
|
7
|
+
fail 'You have to specify a project ID' if pid.nil?
|
|
8
|
+
fail 'You have to specify directory of the brick run' if brick_dir.nil?
|
|
9
|
+
fail 'You specified file as a birck run directory. You have to specify directory.' if File.exist?(brick_dir) && !File.directory?(brick_dir)
|
|
9
10
|
|
|
10
11
|
params = options[:expanded_params] || {}
|
|
11
12
|
|
|
12
13
|
GoodData.connection.connect!
|
|
13
|
-
sst = GoodData.connection.cookies[:cookies][
|
|
14
|
+
sst = GoodData.connection.cookies[:cookies]['GDCAuthSST']
|
|
14
15
|
pwd = Pathname.new(Dir.pwd)
|
|
15
16
|
logger_stream = STDOUT
|
|
16
17
|
|
|
17
18
|
server_uri = URI(options[:server]) unless options[:server].nil?
|
|
18
|
-
scheme = server_uri.nil? ?
|
|
19
|
-
hostname = server_uri.nil? ?
|
|
19
|
+
scheme = server_uri.nil? ? '' : server_uri.scheme
|
|
20
|
+
hostname = server_uri.nil? ? '' : server_uri.host
|
|
20
21
|
|
|
21
|
-
script_body = <<-script_body
|
|
22
|
+
script_body = <<-script_body
|
|
22
23
|
require 'fileutils'
|
|
23
24
|
FileUtils::cd(\"#{pwd+brick_dir}\") do\
|
|
24
25
|
require 'bundler/setup'
|
|
@@ -33,12 +34,11 @@ script_body = <<-script_body
|
|
|
33
34
|
}.merge(#{params})
|
|
34
35
|
eval(File.read(\"./main.rb\"))
|
|
35
36
|
end
|
|
36
|
-
script_body
|
|
37
|
+
script_body
|
|
37
38
|
|
|
38
39
|
Bundler.with_clean_env do
|
|
39
|
-
system(
|
|
40
|
+
system('ruby', '-e', script_body)
|
|
40
41
|
end
|
|
41
42
|
end
|
|
42
|
-
|
|
43
43
|
end
|
|
44
44
|
end
|
|
@@ -1,61 +1,63 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
require 'erubis'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
|
|
1
6
|
module GoodData::Command
|
|
2
7
|
class Scaffold
|
|
3
|
-
|
|
8
|
+
TEMPLATES_PATH = Pathname(__FILE__) + '../../../templates'
|
|
4
9
|
|
|
10
|
+
class << self
|
|
11
|
+
# Scaffolds new project
|
|
12
|
+
# TODO: Add option for custom output dir
|
|
5
13
|
def project(name)
|
|
6
|
-
|
|
7
|
-
require 'fileutils'
|
|
8
|
-
|
|
9
|
-
templates_path = Pathname(__FILE__) + "../../../templates"
|
|
14
|
+
fail ArgumentError, 'No name specified' if name.nil?
|
|
10
15
|
|
|
11
16
|
FileUtils.mkdir(name)
|
|
12
17
|
FileUtils.cd(name) do
|
|
13
18
|
|
|
14
|
-
FileUtils.mkdir(
|
|
15
|
-
FileUtils.cd(
|
|
16
|
-
input = File.read(
|
|
19
|
+
FileUtils.mkdir('model')
|
|
20
|
+
FileUtils.cd('model') do
|
|
21
|
+
input = File.read(TEMPLATES_PATH + 'project/model/model.rb.erb')
|
|
17
22
|
eruby = Erubis::Eruby.new(input)
|
|
18
|
-
File.open(
|
|
23
|
+
File.open('model.rb', 'w') do |f|
|
|
19
24
|
f.write(eruby.result(:name => name))
|
|
20
25
|
end
|
|
21
26
|
end
|
|
22
27
|
|
|
23
|
-
FileUtils.mkdir(
|
|
24
|
-
FileUtils.cd(
|
|
25
|
-
FileUtils.cp(Dir.glob(
|
|
28
|
+
FileUtils.mkdir('data')
|
|
29
|
+
FileUtils.cd('data') do
|
|
30
|
+
FileUtils.cp(Dir.glob(TEMPLATES_PATH + 'project/data/*.csv'), '.')
|
|
26
31
|
end
|
|
27
32
|
|
|
28
|
-
input = File.read(
|
|
33
|
+
input = File.read(TEMPLATES_PATH + 'project/Goodfile.erb')
|
|
29
34
|
eruby = Erubis::Eruby.new(input)
|
|
30
|
-
File.open(
|
|
35
|
+
File.open('Goodfile', 'w') do |f|
|
|
31
36
|
f.write(eruby.result())
|
|
32
37
|
end
|
|
33
38
|
end
|
|
34
39
|
end
|
|
35
40
|
|
|
41
|
+
# Scaffolds new brick
|
|
42
|
+
# TODO: Add option for custom output dir
|
|
36
43
|
def brick(name)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
require 'fileutils'
|
|
40
|
-
|
|
41
|
-
templates_path = Pathname(__FILE__) + "../../../templates"
|
|
42
|
-
|
|
44
|
+
fail ArgumentError, 'No name specified' if name.nil?
|
|
45
|
+
|
|
43
46
|
FileUtils.mkdir(name)
|
|
44
47
|
FileUtils.cd(name) do
|
|
45
|
-
input = File.read(
|
|
48
|
+
input = File.read(TEMPLATES_PATH + 'bricks/brick.rb.erb')
|
|
46
49
|
eruby = Erubis::Eruby.new(input)
|
|
47
|
-
File.open(
|
|
50
|
+
File.open('brick.rb', 'w') do |f|
|
|
48
51
|
f.write(eruby.result())
|
|
49
52
|
end
|
|
50
|
-
|
|
51
|
-
input = File.read(
|
|
53
|
+
|
|
54
|
+
input = File.read(TEMPLATES_PATH + 'bricks/main.rb.erb')
|
|
52
55
|
eruby = Erubis::Eruby.new(input)
|
|
53
|
-
File.open(
|
|
56
|
+
File.open('main.rb', 'w') do |f|
|
|
54
57
|
f.write(eruby.result())
|
|
55
58
|
end
|
|
56
59
|
end
|
|
57
60
|
end
|
|
58
|
-
|
|
59
61
|
end
|
|
60
62
|
end
|
|
61
63
|
end
|
data/lib/gooddata/connection.rb
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
require 'multi_json'
|
|
2
4
|
require 'rest-client'
|
|
3
5
|
|
|
4
|
-
|
|
6
|
+
require_relative 'version'
|
|
5
7
|
|
|
6
8
|
module GoodData
|
|
7
|
-
|
|
8
9
|
# # GoodData HTTP wrapper
|
|
9
10
|
#
|
|
10
11
|
# Provides a convenient HTTP wrapper for talking with the GoodData API.
|
|
@@ -27,7 +28,6 @@ module GoodData
|
|
|
27
28
|
# To send a HTTP request use either the get, post or delete methods documented below.
|
|
28
29
|
#
|
|
29
30
|
class Connection
|
|
30
|
-
|
|
31
31
|
DEFAULT_URL = 'https://secure.gooddata.com'
|
|
32
32
|
LOGIN_PATH = '/gdc/account/login'
|
|
33
33
|
TOKEN_PATH = '/gdc/account/token'
|
|
@@ -46,7 +46,7 @@ module GoodData
|
|
|
46
46
|
# end
|
|
47
47
|
#
|
|
48
48
|
def retryable(options = {}, &block)
|
|
49
|
-
opts = {
|
|
49
|
+
opts = {:tries => 1, :on => Exception}.merge(options)
|
|
50
50
|
|
|
51
51
|
retry_exception, retries = opts[:on], opts[:tries]
|
|
52
52
|
|
|
@@ -67,15 +67,14 @@ module GoodData
|
|
|
67
67
|
# @param password The GoodData account password
|
|
68
68
|
#
|
|
69
69
|
def initialize(username, password, options = {})
|
|
70
|
-
@status
|
|
71
|
-
@username
|
|
72
|
-
@password
|
|
73
|
-
@url
|
|
70
|
+
@status = :not_connected
|
|
71
|
+
@username = username
|
|
72
|
+
@password = password
|
|
73
|
+
@url = options[:server] || DEFAULT_URL
|
|
74
74
|
@auth_token = options[:gdc_temporary_token]
|
|
75
|
-
@options
|
|
75
|
+
@options = options
|
|
76
76
|
|
|
77
77
|
@server = create_server_connection(@url, @options)
|
|
78
|
-
|
|
79
78
|
end
|
|
80
79
|
|
|
81
80
|
# Returns the user JSON object of the currently logged in GoodData user account.
|
|
@@ -158,7 +157,7 @@ module GoodData
|
|
|
158
157
|
|
|
159
158
|
# Get the cookies associated with the current connection.
|
|
160
159
|
def cookies
|
|
161
|
-
@cookies ||= {
|
|
160
|
+
@cookies ||= {:cookies => {}}
|
|
162
161
|
end
|
|
163
162
|
|
|
164
163
|
# Set the cookies used when communicating with the GoodData API.
|
|
@@ -189,13 +188,12 @@ module GoodData
|
|
|
189
188
|
# /uploads/ resources are special in that they use a different
|
|
190
189
|
# host and a basic authentication.
|
|
191
190
|
def upload(file, options={})
|
|
192
|
-
|
|
193
191
|
ensure_connection
|
|
194
192
|
|
|
195
193
|
dir = options[:directory] || ''
|
|
196
194
|
staging_uri = options[:staging_url].to_s
|
|
197
195
|
url = dir.empty? ? staging_uri : URI.join(staging_uri, "#{dir}/").to_s
|
|
198
|
-
|
|
196
|
+
|
|
199
197
|
# Make a directory, if needed
|
|
200
198
|
unless dir.empty? then
|
|
201
199
|
method = :get
|
|
@@ -203,46 +201,46 @@ module GoodData
|
|
|
203
201
|
begin
|
|
204
202
|
# first check if it does exits
|
|
205
203
|
RestClient::Request.execute({
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
204
|
+
:method => method,
|
|
205
|
+
:url => url,
|
|
206
|
+
:timeout => @options[:timeout],
|
|
207
|
+
:headers => {
|
|
208
|
+
:user_agent => GoodData.gem_version_string
|
|
209
|
+
}}.merge(cookies)
|
|
212
210
|
)
|
|
213
211
|
rescue RestClient::Exception => e
|
|
214
212
|
if e.http_code == 404 then
|
|
215
213
|
method = :mkcol
|
|
216
214
|
GoodData.logger.debug "#{method}: #{url}"
|
|
217
215
|
RestClient::Request.execute({
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
216
|
+
:method => method,
|
|
217
|
+
:url => url,
|
|
218
|
+
:timeout => @options[:timeout],
|
|
219
|
+
:headers => {
|
|
220
|
+
:user_agent => GoodData.gem_version_string
|
|
221
|
+
}}.merge(cookies)
|
|
224
222
|
)
|
|
225
223
|
end
|
|
226
224
|
end
|
|
227
225
|
end
|
|
228
226
|
|
|
229
|
-
payload = options[:stream] ?
|
|
230
|
-
filename = options[:filename] || options[:stream] ?
|
|
227
|
+
payload = options[:stream] ? 'file' : File.read(file)
|
|
228
|
+
filename = options[:filename] || options[:stream] ? 'randome-filename.txt' : File.basename(file)
|
|
231
229
|
|
|
232
230
|
# Upload the file
|
|
233
231
|
# puts "uploading the file #{URI.join(url, filename).to_s}"
|
|
234
232
|
req = RestClient::Request.new({
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
233
|
+
:method => :put,
|
|
234
|
+
:url => URI.join(url, filename).to_s,
|
|
235
|
+
:timeout => @options[:timeout],
|
|
236
|
+
:headers => {
|
|
237
|
+
:user_agent => GoodData.gem_version_string,
|
|
238
|
+
},
|
|
239
|
+
:payload => payload,
|
|
240
|
+
:raw_response => true,
|
|
241
|
+
:user => @username,
|
|
242
|
+
:password => @password
|
|
243
|
+
})
|
|
246
244
|
# .merge(cookies))
|
|
247
245
|
resp = req.execute
|
|
248
246
|
true
|
|
@@ -252,11 +250,11 @@ module GoodData
|
|
|
252
250
|
staging_uri = options[:staging_url].to_s
|
|
253
251
|
url = staging_uri + what
|
|
254
252
|
req = RestClient::Request.new({
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
253
|
+
:method => 'GET',
|
|
254
|
+
:url => url,
|
|
255
|
+
:user => @username,
|
|
256
|
+
:password => @password
|
|
257
|
+
})
|
|
260
258
|
|
|
261
259
|
if where.is_a?(String)
|
|
262
260
|
File.open(where, 'w') do |f|
|
|
@@ -277,8 +275,8 @@ module GoodData
|
|
|
277
275
|
end
|
|
278
276
|
|
|
279
277
|
def disconnect
|
|
280
|
-
if connected? && GoodData.connection.user[
|
|
281
|
-
GoodData.delete(GoodData.connection.user[
|
|
278
|
+
if connected? && GoodData.connection.user['state']
|
|
279
|
+
GoodData.delete(GoodData.connection.user['state'])
|
|
282
280
|
@status = :not_connected
|
|
283
281
|
end
|
|
284
282
|
end
|
|
@@ -287,12 +285,12 @@ module GoodData
|
|
|
287
285
|
|
|
288
286
|
def create_server_connection(url, options)
|
|
289
287
|
RestClient::Resource.new url,
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
288
|
+
:timeout => options[:timeout],
|
|
289
|
+
:headers => {
|
|
290
|
+
:content_type => :json,
|
|
291
|
+
:accept => [:json, :zip],
|
|
292
|
+
:user_agent => GoodData::gem_version_string,
|
|
293
|
+
}
|
|
296
294
|
end
|
|
297
295
|
|
|
298
296
|
def ensure_connection
|
|
@@ -300,7 +298,7 @@ module GoodData
|
|
|
300
298
|
end
|
|
301
299
|
|
|
302
300
|
def connect
|
|
303
|
-
GoodData.logger.info
|
|
301
|
+
GoodData.logger.info 'Connecting to GoodData...'
|
|
304
302
|
@status = :connecting
|
|
305
303
|
authenticate
|
|
306
304
|
end
|
|
@@ -313,7 +311,7 @@ module GoodData
|
|
|
313
311
|
'remember' => 1
|
|
314
312
|
}
|
|
315
313
|
}
|
|
316
|
-
GoodData.logger.debug
|
|
314
|
+
GoodData.logger.debug 'Logging in...'
|
|
317
315
|
@user = post(LOGIN_PATH, credentials, :dont_reauth => true)['userLogin']
|
|
318
316
|
refresh_token :dont_reauth => true # avoid infinite loop if refresh_token fails with 401
|
|
319
317
|
|
|
@@ -334,19 +332,19 @@ module GoodData
|
|
|
334
332
|
return response if options[:process] == false
|
|
335
333
|
|
|
336
334
|
if content_type == "application/json" || content_type == "application/json;charset=UTF-8" then
|
|
337
|
-
result = response.to_str == '""' ? {} :
|
|
335
|
+
result = response.to_str == '""' ? {} : MultiJson.load(response.to_str)
|
|
338
336
|
GoodData.logger.debug "Response: #{result.inspect}"
|
|
339
|
-
elsif content_type ==
|
|
337
|
+
elsif content_type == 'application/zip' then
|
|
340
338
|
result = response
|
|
341
|
-
GoodData.logger.debug
|
|
339
|
+
GoodData.logger.debug 'Response: a zipped stream'
|
|
342
340
|
elsif response.headers[:content_length].to_s == '0'
|
|
343
341
|
result = nil
|
|
344
|
-
GoodData.logger.debug
|
|
342
|
+
GoodData.logger.debug 'Response: Empty response possibly 204'
|
|
345
343
|
elsif response.code == 204
|
|
346
344
|
result = nil
|
|
347
|
-
GoodData.logger.debug
|
|
345
|
+
GoodData.logger.debug 'Response: 204 no content'
|
|
348
346
|
else
|
|
349
|
-
raise "Unsupported response content type '%s':\n%s" % [
|
|
347
|
+
raise "Unsupported response content type '%s':\n%s" % [content_type, response.to_str[0..127]]
|
|
350
348
|
end
|
|
351
349
|
result
|
|
352
350
|
rescue RestClient::Exception => e
|
|
@@ -356,7 +354,7 @@ module GoodData
|
|
|
356
354
|
end
|
|
357
355
|
|
|
358
356
|
def refresh_token(options = {})
|
|
359
|
-
GoodData.logger.debug
|
|
357
|
+
GoodData.logger.debug 'Getting authentication token...'
|
|
360
358
|
begin
|
|
361
359
|
get TOKEN_PATH, :dont_reauth => true # avoid infinite loop GET fails with 401
|
|
362
360
|
rescue RestClient::Unauthorized
|
|
@@ -366,20 +364,15 @@ module GoodData
|
|
|
366
364
|
end
|
|
367
365
|
|
|
368
366
|
def scrub_params(params, keys)
|
|
369
|
-
keys = keys.reduce([]) {|memo, k| memo.concat([k.to_s, k.to_sym])}
|
|
367
|
+
keys = keys.reduce([]) { |memo, k| memo.concat([k.to_s, k.to_sym]) }
|
|
370
368
|
|
|
371
369
|
new_params = Marshal.load(Marshal.dump(params))
|
|
372
370
|
GoodData::Helpers.hash_dfs(new_params) do |k, key|
|
|
373
371
|
keys.each do |key_to_scrub|
|
|
374
|
-
|
|
375
|
-
k[key_to_scrub] = ("*" * k[key_to_scrub].length) if k && k.has_key?(key_to_scrub) && k[key_to_scrub]
|
|
376
|
-
rescue
|
|
377
|
-
binding.pry
|
|
378
|
-
end
|
|
372
|
+
k[key_to_scrub] = ('*' * k[key_to_scrub].length) if k && k.has_key?(key_to_scrub) && k[key_to_scrub]
|
|
379
373
|
end
|
|
380
374
|
end
|
|
381
375
|
new_params
|
|
382
376
|
end
|
|
383
|
-
|
|
384
377
|
end
|
|
385
378
|
end
|
data/lib/gooddata/core/core.rb
CHANGED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
require 'date'
|
|
4
|
+
|
|
5
|
+
require_relative '../extract'
|
|
6
|
+
require_relative '../exceptions/command_failed'
|
|
7
|
+
|
|
8
|
+
module GoodData
|
|
9
|
+
module Data
|
|
10
|
+
##
|
|
11
|
+
# Utility class to guess data types of a data stream by looking at first couple of rows
|
|
12
|
+
#
|
|
13
|
+
class Guesser
|
|
14
|
+
TYPES_PRIORITY = [:connection_point, :fact, :date, :attribute]
|
|
15
|
+
attr_reader :headers
|
|
16
|
+
|
|
17
|
+
class << self
|
|
18
|
+
def sort_types(types)
|
|
19
|
+
types.sort do |x, y|
|
|
20
|
+
TYPES_PRIORITY.index(x) <=> TYPES_PRIORITY.index(y)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def initialize(reader)
|
|
26
|
+
@reader = reader
|
|
27
|
+
@headers = reader.shift.map! { |h| h.to_s } or raise 'Empty data set'
|
|
28
|
+
@pros = {}; @cons = {}; @seen = {}
|
|
29
|
+
@headers.map do |h|
|
|
30
|
+
@cons[h.to_s] = {}
|
|
31
|
+
@pros[h.to_s] = {}
|
|
32
|
+
@seen[h.to_s] = {}
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def guess(limit)
|
|
37
|
+
count = 0
|
|
38
|
+
while row = @reader.shift
|
|
39
|
+
break unless row && !row.empty? && count < limit
|
|
40
|
+
raise '%i fields in row %i, %i expected' % [row.size, count + 1, @headers.size] if row.size != @headers.size
|
|
41
|
+
row.each_with_index do |value, j|
|
|
42
|
+
header = @headers[j]
|
|
43
|
+
number = check_number(header, value)
|
|
44
|
+
date = check_date(header, value)
|
|
45
|
+
store_guess header, {@pros => :attribute} unless number || date
|
|
46
|
+
hash_increment @seen[header], value
|
|
47
|
+
end
|
|
48
|
+
count += 1
|
|
49
|
+
end
|
|
50
|
+
# fields with unique values are connection point candidates
|
|
51
|
+
@seen.each do |header, values|
|
|
52
|
+
store_guess header, {@pros => :connection_point} if values.size == count
|
|
53
|
+
end
|
|
54
|
+
guess_result
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def guess_result
|
|
60
|
+
result = {}
|
|
61
|
+
@headers.each do |header|
|
|
62
|
+
result[header] = Guesser::sort_types @pros[header].keys.select { |type| @cons[header][type].nil? }
|
|
63
|
+
end
|
|
64
|
+
result
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def hash_increment(hash, key)
|
|
68
|
+
if hash[key]
|
|
69
|
+
hash[key] += 1
|
|
70
|
+
else
|
|
71
|
+
hash[key] = 1
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def check_number(header, value)
|
|
76
|
+
if value.nil? || value =~ /^[\+-]?\d*(\.\d*)?$/
|
|
77
|
+
return store_guess(header, @pros => [:fact, :attribute])
|
|
78
|
+
end
|
|
79
|
+
store_guess header, {@cons => :fact}
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def check_date(header, value)
|
|
83
|
+
return store_guess(header, @pros => [:date, :attribute, :fact]) if value.nil? || value == '0000-00-00'
|
|
84
|
+
begin
|
|
85
|
+
DateTime.parse value
|
|
86
|
+
return store_guess(header, @pros => [:date, :attribute])
|
|
87
|
+
rescue ArgumentError;
|
|
88
|
+
end
|
|
89
|
+
store_guess header, {@cons => :date}
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
##
|
|
93
|
+
# Stores a guess about given header.
|
|
94
|
+
#
|
|
95
|
+
# Returns true if the @pros key is present, false otherwise
|
|
96
|
+
#
|
|
97
|
+
# === Parameters
|
|
98
|
+
#
|
|
99
|
+
# * +header+ - A header name
|
|
100
|
+
# * +guess+ - A hash with optional @pros and @cons keys
|
|
101
|
+
#
|
|
102
|
+
def store_guess(header, guess)
|
|
103
|
+
result = !guess[@pros].nil?
|
|
104
|
+
[@pros, @cons].each do |hash|
|
|
105
|
+
if guess[hash] then
|
|
106
|
+
guess[hash] = [guess[hash]] unless guess[hash].is_a? Array
|
|
107
|
+
guess[hash].each { |type| hash_increment hash[header], type }
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
result
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|