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,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
|