gooddata 0.6.0.pre11 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,7 +1,9 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
1
3
|
require 'pp'
|
2
4
|
|
3
|
-
|
4
|
-
|
5
|
+
require_relative '../shared'
|
6
|
+
require_relative '../../commands/scaffold'
|
5
7
|
|
6
8
|
GoodData::CLI.module_eval do
|
7
9
|
|
@@ -9,21 +11,21 @@ GoodData::CLI.module_eval do
|
|
9
11
|
arg_name 'show'
|
10
12
|
command :scaffold do |c|
|
11
13
|
|
12
|
-
c.desc
|
14
|
+
c.desc 'Scaffold a gooddata project blueprint'
|
13
15
|
c.command :project do |project|
|
14
16
|
project.action do |global_options, options, args|
|
15
17
|
name = args.first
|
16
|
-
fail
|
18
|
+
fail 'Name of the project has to be provided' if name.nil? || name.empty?
|
17
19
|
GoodData::Command::Scaffold.project(name)
|
18
20
|
end
|
19
21
|
end
|
20
22
|
|
21
|
-
c.desc
|
23
|
+
c.desc 'Scaffold a gooddata ruby brick. This is a piece of code that you can run on our platform'
|
22
24
|
c.command :brick do |brick|
|
23
25
|
# brick.arg_name 'name'
|
24
26
|
brick.action do |global_options, options, args|
|
25
27
|
name = args.first
|
26
|
-
fail
|
28
|
+
fail 'Name of the brick has to be provided' if name.nil? || name.empty?
|
27
29
|
GoodData::Command::Scaffold.brick(name)
|
28
30
|
end
|
29
31
|
end
|
data/lib/gooddata/cli/hooks.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
1
3
|
require 'gli'
|
2
4
|
require 'pp'
|
3
5
|
|
@@ -36,8 +38,8 @@ GoodData::CLI.module_eval do
|
|
36
38
|
# Error logic here
|
37
39
|
# return false to skip default error handling
|
38
40
|
# binding.pry
|
39
|
-
pp exception.backtrace
|
41
|
+
# pp exception.backtrace
|
40
42
|
pp exception
|
41
43
|
true
|
42
44
|
end
|
43
|
-
end
|
45
|
+
end
|
data/lib/gooddata/cli/shared.rb
CHANGED
data/lib/gooddata/client.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require_relative 'version'
|
4
|
+
require_relative 'connection'
|
5
|
+
require_relative 'helpers'
|
4
6
|
|
5
7
|
# fastercsv is built in Ruby 1.9
|
6
|
-
if RUBY_VERSION <
|
8
|
+
if RUBY_VERSION < '1.9'
|
7
9
|
require 'fastercsv'
|
8
10
|
else
|
9
11
|
require 'csv'
|
@@ -13,7 +15,7 @@ end
|
|
13
15
|
# Initializes required dynamically loaded classes
|
14
16
|
def init_gd_module()
|
15
17
|
# Metadata packages, such as report.rb, require this to be loaded first
|
16
|
-
|
18
|
+
require_relative 'models/metadata.rb'
|
17
19
|
|
18
20
|
# Load models from models folder
|
19
21
|
Dir[File.dirname(__FILE__) + '/models/*.rb'].each { |file| require file }
|
@@ -62,7 +64,10 @@ module GoodData
|
|
62
64
|
|
63
65
|
# Dummy implementation of logger
|
64
66
|
class NilLogger
|
65
|
-
def debug(*args)
|
67
|
+
def debug(*args)
|
68
|
+
;
|
69
|
+
end
|
70
|
+
|
66
71
|
alias :info :debug
|
67
72
|
alias :warn :debug
|
68
73
|
alias :error :debug
|
@@ -73,6 +78,7 @@ module GoodData
|
|
73
78
|
GoodData.project = project
|
74
79
|
GoodData.project
|
75
80
|
end
|
81
|
+
|
76
82
|
alias :use :project=
|
77
83
|
|
78
84
|
class << self
|
@@ -88,11 +94,11 @@ module GoodData
|
|
88
94
|
#
|
89
95
|
def connect(options=nil, second_options=nil, third_options={})
|
90
96
|
if options.is_a? Hash
|
91
|
-
fail
|
97
|
+
fail 'You have to provide login and password' if ((options[:login].nil? || options[:login].empty?) && (options[:password].nil? || options[:password].empty?))
|
92
98
|
threaded[:connection] = Connection.new(options[:login], options[:password], options)
|
93
99
|
GoodData.project = options[:project] if options[:project]
|
94
100
|
elsif options.is_a?(String) && second_options.is_a?(String)
|
95
|
-
fail
|
101
|
+
fail 'You have to provide login and password' if ((options.nil? || options.empty?) && (second_options.nil? || second_options.empty?))
|
96
102
|
threaded[:connection] = Connection.new(options, second_options, third_options)
|
97
103
|
end
|
98
104
|
|
@@ -126,7 +132,7 @@ module GoodData
|
|
126
132
|
# @param options Options get routed to connect eventually so everything that you can use there should be possible to use here.
|
127
133
|
#
|
128
134
|
def connect_with_sst(token, options={})
|
129
|
-
create_authenticated_connection(options.merge({:cookies => {
|
135
|
+
create_authenticated_connection(options.merge({:cookies => {'GDCAuthSST' => token}}))
|
130
136
|
end
|
131
137
|
|
132
138
|
# This method is aimed at creating an authenticated connection in case you do not hae pass/login but you have SST
|
@@ -146,7 +152,7 @@ module GoodData
|
|
146
152
|
# @param project Project to use
|
147
153
|
# @param bl Block to be performed
|
148
154
|
def with_project(project, &bl)
|
149
|
-
fail
|
155
|
+
fail 'You have to specify a project when using with_project' if project.nil? || (project.is_a?(String) && project.empty?)
|
150
156
|
old_project = GoodData.project
|
151
157
|
begin
|
152
158
|
GoodData.use(project)
|
@@ -162,7 +168,7 @@ module GoodData
|
|
162
168
|
#
|
163
169
|
# @see GoodData.connect
|
164
170
|
def connection
|
165
|
-
threaded[:connection] || raise(
|
171
|
+
threaded[:connection] || raise('Please authenticate with GoodData.connect first')
|
166
172
|
end
|
167
173
|
|
168
174
|
# Sets the active project
|
@@ -264,12 +270,12 @@ module GoodData
|
|
264
270
|
end
|
265
271
|
|
266
272
|
def upload_to_user_webdav(file, options={})
|
267
|
-
u = URI(connection.options[:webdav_server] || GoodData.project.links[
|
268
|
-
url = URI.join(u.to_s.chomp(u.path.to_s),
|
273
|
+
u = URI(connection.options[:webdav_server] || GoodData.project.links['uploads'])
|
274
|
+
url = URI.join(u.to_s.chomp(u.path.to_s), '/uploads/')
|
269
275
|
connection.upload(file, options.merge({
|
270
|
-
|
271
|
-
|
272
|
-
|
276
|
+
:directory => options[:directory],
|
277
|
+
:staging_url => url
|
278
|
+
}))
|
273
279
|
end
|
274
280
|
|
275
281
|
def get_project_webdav_path(file, options={})
|
@@ -280,9 +286,9 @@ module GoodData
|
|
280
286
|
def upload_to_project_webdav(file, options={})
|
281
287
|
url = get_project_webdav_path(file, options)
|
282
288
|
connection.upload(file, options.merge({
|
283
|
-
|
284
|
-
|
285
|
-
|
289
|
+
:directory => options[:directory],
|
290
|
+
:staging_url => url
|
291
|
+
}))
|
286
292
|
end
|
287
293
|
|
288
294
|
def get_user_webdav_path(file, options={})
|
@@ -293,13 +299,13 @@ module GoodData
|
|
293
299
|
def download_from_user_webdav(file, where, options={})
|
294
300
|
url = get_user_webdav_path(file, options)
|
295
301
|
connection.download(file, where, options.merge({
|
296
|
-
|
297
|
-
|
302
|
+
:staging_url => url
|
303
|
+
}))
|
298
304
|
end
|
299
305
|
|
300
306
|
def poll(result, key, options={})
|
301
307
|
sleep_interval = options[:sleep_interval] || 10
|
302
|
-
link = result[key][
|
308
|
+
link = result[key]['links']['poll']
|
303
309
|
response = GoodData.get(link, :process => false)
|
304
310
|
while response.code != 204
|
305
311
|
sleep sleep_interval
|
@@ -1,34 +1,51 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
def info
|
5
|
-
json = {}
|
6
|
-
puts "GoodData API"
|
7
|
-
puts " Version: #{json['releaseName']}"
|
8
|
-
puts " Released: #{json['releaseDate']}"
|
9
|
-
puts " For more info see #{json['releaseNotesUri']}"
|
10
|
-
end
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require_relative '../exceptions/command_failed'
|
11
4
|
|
12
|
-
|
5
|
+
module GoodData
|
6
|
+
module Command
|
7
|
+
# Low level access to GoodData API
|
8
|
+
class Api
|
9
|
+
class << self
|
10
|
+
def info
|
11
|
+
json = {
|
12
|
+
'releaseName' => 'N/A',
|
13
|
+
'releaseDate' => 'N/A',
|
14
|
+
'releaseNotesUri' => 'N/A'
|
15
|
+
}
|
13
16
|
|
14
|
-
|
15
|
-
|
16
|
-
puts "
|
17
|
-
|
18
|
-
puts "Unable to log in to GoodData server!"
|
17
|
+
puts 'GoodData API'
|
18
|
+
puts " Version: #{json['releaseName']}"
|
19
|
+
puts " Released: #{json['releaseDate']}"
|
20
|
+
puts " For more info see #{json['releaseNotesUri']}"
|
19
21
|
end
|
20
|
-
end
|
21
22
|
|
22
|
-
|
23
|
-
raise(CommandFailed, "Specify the path you want to GET.") if path.nil?
|
24
|
-
result = GoodData.get path
|
25
|
-
result rescue puts result
|
26
|
-
end
|
23
|
+
alias_method :index, :info
|
27
24
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
25
|
+
# Test of login
|
26
|
+
def test
|
27
|
+
if GoodData.test_login
|
28
|
+
puts "Succesfully logged in as #{GoodData.profile.user}"
|
29
|
+
else
|
30
|
+
puts 'Unable to log in to GoodData server!'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Get resource
|
35
|
+
# @param path Resource path
|
36
|
+
def get(path)
|
37
|
+
raise(GoodData::CommandFailed, 'Specify the path you want to GET.') if path.nil?
|
38
|
+
result = GoodData.get path
|
39
|
+
result rescue puts result
|
40
|
+
end
|
41
|
+
|
42
|
+
# Delete resource
|
43
|
+
# @param path Resource path
|
44
|
+
def delete(path)
|
45
|
+
raise(GoodData::CommandFailed, 'Specify the path you want to DELETE.') if path.nil?
|
46
|
+
result = GoodData.delete path
|
47
|
+
result rescue puts result
|
48
|
+
end
|
32
49
|
end
|
33
50
|
end
|
34
51
|
end
|
@@ -1,57 +1,31 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
1
3
|
require 'highline/import'
|
2
|
-
require '
|
4
|
+
require 'multi_json'
|
3
5
|
|
4
|
-
|
6
|
+
require_relative '../cli/terminal'
|
7
|
+
require_relative '../helpers'
|
5
8
|
|
6
9
|
module GoodData::Command
|
7
10
|
class Auth
|
8
|
-
|
9
11
|
class << self
|
10
|
-
def connect
|
11
|
-
unless defined? @connected
|
12
|
-
GoodData.connect({
|
13
|
-
:login => user,
|
14
|
-
:password => password,
|
15
|
-
:server => url,
|
16
|
-
:auth_token => auth_token
|
17
|
-
})
|
18
|
-
@connected = true
|
19
|
-
end
|
20
|
-
@connected
|
21
|
-
end
|
22
|
-
|
23
|
-
def user
|
24
|
-
ensure_credentials
|
25
|
-
@credentials[:username]
|
26
|
-
end
|
27
|
-
|
28
|
-
def password
|
29
|
-
ensure_credentials
|
30
|
-
@credentials[:password]
|
31
|
-
end
|
32
|
-
|
33
|
-
def url
|
34
|
-
ensure_credentials
|
35
|
-
@credentials[:url]
|
36
|
-
end
|
37
|
-
|
38
|
-
def auth_token
|
39
|
-
ensure_credentials
|
40
|
-
@credentials[:auth_token]
|
41
|
-
end
|
42
12
|
|
13
|
+
# Get path of .gooddata config
|
43
14
|
def credentials_file
|
44
15
|
"#{GoodData::Helpers.home_directory}/.gooddata"
|
45
16
|
end
|
46
17
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
18
|
+
# Ask for credentials
|
19
|
+
def ask_for_credentials
|
20
|
+
puts 'Enter your GoodData credentials.'
|
21
|
+
user = GoodData::CLI.terminal.ask('Email')
|
22
|
+
password = GoodData::CLI.terminal.ask('Password') { |q| q.echo = 'x' }
|
23
|
+
auth_token = GoodData::CLI.terminal.ask('Authorization Token')
|
24
|
+
|
25
|
+
{:username => user, :password => password, :auth_token => auth_token}
|
53
26
|
end
|
54
27
|
|
28
|
+
# Read credentials
|
55
29
|
def read_credentials
|
56
30
|
if File.exists?(credentials_file) then
|
57
31
|
config = File.read(credentials_file)
|
@@ -61,22 +35,16 @@ module GoodData::Command
|
|
61
35
|
end
|
62
36
|
end
|
63
37
|
|
64
|
-
|
65
|
-
puts "Enter your GoodData credentials."
|
66
|
-
user = HighLine.new.ask("Email")
|
67
|
-
password = HighLine.new.ask("Password") { |q| q.echo = "x" }
|
68
|
-
auth_token = HighLine.new.ask("Authorization Token")
|
69
|
-
{ :username => user, :password => password, :auth_token => auth_token }
|
70
|
-
end
|
71
|
-
|
38
|
+
# Ask for credentials and store them
|
72
39
|
def store
|
73
40
|
credentials = ask_for_credentials
|
74
41
|
|
75
42
|
ovewrite = if File.exist?(credentials_file)
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
43
|
+
GoodData::CLI.terminal.ask("Overwrite existing stored credentials (y/n)")
|
44
|
+
else
|
45
|
+
'y'
|
46
|
+
end
|
47
|
+
|
80
48
|
if ovewrite == 'y'
|
81
49
|
File.open(credentials_file, 'w', 0600) do |f|
|
82
50
|
f.puts JSON.pretty_generate(credentials)
|
@@ -86,6 +54,7 @@ module GoodData::Command
|
|
86
54
|
end
|
87
55
|
end
|
88
56
|
|
57
|
+
# Delete stored credentials
|
89
58
|
def unstore
|
90
59
|
FileUtils.rm_f(credentials_file)
|
91
60
|
end
|
@@ -1,21 +1,26 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
1
3
|
require 'date'
|
2
|
-
|
4
|
+
|
5
|
+
require_relative '../data/guesser'
|
6
|
+
require_relative '../extract'
|
7
|
+
require_relative '../exceptions/command_failed'
|
3
8
|
|
4
9
|
module GoodData
|
5
10
|
module Command
|
6
11
|
class Datasets
|
7
|
-
|
8
12
|
# List all data sets present in the project specified by the --project option
|
9
13
|
#
|
10
|
-
#
|
14
|
+
# ## Usage
|
11
15
|
#
|
12
|
-
#
|
13
|
-
#
|
16
|
+
# gooddata datasets --project <projectid>
|
17
|
+
# gooddata datasets:list --project <projectid>
|
14
18
|
#
|
15
|
-
# *
|
19
|
+
# * `--project` - GoodData project identifier
|
16
20
|
#
|
17
21
|
def index
|
18
|
-
connect
|
22
|
+
# TODO: Review following connect replacement/reimplementation
|
23
|
+
# connect
|
19
24
|
with_project do |project_id|
|
20
25
|
Project[project_id].datasets.each do |ds|
|
21
26
|
puts "#{ds.uri}\t#{ds.identifier}\t#{ds.title}"
|
@@ -28,20 +33,20 @@ module GoodData
|
|
28
33
|
# The command prescans the data set, picks possible LDM types for it's
|
29
34
|
# fields and asks user for confirmation.
|
30
35
|
#
|
31
|
-
#
|
36
|
+
# ## Usage
|
32
37
|
#
|
33
|
-
#
|
38
|
+
# gooddata datasets:describe --file-csv <path> --name <name> --output <output path>
|
34
39
|
#
|
35
|
-
# *
|
36
|
-
# *
|
37
|
-
# *
|
40
|
+
# * `--file-csv` - path to the CSV file (required)
|
41
|
+
# * `--name` - name of the data set (user will be prompted unless provided)
|
42
|
+
# * `--output` - name of the output JSON file with the model description (user will be prompted unless provided)
|
38
43
|
#
|
39
44
|
def describe
|
40
|
-
columns
|
41
|
-
name
|
42
|
-
output = extract_option('--output') || ask(
|
45
|
+
columns = ask_for_fields
|
46
|
+
name = extract_option('--name') || ask('Enter the dataset name')
|
47
|
+
output = extract_option('--output') || ask('Enter path to the file where to save the model description', :default => "#{name}.json")
|
43
48
|
open output, 'w' do |f|
|
44
|
-
f << JSON.pretty_generate(
|
49
|
+
f << JSON.pretty_generate(:title => name, :columns => columns) + "\n"
|
45
50
|
f.flush
|
46
51
|
end
|
47
52
|
end
|
@@ -49,15 +54,16 @@ module GoodData
|
|
49
54
|
# Creates a server-side model based on local model description. The model description
|
50
55
|
# is read from a JSON file that can be generated using the +datasets:describe+ command
|
51
56
|
#
|
52
|
-
#
|
57
|
+
# ## Usage
|
53
58
|
#
|
54
|
-
#
|
59
|
+
# gooddata datasets:apply --project <projectid> <data set config>
|
55
60
|
#
|
56
|
-
# *
|
57
|
-
# *
|
61
|
+
# * `--project`- GoodData project identifier
|
62
|
+
# * `data set config` - JSON file with the model description (possibly generated by the <tt>datasets:describe</tt> command)
|
58
63
|
#
|
59
64
|
def apply
|
60
|
-
connect
|
65
|
+
# TODO: Review following connect replacement/reimplementation
|
66
|
+
# connect
|
61
67
|
with_project do |project_id|
|
62
68
|
cfg_file = args.shift rescue nil
|
63
69
|
raise(CommandFailed, "Usage: #{$0} <dataset config>") unless cfg_file
|
@@ -67,18 +73,19 @@ module GoodData
|
|
67
73
|
end
|
68
74
|
end
|
69
75
|
|
70
|
-
#
|
76
|
+
# Loads a CSV file into an existing server-side data set
|
71
77
|
#
|
72
|
-
#
|
78
|
+
# ## Usage
|
73
79
|
#
|
74
|
-
#
|
80
|
+
# gooddata datasets:load --project <projectid> <file> <dataset config><
|
75
81
|
#
|
76
|
-
# *
|
77
|
-
# *
|
78
|
-
# *
|
82
|
+
# * `--project` - GoodData project identifier
|
83
|
+
# * `file` - CSV file to load
|
84
|
+
# * `data set config` - JSON file with the model description (possibly generated by the <tt>datasets:describe</tt> command)
|
79
85
|
#
|
80
86
|
def load
|
81
|
-
connect
|
87
|
+
# TODO: Review following connect replacement/reimplementation
|
88
|
+
# connect
|
82
89
|
with_project do |project_id|
|
83
90
|
file, cfg_file = args
|
84
91
|
raise(CommandFailed, "Usage: #{$0} datasets:load <file> <dataset config>") unless cfg_file
|
@@ -93,13 +100,13 @@ module GoodData
|
|
93
100
|
def with_project
|
94
101
|
unless @project_id
|
95
102
|
@project_id = extract_option('--project')
|
96
|
-
raise CommandFailed.new(
|
103
|
+
raise CommandFailed.new('Project not specified, use the --project switch') unless @project_id
|
97
104
|
end
|
98
105
|
yield @project_id
|
99
106
|
end
|
100
107
|
|
101
108
|
def ask_for_fields
|
102
|
-
guesser = Guesser.new create_dataset.read
|
109
|
+
guesser = GoodData::Data::Guesser.new create_dataset.read
|
103
110
|
guess = guesser.guess(1000)
|
104
111
|
model = []
|
105
112
|
connection_point_set = false
|
@@ -107,7 +114,7 @@ module GoodData
|
|
107
114
|
guesser.headers.each_with_index do |header, i|
|
108
115
|
options = guess[header].map { |t| t.to_s }
|
109
116
|
options = options.select { |t| t != :connection_point.to_s } if connection_point_set
|
110
|
-
type = ask question_fmt % [
|
117
|
+
type = ask question_fmt % [i + 1, header], :answers => options
|
111
118
|
model.push :title => header, :name => header, :type => type.upcase
|
112
119
|
connection_point_set = true if type == :connection_point.to_s
|
113
120
|
end
|
@@ -117,111 +124,7 @@ module GoodData
|
|
117
124
|
def create_dataset
|
118
125
|
file = extract_option('--file-csv')
|
119
126
|
return Extract::CsvFile.new(file) if file
|
120
|
-
raise CommandFailed.new(
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
##
|
125
|
-
# Utility class to guess data types of a data stream by looking at first couple of rows
|
126
|
-
#
|
127
|
-
class Guesser
|
128
|
-
|
129
|
-
TYPES_PRIORITY = [ :connection_point, :fact, :date, :attribute ]
|
130
|
-
attr_reader :headers
|
131
|
-
|
132
|
-
class << self
|
133
|
-
def sort_types(types)
|
134
|
-
types.sort do |x, y|
|
135
|
-
TYPES_PRIORITY.index(x) <=> TYPES_PRIORITY.index(y)
|
136
|
-
end
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
def initialize(reader)
|
141
|
-
@reader = reader
|
142
|
-
@headers = reader.shift.map! { |h| h.to_s } or raise "Empty data set"
|
143
|
-
@pros = {}; @cons = {}; @seen = {}
|
144
|
-
@headers.map do |h|
|
145
|
-
@cons[h.to_s] = {}
|
146
|
-
@pros[h.to_s] = {}
|
147
|
-
@seen[h.to_s] = {}
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
|
-
def guess(limit)
|
152
|
-
count = 0
|
153
|
-
while row = @reader.shift
|
154
|
-
break unless row && !row.empty? && count < limit
|
155
|
-
raise "%i fields in row %i, %i expected" % [ row.size, count + 1, @headers.size ] if row.size != @headers.size
|
156
|
-
row.each_with_index do |value, j|
|
157
|
-
header = @headers[j]
|
158
|
-
number = check_number(header, value)
|
159
|
-
date = check_date(header, value)
|
160
|
-
store_guess header, { @pros => :attribute } unless number || date
|
161
|
-
hash_increment @seen[header], value
|
162
|
-
end
|
163
|
-
count += 1
|
164
|
-
end
|
165
|
-
# fields with unique values are connection point candidates
|
166
|
-
@seen.each do |header, values|
|
167
|
-
store_guess header, { @pros => :connection_point } if values.size == count
|
168
|
-
end
|
169
|
-
guess_result
|
170
|
-
end
|
171
|
-
|
172
|
-
private
|
173
|
-
|
174
|
-
def guess_result
|
175
|
-
result = {}
|
176
|
-
@headers.each do |header|
|
177
|
-
result[header] = Guesser::sort_types @pros[header].keys.select { |type| @cons[header][type].nil? }
|
178
|
-
end
|
179
|
-
result
|
180
|
-
end
|
181
|
-
|
182
|
-
def hash_increment(hash, key)
|
183
|
-
if hash[key]
|
184
|
-
hash[key] += 1
|
185
|
-
else
|
186
|
-
hash[key] = 1
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
|
-
def check_number(header, value)
|
191
|
-
if value.nil? || value =~ /^[\+-]?\d*(\.\d*)?$/
|
192
|
-
return store_guess(header, @pros => [ :fact, :attribute ] )
|
193
|
-
end
|
194
|
-
store_guess header, { @cons => :fact }
|
195
|
-
end
|
196
|
-
|
197
|
-
def check_date(header, value)
|
198
|
-
return store_guess(header, @pros => [ :date, :attribute, :fact ]) if value.nil? || value == '0000-00-00'
|
199
|
-
begin
|
200
|
-
DateTime.parse value
|
201
|
-
return store_guess(header, @pros => [ :date, :attribute ])
|
202
|
-
rescue ArgumentError; end
|
203
|
-
store_guess header, { @cons => :date }
|
204
|
-
end
|
205
|
-
|
206
|
-
##
|
207
|
-
# Stores a guess about given header.
|
208
|
-
#
|
209
|
-
# Returns true if the @pros key is present, false otherwise
|
210
|
-
#
|
211
|
-
# === Parameters
|
212
|
-
#
|
213
|
-
# * +header+ - A header name
|
214
|
-
# * +guess+ - A hash with optional @pros and @cons keys
|
215
|
-
#
|
216
|
-
def store_guess(header, guess)
|
217
|
-
result = !guess[@pros].nil?
|
218
|
-
[@pros, @cons].each do |hash|
|
219
|
-
if guess[hash] then
|
220
|
-
guess[hash] = [ guess[hash] ] unless guess[hash].is_a? Array
|
221
|
-
guess[hash].each { |type| hash_increment hash[header], type }
|
222
|
-
end
|
223
|
-
end
|
224
|
-
result
|
127
|
+
raise CommandFailed.new('Unknown data set. Please specify a data set using --file-csv option (more supported data sources to come!)')
|
225
128
|
end
|
226
129
|
end
|
227
130
|
end
|