gooddata 0.6.0.pre9 → 0.6.0.pre10
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/.rspec +3 -0
- data/.yardopts +19 -0
- data/Gemfile +5 -0
- data/README.md +191 -0
- data/Rakefile +18 -1
- data/bin/gooddata +74 -63
- data/doc/.gitignore +1 -0
- data/doc/css/.gitkeepme +1 -0
- data/doc/images/.gitkeepme +1 -0
- data/doc/images/background.png +0 -0
- data/doc/images/bg-callout-button.png +0 -0
- data/doc/images/header-logo.png +0 -0
- data/doc/images/logo-image.png +0 -0
- data/doc/js/.gitkeepme +1 -0
- data/doc/pages/GET_STARTED.md +309 -0
- data/doc/pages/HOMEPAGE.md +75 -0
- data/doc/pages/TUTORIALS.md +52 -0
- data/doc/pages/tutorial/BRICKS.md +257 -0
- data/doc/pages/tutorial/CREATING_A_MODEL.md +79 -0
- data/doc/pages/tutorial/CRUNCHING_NUMBERS.md +233 -0
- data/doc/pages/tutorial/TEST_DRIVEN_DEVELOPMENT.md +118 -0
- data/doc/pages/tutorial/YOUR_FIRST_PROJECT.md +52 -0
- data/doc/templates/default/class/dot/setup.rb +6 -0
- data/doc/templates/default/class/dot/superklass.erb +3 -0
- data/doc/templates/default/class/setup.rb +36 -0
- data/doc/templates/default/class/text/setup.rb +11 -0
- data/doc/templates/default/class/text/subclasses.erb +5 -0
- data/doc/templates/default/constant/text/header.erb +11 -0
- data/doc/templates/default/constant/text/setup.rb +3 -0
- data/doc/templates/default/docstring/setup.rb +51 -0
- data/doc/templates/default/docstring/text/abstract.erb +2 -0
- data/doc/templates/default/docstring/text/deprecated.erb +2 -0
- data/doc/templates/default/docstring/text/index.erb +2 -0
- data/doc/templates/default/docstring/text/note.erb +4 -0
- data/doc/templates/default/docstring/text/private.erb +2 -0
- data/doc/templates/default/docstring/text/returns_void.erb +1 -0
- data/doc/templates/default/docstring/text/text.erb +1 -0
- data/doc/templates/default/docstring/text/todo.erb +4 -0
- data/doc/templates/default/layout/dot/header.erb +6 -0
- data/doc/templates/default/layout/dot/setup.rb +14 -0
- data/doc/templates/default/method/setup.rb +3 -0
- data/doc/templates/default/method/text/header.erb +1 -0
- data/doc/templates/default/method_details/setup.rb +10 -0
- data/doc/templates/default/method_details/text/header.erb +10 -0
- data/doc/templates/default/method_details/text/method_signature.erb +12 -0
- data/doc/templates/default/method_details/text/setup.rb +10 -0
- data/doc/templates/default/module/dot/child.erb +1 -0
- data/doc/templates/default/module/dot/dependencies.erb +3 -0
- data/doc/templates/default/module/dot/header.erb +6 -0
- data/doc/templates/default/module/dot/info.erb +14 -0
- data/doc/templates/default/module/dot/setup.rb +14 -0
- data/doc/templates/default/module/setup.rb +164 -0
- data/doc/templates/default/module/text/children.erb +10 -0
- data/doc/templates/default/module/text/class_meths_list.erb +8 -0
- data/doc/templates/default/module/text/extends.erb +8 -0
- data/doc/templates/default/module/text/header.erb +7 -0
- data/doc/templates/default/module/text/includes.erb +8 -0
- data/doc/templates/default/module/text/instance_meths_list.erb +8 -0
- data/doc/templates/default/module/text/setup.rb +12 -0
- data/doc/templates/default/root/dot/child.erb +3 -0
- data/doc/templates/default/root/dot/setup.rb +5 -0
- data/doc/templates/default/tags/setup.rb +55 -0
- data/doc/templates/default/tags/text/example.erb +12 -0
- data/doc/templates/default/tags/text/index.erb +1 -0
- data/doc/templates/default/tags/text/option.erb +20 -0
- data/doc/templates/default/tags/text/overload.erb +19 -0
- data/doc/templates/default/tags/text/see.erb +11 -0
- data/doc/templates/default/tags/text/tag.erb +13 -0
- data/examples.rb +2 -2
- data/gooddata.gemspec +31 -26
- data/lib/gooddata/bricks/middleware/gooddata_middleware.rb +1 -1
- data/lib/gooddata/client.rb +65 -53
- data/lib/gooddata/commands/commands.rb +9 -0
- data/lib/gooddata/commands/process.rb +9 -8
- data/lib/gooddata/commands/projects.rb +29 -0
- data/lib/gooddata/commands/runners.rb +1 -1
- data/lib/gooddata/connection.rb +6 -4
- data/lib/gooddata/exceptions.rb +2 -1
- data/lib/gooddata/helpers.rb +1 -1
- data/lib/gooddata/model.rb +360 -189
- data/lib/gooddata/models/metadata.rb +1 -1
- data/lib/gooddata/models/metric.rb +2 -1
- data/lib/gooddata/models/project.rb +1 -1
- data/lib/gooddata/models/report.rb +0 -18
- data/lib/gooddata/version.rb +1 -1
- data/spec/blueprint_spec.rb +83 -43
- data/spec/data/additional_dataset_module.json +18 -0
- data/spec/data/blueprint_invalid.json +36 -0
- data/spec/data/blueprint_valid.json +37 -0
- data/spec/data/model_module.json +18 -0
- data/spec/{test_project_model_spec.json → data/test_project_model_spec.json} +4 -0
- data/spec/full_project_spec.rb +4 -3
- data/spec/helpers/blueprint_helper.rb +17 -0
- data/spec/merging_blueprints_spec.rb +23 -48
- data/spec/model_dsl_spec.rb +2 -2
- data/spec/model_spec.rb +44 -0
- data/spec/project_build_and_update_spec.rb +28 -0
- data/spec/spec_helper.rb +6 -0
- data/yard-server.sh +3 -0
- metadata +251 -74
- data/README.rdoc +0 -176
@@ -7,7 +7,7 @@ module GoodData::Bricks
|
|
7
7
|
logger = params[:gdc_logger]
|
8
8
|
token_name = :GDC_SST
|
9
9
|
protocol_name = :GDC_PROTOCOL
|
10
|
-
server_name = :
|
10
|
+
server_name = :GDC_HOSTNAME
|
11
11
|
project_id = params[:GDC_PROJECT_ID]
|
12
12
|
|
13
13
|
fail "SST (SuperSecureToken) not present in params" if params[token_name].nil?
|
data/lib/gooddata/client.rb
CHANGED
@@ -10,39 +10,47 @@ else
|
|
10
10
|
FasterCSV = CSV
|
11
11
|
end
|
12
12
|
|
13
|
-
#
|
14
|
-
|
13
|
+
# Initializes required dynamically loaded classes
|
14
|
+
def init_gd_module()
|
15
|
+
# Metadata packages, such as report.rb, require this to be loaded first
|
16
|
+
require File.dirname(__FILE__) + '/models/metadata.rb'
|
15
17
|
|
16
|
-
|
17
|
-
Dir[File.dirname(__FILE__) + '/
|
18
|
+
# Load models from models folder
|
19
|
+
Dir[File.dirname(__FILE__) + '/models/*.rb'].each { |file| require file }
|
18
20
|
|
19
|
-
#
|
21
|
+
# Load collections
|
22
|
+
Dir[File.dirname(__FILE__) + '/collections/*.rb'].each { |file| require file }
|
23
|
+
end
|
24
|
+
|
25
|
+
init_gd_module()
|
26
|
+
|
27
|
+
# # GoodData API wrapper
|
20
28
|
#
|
21
29
|
# A convenient Ruby wrapper around the GoodData RESTful API.
|
22
30
|
#
|
23
31
|
# The best documentation for the API can be found using these resources:
|
32
|
+
#
|
24
33
|
# * http://developer.gooddata.com/api
|
25
34
|
# * https://secure.gooddata.com/gdc
|
26
35
|
#
|
27
|
-
#
|
36
|
+
# ## Usage
|
28
37
|
#
|
29
38
|
# To communicate with the API you first need a personal GoodData account.
|
30
|
-
#
|
39
|
+
# [Sign up here](https://secure.gooddata.com/registration.html) if you havent already.
|
31
40
|
#
|
32
|
-
# Now it is just a matter of initializing the GoodData connection via the connect
|
33
|
-
# method:
|
41
|
+
# Now it is just a matter of initializing the GoodData connection via the connect method:
|
34
42
|
#
|
35
|
-
#
|
43
|
+
# GoodData.connect 'gooddata_user', 'gooddata_password'
|
36
44
|
#
|
37
45
|
# This GoodData object can now be utalized to retrieve your GoodData profile, the available
|
38
46
|
# projects etc.
|
39
47
|
#
|
40
|
-
#
|
48
|
+
# ## Logging
|
41
49
|
#
|
42
|
-
#
|
50
|
+
# GoodData.logger = Logger.new(STDOUT)
|
43
51
|
#
|
44
52
|
# For details about the logger options and methods, see the
|
45
|
-
#
|
53
|
+
# (Logger module documentation)[http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc].
|
46
54
|
#
|
47
55
|
module GoodData
|
48
56
|
module Threaded
|
@@ -52,6 +60,7 @@ module GoodData
|
|
52
60
|
end
|
53
61
|
end
|
54
62
|
|
63
|
+
# Dummy implementation of logger
|
55
64
|
class NilLogger
|
56
65
|
def debug(*args) ; end
|
57
66
|
alias :info :debug
|
@@ -59,6 +68,7 @@ module GoodData
|
|
59
68
|
alias :error :debug
|
60
69
|
end
|
61
70
|
|
71
|
+
# Assigns global/default GoodData project
|
62
72
|
def project=(project)
|
63
73
|
GoodData.project = project
|
64
74
|
GoodData.project
|
@@ -70,21 +80,24 @@ module GoodData
|
|
70
80
|
|
71
81
|
RELEASE_INFO_PATH = '/gdc/releaseInfo'
|
72
82
|
|
83
|
+
# Version
|
73
84
|
def version
|
74
85
|
VERSION
|
75
86
|
end
|
76
87
|
|
77
|
-
|
88
|
+
# Identifier of gem version
|
89
|
+
# @return Formatted gem version
|
90
|
+
def gem_version_string()
|
78
91
|
"gooddata-gem/#{version}"
|
79
92
|
end
|
80
93
|
|
81
94
|
# Connect to the GoodData API
|
82
95
|
#
|
83
|
-
#
|
84
|
-
#
|
85
|
-
#
|
86
|
-
# * +password+ - A GoodData password
|
96
|
+
# @param options
|
97
|
+
# @param second_options
|
98
|
+
# @param third_options
|
87
99
|
#
|
100
|
+
# Goodd
|
88
101
|
def connect(options=nil, second_options=nil, third_options={})
|
89
102
|
if options.is_a? Hash
|
90
103
|
fail "You have to provide login and password" if ((options[:login].nil? || options[:login].empty?) && (options[:password].nil? || options[:password].empty?))
|
@@ -94,36 +107,34 @@ module GoodData
|
|
94
107
|
fail "You have to provide login and password" if ((options.nil? || options.empty?) && (second_options.nil? || second_options.empty?))
|
95
108
|
threaded[:connection] = Connection.new(options, second_options, third_options)
|
96
109
|
end
|
97
|
-
|
98
110
|
end
|
99
111
|
|
112
|
+
# Turn logging on
|
100
113
|
def logging_on
|
101
114
|
if logger.is_a? NilLogger
|
102
115
|
GoodData::logger = Logger.new(STDOUT)
|
103
116
|
end
|
104
117
|
end
|
105
118
|
|
119
|
+
# Turn logging off
|
106
120
|
def logging_off
|
107
121
|
GoodData::logger = NilLogger.new
|
108
122
|
end
|
109
123
|
|
110
124
|
|
111
125
|
# Hepler for starting with SST easier
|
112
|
-
# === Parameters
|
113
126
|
#
|
114
|
-
#
|
115
|
-
#
|
127
|
+
# @param token SST token
|
128
|
+
# @param options Options get routed to connect eventually so everything that you can use there should be possible to use here.
|
116
129
|
#
|
117
130
|
def connect_with_sst(token, options={})
|
118
131
|
create_authenticated_connection(options.merge({:cookies => {"GDCAuthSST" => token}}))
|
119
132
|
end
|
120
133
|
|
121
134
|
# This method is aimed at creating an authenticated connection in case you do not hae pass/login but you have SST
|
122
|
-
# === Parameters
|
123
135
|
#
|
124
|
-
#
|
125
|
-
#
|
126
|
-
#
|
136
|
+
# @param options :server => optional GD server uri, If nil it secure will be used. :cookies => you can specify a hash of cookies
|
137
|
+
#
|
127
138
|
def create_authenticated_connection(options={})
|
128
139
|
connect(options)
|
129
140
|
server_cookies = options[:cookies]
|
@@ -132,6 +143,10 @@ module GoodData
|
|
132
143
|
connection
|
133
144
|
end
|
134
145
|
|
146
|
+
# Perform block in context of another project than currently set
|
147
|
+
#
|
148
|
+
# @param project Project to use
|
149
|
+
# @param bl Block to be performed
|
135
150
|
def with_project(project, &bl)
|
136
151
|
fail "You have to specify a project when using with_project" if project.nil? || (project.is_a?(String) && project.empty?)
|
137
152
|
old_project = GoodData.project
|
@@ -145,24 +160,21 @@ module GoodData
|
|
145
160
|
end
|
146
161
|
end
|
147
162
|
|
148
|
-
# Returns the active GoodData connection earlier initialized via
|
149
|
-
# GoodData.connect call
|
163
|
+
# Returns the active GoodData connection earlier initialized via GoodData.connect call
|
150
164
|
#
|
151
165
|
# @see GoodData.connect
|
152
|
-
#
|
153
166
|
def connection
|
154
167
|
threaded[:connection] || raise("Please authenticate with GoodData.connect first")
|
155
168
|
end
|
156
169
|
|
157
170
|
# Sets the active project
|
158
171
|
#
|
159
|
-
#
|
172
|
+
# @param project A project identifier
|
160
173
|
#
|
161
|
-
#
|
174
|
+
# ### Examples
|
162
175
|
#
|
163
|
-
#
|
176
|
+
# The following calls are equivalent
|
164
177
|
#
|
165
|
-
# The following calls are equivalent:
|
166
178
|
# * GoodData.project = 'afawtv356b6usdfsdf34vt'
|
167
179
|
# * GoodData.use 'afawtv356b6usdfsdf34vt'
|
168
180
|
# * GoodData.use '/gdc/projects/afawtv356b6usdfsdf34vt'
|
@@ -190,12 +202,12 @@ module GoodData
|
|
190
202
|
#
|
191
203
|
# Retuns the JSON response formatted as a Hash object.
|
192
204
|
#
|
193
|
-
#
|
205
|
+
# @param path The HTTP path on the GoodData server (must be prefixed with a forward slash)
|
206
|
+
#
|
207
|
+
# ### Examples
|
194
208
|
#
|
195
|
-
#
|
196
|
-
# === Examples
|
209
|
+
# GoodData.get '/gdc/projects'
|
197
210
|
#
|
198
|
-
# GoodData.get '/gdc/projects'
|
199
211
|
def get(path, options = {})
|
200
212
|
connection.get(path, options)
|
201
213
|
end
|
@@ -204,14 +216,13 @@ module GoodData
|
|
204
216
|
#
|
205
217
|
# Retuns the JSON response formatted as a Hash object.
|
206
218
|
#
|
207
|
-
#
|
208
|
-
#
|
209
|
-
# * +path+ - The HTTP path on the GoodData server (must be prefixed with a forward slash)
|
210
|
-
# * +data+ - The payload data in the format of a Hash object
|
219
|
+
# @param path The HTTP path on the GoodData server (must be prefixed with a forward slash)
|
220
|
+
# @param data The payload data in the format of a Hash object
|
211
221
|
#
|
212
|
-
#
|
222
|
+
# ### Examples
|
213
223
|
#
|
214
224
|
# GoodData.post '/gdc/projects', { ... }
|
225
|
+
#
|
215
226
|
def post(path, data, options = {})
|
216
227
|
connection.post path, data, options
|
217
228
|
end
|
@@ -220,14 +231,15 @@ module GoodData
|
|
220
231
|
#
|
221
232
|
# Retuns the JSON response formatted as a Hash object.
|
222
233
|
#
|
223
|
-
#
|
234
|
+
# ### Parameters
|
224
235
|
#
|
225
|
-
#
|
226
|
-
#
|
236
|
+
# @param path The HTTP path on the GoodData server (must be prefixed with a forward slash)
|
237
|
+
# @param data The payload data in the format of a Hash object
|
227
238
|
#
|
228
|
-
#
|
239
|
+
# ### Examples
|
229
240
|
#
|
230
241
|
# GoodData.put '/gdc/projects', { ... }
|
242
|
+
#
|
231
243
|
def put(path, data, options = {})
|
232
244
|
connection.put path, data, options
|
233
245
|
end
|
@@ -236,13 +248,12 @@ module GoodData
|
|
236
248
|
#
|
237
249
|
# Retuns the JSON response formatted as a Hash object.
|
238
250
|
#
|
239
|
-
#
|
251
|
+
# @param path The HTTP path on the GoodData server (must be prefixed with a forward slash)
|
240
252
|
#
|
241
|
-
#
|
253
|
+
# ### Examples
|
242
254
|
#
|
243
|
-
#
|
255
|
+
# GoodData.delete '/gdc/project/1'
|
244
256
|
#
|
245
|
-
# GoodData.delete '/gdc/project/1'
|
246
257
|
def delete(path, options = {})
|
247
258
|
connection.delete path, options
|
248
259
|
end
|
@@ -306,10 +317,11 @@ module GoodData
|
|
306
317
|
# For some serious logging, set the logger instance using
|
307
318
|
# the logger= method
|
308
319
|
#
|
309
|
-
#
|
320
|
+
# ### Example
|
321
|
+
#
|
322
|
+
# require 'logger'
|
323
|
+
# GoodData.logger = Logger.new(STDOUT)
|
310
324
|
#
|
311
|
-
# require 'logger'
|
312
|
-
# GoodData.logger = Logger.new(STDOUT)
|
313
325
|
def logger
|
314
326
|
@logger ||= NilLogger.new
|
315
327
|
end
|
@@ -19,23 +19,25 @@ module GoodData::Command
|
|
19
19
|
def self.deploy(dir, options={})
|
20
20
|
verbose = options[:verbose] || false
|
21
21
|
GoodData.with_project(options[:project_id]) do
|
22
|
-
|
22
|
+
params = options[:params].nil? ? [] : [options[:params]]
|
23
|
+
deploy_graph(dir, options.merge({:files_to_exclude => params}))
|
23
24
|
end
|
24
25
|
end
|
25
26
|
|
26
27
|
def self.with_deploy(dir, options={}, &block)
|
27
28
|
verbose = options[:verbose] || false
|
28
29
|
GoodData.with_project(options[:project_id]) do
|
30
|
+
params = options[:params].nil? ? [] : [options[:params]]
|
29
31
|
if block
|
30
32
|
begin
|
31
|
-
res = deploy_graph(dir, options.merge({:files_to_exclude =>
|
33
|
+
res = deploy_graph(dir, options.merge({:files_to_exclude => params}))
|
32
34
|
block.call(res)
|
33
35
|
ensure
|
34
|
-
|
35
|
-
|
36
|
+
self_link = res && res["process"]["links"]["self"]
|
37
|
+
GoodData.delete(self_link)
|
36
38
|
end
|
37
39
|
else
|
38
|
-
deploy_graph(dir, options.merge({:files_to_exclude =>
|
40
|
+
deploy_graph(dir, options.merge({:files_to_exclude => params}))
|
39
41
|
end
|
40
42
|
end
|
41
43
|
end
|
@@ -100,8 +102,7 @@ module GoodData::Command
|
|
100
102
|
fail "\"#{dir}\" is not a directory" unless dir.directory?
|
101
103
|
files_to_exclude = options[:files_to_exclude].map {|p| Pathname(p)}
|
102
104
|
|
103
|
-
|
104
|
-
|
105
|
+
project_id = options[:project_id] || fail("Project Id has to be specified")
|
105
106
|
|
106
107
|
type = options[:type] || "GRAPH"
|
107
108
|
deploy_name = options[:name]
|
@@ -141,7 +142,7 @@ module GoodData::Command
|
|
141
142
|
GoodData.put("/gdc/projects/#{GoodData.project.pid}/dataload/processes/#{process_id}", data)
|
142
143
|
end
|
143
144
|
end
|
144
|
-
puts HighLine::color("Deploy DONE #{dir}", HighLine::
|
145
|
+
puts HighLine::color("Deploy DONE #{dir}", HighLine::GREEN) if verbose
|
145
146
|
res
|
146
147
|
end
|
147
148
|
|
@@ -66,6 +66,35 @@ module GoodData::Command
|
|
66
66
|
p.delete
|
67
67
|
end
|
68
68
|
|
69
|
+
def self.get_spec_and_project_id(base_path)
|
70
|
+
goodfile_path = GoodData::Helpers.find_goodfile(Pathname(base_path))
|
71
|
+
fail "Goodfile could not be located in any parent directory. Please make sure you are inside a gooddata project folder." if goodfile_path.nil?
|
72
|
+
goodfile = JSON.parse(File.read(goodfile_path), :symbolize_names => true)
|
73
|
+
spec_path = goodfile[:model] || fail("You need to specify the path of the build spec")
|
74
|
+
fail "Model path provided in Goodfile \"#{spec_path}\" does not exist" unless File.exist?(spec_path) && !File.directory?(spec_path)
|
75
|
+
|
76
|
+
spec_path = Pathname(spec_path)
|
77
|
+
|
78
|
+
content = File.read(spec_path)
|
79
|
+
spec = if (spec_path.extname == ".rb")
|
80
|
+
eval(content)
|
81
|
+
elsif (spec_path.extname == ".json")
|
82
|
+
JSON.parse(spec_path, :symbolize_names => true)
|
83
|
+
end
|
84
|
+
[spec, goodfile[:project_id]]
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.update(options={})
|
88
|
+
project = options[:project]
|
89
|
+
project_id = project && project.pid
|
90
|
+
fail "You have to provide 'project_id'. You can either provide it through -p flag or even better way is to fill it in in your Goodfile under key \"project_id\". If you just started a project you have to create it first. One way might be through \"gooddata project build\"" if project_id.nil? || project_id.empty?
|
91
|
+
GoodData::Model::ProjectCreator.migrate(:spec => options[:spec], :project => project_id)
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.build(options={})
|
95
|
+
GoodData::Model::ProjectCreator.migrate(:spec => options[:spec], :token => options[:token])
|
96
|
+
end
|
97
|
+
|
69
98
|
end
|
70
99
|
end
|
71
100
|
|
@@ -29,7 +29,7 @@ script_body = <<-script_body
|
|
29
29
|
:GDC_SST => \"#{sst}\",
|
30
30
|
:GDC_PROJECT_ID => \"#{pid}\",
|
31
31
|
:GDC_PROTOCOL => \"#{scheme}\",
|
32
|
-
:
|
32
|
+
:GDC_HOSTNAME => \"#{hostname}\",
|
33
33
|
:GDC_LOGGER_FILE => STDOUT
|
34
34
|
}.merge(#{params})
|
35
35
|
eval(File.read(\"./main.rb\"))
|
data/lib/gooddata/connection.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
require 'json'
|
2
|
-
|
3
|
-
# silence the parenthesis warning in rest-client 1.6.1
|
4
|
-
old_verbose, $VERBOSE = $VERBOSE, nil ; require 'rest-client' ; $VERBOSE = old_verbose
|
2
|
+
require 'rest-client'
|
5
3
|
|
6
4
|
module GoodData
|
7
5
|
|
@@ -368,7 +366,11 @@ module GoodData
|
|
368
366
|
new_params = Marshal.load(Marshal.dump(params))
|
369
367
|
GoodData::Helpers.hash_dfs(new_params) do |k, key|
|
370
368
|
keys.each do |key_to_scrub|
|
371
|
-
|
369
|
+
begin
|
370
|
+
k[key_to_scrub] = ("*" * k[key_to_scrub].length) if k && k.has_key?(key_to_scrub) && k[key_to_scrub]
|
371
|
+
rescue
|
372
|
+
binding.pry
|
373
|
+
end
|
372
374
|
end
|
373
375
|
end
|
374
376
|
new_params
|
data/lib/gooddata/exceptions.rb
CHANGED
data/lib/gooddata/helpers.rb
CHANGED
data/lib/gooddata/model.rb
CHANGED
@@ -34,8 +34,8 @@ module GoodData
|
|
34
34
|
|
35
35
|
class << self
|
36
36
|
def add_dataset(name, columns, project = nil)
|
37
|
-
|
38
|
-
add_schema(schema
|
37
|
+
Schema.new('columns' => columns, 'name' => name)
|
38
|
+
add_schema(schema, project)
|
39
39
|
end
|
40
40
|
|
41
41
|
def add_schema(schema, project = nil)
|
@@ -46,7 +46,7 @@ module GoodData
|
|
46
46
|
project = GoodData.project unless project
|
47
47
|
ldm_links = GoodData.get project.md[LDM_CTG]
|
48
48
|
ldm_uri = Links.new(ldm_links)[LDM_MANAGE_CTG]
|
49
|
-
GoodData.post ldm_uri, {
|
49
|
+
GoodData.post ldm_uri, {'manage' => {'maql' => schema.to_maql_create}}
|
50
50
|
end
|
51
51
|
|
52
52
|
# Load given file into a data set described by the given schema
|
@@ -80,7 +80,7 @@ module GoodData
|
|
80
80
|
end
|
81
81
|
|
82
82
|
# kick the load
|
83
|
-
pull = {
|
83
|
+
pull = {'pullIntegration' => File.basename(dir)}
|
84
84
|
link = project.md.links('etl')['pull']
|
85
85
|
task = GoodData.post link, pull
|
86
86
|
while (GoodData.get(task["pullTask"]["uri"])["taskStatus"] === "RUNNING" || GoodData.get(task["pullTask"]["uri"])["taskStatus"] === "PREPARED") do
|
@@ -92,16 +92,17 @@ module GoodData
|
|
92
92
|
js = JSON.parse(s.string)
|
93
93
|
fail "Load Failed with error #{JSON.pretty_generate(js)}"
|
94
94
|
end
|
95
|
-
puts "Done loading"
|
96
95
|
end
|
97
96
|
|
98
97
|
def merge_dataset_columns(a_schema_blueprint, b_schema_blueprint)
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
98
|
+
a_schema_blueprint = a_schema_blueprint.to_hash
|
99
|
+
b_schema_blueprint = b_schema_blueprint.to_hash
|
100
|
+
d = Marshal.load(Marshal.dump(a_schema_blueprint))
|
101
|
+
d[:columns] = d[:columns] + b_schema_blueprint[:columns]
|
102
|
+
d[:columns].uniq!
|
103
|
+
columns_that_failed_to_merge = d[:columns].group_by {|x| x[:name]}.map {|k, v| [k, v.count]}.find_all {|x| x[1] > 1}
|
104
|
+
fail "Columns #{columns_that_failed_to_merge} failed to merge. When merging columns with the same name they have to be identical." unless columns_that_failed_to_merge.empty?
|
105
|
+
d
|
105
106
|
end
|
106
107
|
|
107
108
|
end
|
@@ -110,6 +111,14 @@ module GoodData
|
|
110
111
|
|
111
112
|
attr_accessor :data
|
112
113
|
|
114
|
+
def self.from_json(spec)
|
115
|
+
if spec.is_a?(String)
|
116
|
+
ProjectBlueprint.new(JSON.parse(File.read(spec), :symbolize_names => true))
|
117
|
+
else
|
118
|
+
ProjectBlueprint.new(spec)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
113
122
|
def change(&block)
|
114
123
|
builder = ProjectBuilder.create_from_data(self)
|
115
124
|
block.call(builder)
|
@@ -119,11 +128,29 @@ module GoodData
|
|
119
128
|
end
|
120
129
|
|
121
130
|
def datasets
|
122
|
-
data[:datasets].map {|d| SchemaBlueprint.new(d)}
|
131
|
+
data[:datasets].map { |d| SchemaBlueprint.new(d) }
|
132
|
+
end
|
133
|
+
|
134
|
+
def add_dataset(a_dataset, index=nil)
|
135
|
+
if index.nil? || index > datasets.length
|
136
|
+
data[:datasets] << a_dataset.to_hash
|
137
|
+
else
|
138
|
+
data[:datasets].insert(index, a_dataset.to_hash)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def remove_dataset(dataset_name)
|
143
|
+
x = data[:datasets].find {|d| d[:name] == dataset_name}
|
144
|
+
index = data[:datasets].index(x)
|
145
|
+
data[:datasets].delete_at(index)
|
146
|
+
end
|
147
|
+
|
148
|
+
def date_dimensions
|
149
|
+
data[:date_dimensions]
|
123
150
|
end
|
124
151
|
|
125
152
|
def get_dataset(name)
|
126
|
-
ds = data[:datasets].find {|d| d[:name] == name}
|
153
|
+
ds = data[:datasets].find { |d| d[:name] == name }
|
127
154
|
SchemaBlueprint.new(ds) unless ds.nil?
|
128
155
|
end
|
129
156
|
|
@@ -135,7 +162,7 @@ module GoodData
|
|
135
162
|
if datasets.count == 1
|
136
163
|
[]
|
137
164
|
else
|
138
|
-
x = datasets.reduce([]) {|memo, schema| schema.has_anchor? ? memo << [schema.name, schema.anchor[:name]] : memo }
|
165
|
+
x = datasets.reduce([]) { |memo, schema| schema.has_anchor? ? memo << [schema.name, schema.anchor[:name]] : memo }
|
139
166
|
refs = datasets.reduce([]) do |memo, dataset|
|
140
167
|
memo.concat(dataset.references)
|
141
168
|
end
|
@@ -147,12 +174,55 @@ module GoodData
|
|
147
174
|
|
148
175
|
def model_valid?
|
149
176
|
errors = model_validate
|
150
|
-
errors.empty? ? true :false
|
177
|
+
errors.empty? ? true : false
|
178
|
+
end
|
179
|
+
|
180
|
+
def merge!(a_blueprint)
|
181
|
+
temp_blueprint = dup
|
182
|
+
a_blueprint.datasets.each do |dataset|
|
183
|
+
local_dataset = temp_blueprint.get_dataset(dataset.name)
|
184
|
+
if local_dataset.nil?
|
185
|
+
temp_blueprint.add_dataset(dataset.dup)
|
186
|
+
else
|
187
|
+
index = temp_blueprint.datasets.index(local_dataset)
|
188
|
+
local_dataset.merge!(dataset)
|
189
|
+
temp_blueprint.remove_dataset(local_dataset.name)
|
190
|
+
temp_blueprint.add_dataset(local_dataset, index)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
@data = temp_blueprint.data
|
194
|
+
self
|
195
|
+
end
|
196
|
+
|
197
|
+
def dup
|
198
|
+
deep_copy = Marshal.load(Marshal.dump(data))
|
199
|
+
ProjectBlueprint.new(deep_copy)
|
151
200
|
end
|
152
201
|
|
153
202
|
def title
|
154
203
|
data[:title]
|
155
204
|
end
|
205
|
+
|
206
|
+
def to_wire_model
|
207
|
+
{
|
208
|
+
"diffRequest" => {
|
209
|
+
"targetModel" => {
|
210
|
+
"projectModel" => {
|
211
|
+
"datasets" => datasets.map {|d| d.to_wire_model},
|
212
|
+
"dateDimensions" => date_dimensions.map {|d|
|
213
|
+
{
|
214
|
+
"dateDimension" => {
|
215
|
+
"name" => d[:name],
|
216
|
+
"title" => d[:title] || d[:name].humanize
|
217
|
+
}
|
218
|
+
}}
|
219
|
+
}}}}
|
220
|
+
end
|
221
|
+
|
222
|
+
def to_hash
|
223
|
+
@data
|
224
|
+
end
|
225
|
+
|
156
226
|
end
|
157
227
|
|
158
228
|
class SchemaBlueprint
|
@@ -173,10 +243,17 @@ module GoodData
|
|
173
243
|
|
174
244
|
def upload(source, options={})
|
175
245
|
project = options[:project] || GoodData.project
|
246
|
+
fail "You have to specify a project into which you want to load." if project.nil?
|
176
247
|
mode = options[:load] || "FULL"
|
177
248
|
project.upload(source, to_schema, mode)
|
178
249
|
end
|
179
250
|
|
251
|
+
def merge!(a_blueprint)
|
252
|
+
new_blueprint = GoodData::Model.merge_dataset_columns(self, a_blueprint)
|
253
|
+
@data = new_blueprint
|
254
|
+
self
|
255
|
+
end
|
256
|
+
|
180
257
|
def name
|
181
258
|
data[:name]
|
182
259
|
end
|
@@ -194,7 +271,7 @@ module GoodData
|
|
194
271
|
end
|
195
272
|
|
196
273
|
def has_anchor?
|
197
|
-
columns.any? {|c| c[:type] ==
|
274
|
+
columns.any? { |c| c[:type].to_s == "anchor" }
|
198
275
|
end
|
199
276
|
|
200
277
|
def anchor
|
@@ -205,19 +282,29 @@ module GoodData
|
|
205
282
|
find_column_by_type(:reference)
|
206
283
|
end
|
207
284
|
|
285
|
+
def attributes
|
286
|
+
find_column_by_type(:attribute)
|
287
|
+
end
|
288
|
+
|
289
|
+
def facts
|
290
|
+
find_column_by_type(:fact)
|
291
|
+
end
|
292
|
+
|
208
293
|
def find_column_by_type(type, all=:all)
|
294
|
+
type = type.to_s
|
209
295
|
if all == :all
|
210
|
-
columns.find_all {|c| c[:type] == type}
|
296
|
+
columns.find_all { |c| c[:type].to_s == type }
|
211
297
|
else
|
212
|
-
columns.find {|c| c[:type] == type}
|
298
|
+
columns.find { |c| c[:type].to_s == type }
|
213
299
|
end
|
214
300
|
end
|
215
301
|
|
216
302
|
def find_column_by_name(type, all=:all)
|
303
|
+
type = type.to_s
|
217
304
|
if all == :all
|
218
|
-
columns.find_all {|c| c[:name] == type}
|
305
|
+
columns.find_all { |c| c[:name].to_s == type }
|
219
306
|
else
|
220
|
-
columns.find {|c| c[:name] == type}
|
307
|
+
columns.find { |c| c[:name].to_s == type }
|
221
308
|
end
|
222
309
|
end
|
223
310
|
|
@@ -228,12 +315,12 @@ module GoodData
|
|
228
315
|
def to_manifest
|
229
316
|
to_schema.to_manifest
|
230
317
|
end
|
231
|
-
|
318
|
+
|
232
319
|
def pretty_print(printer)
|
233
|
-
printer.text "Schema <#{object_id}>:\n"
|
320
|
+
printer.text "Schema <#{object_id}>:\n"
|
234
321
|
printer.text " Name: #{name}\n"
|
235
322
|
printer.text " Columns: \n"
|
236
|
-
printer.text columns.map {|c| " #{c[:name]}: #{c[:type]}"}.join("\n")
|
323
|
+
printer.text columns.map { |c| " #{c[:name]}: #{c[:type]}" }.join("\n")
|
237
324
|
end
|
238
325
|
|
239
326
|
def dup
|
@@ -241,6 +328,14 @@ module GoodData
|
|
241
328
|
SchemaBlueprint.new(deep_copy)
|
242
329
|
end
|
243
330
|
|
331
|
+
def to_wire_model
|
332
|
+
to_schema.to_wire_model
|
333
|
+
end
|
334
|
+
|
335
|
+
def ==(other)
|
336
|
+
to_hash == other.to_hash
|
337
|
+
end
|
338
|
+
|
244
339
|
end
|
245
340
|
|
246
341
|
class ProjectBuilder
|
@@ -282,8 +377,8 @@ module GoodData
|
|
282
377
|
def add_dataset(name, &block)
|
283
378
|
builder = GoodData::Model::SchemaBuilder.new(name)
|
284
379
|
block.call(builder)
|
285
|
-
if @datasets.any? {|item| item[:name] == name}
|
286
|
-
ds = @datasets.find {|item| item[:name] == name}
|
380
|
+
if @datasets.any? { |item| item[:name] == name }
|
381
|
+
ds = @datasets.find { |item| item[:name] == name }
|
287
382
|
index = @datasets.index(ds)
|
288
383
|
stuff = GoodData::Model.merge_dataset_columns(ds, builder.to_hash)
|
289
384
|
@datasets.delete_at(index)
|
@@ -325,9 +420,9 @@ module GoodData
|
|
325
420
|
mode = options[:mode] || "FULL"
|
326
421
|
dataset = options[:dataset]
|
327
422
|
@uploads << {
|
328
|
-
|
329
|
-
|
330
|
-
|
423
|
+
:source => data,
|
424
|
+
:mode => mode,
|
425
|
+
:dataset => dataset
|
331
426
|
}
|
332
427
|
end
|
333
428
|
|
@@ -337,9 +432,9 @@ module GoodData
|
|
337
432
|
|
338
433
|
def to_json(options={})
|
339
434
|
eliminate_empty = options[:eliminate_empty] || false
|
340
|
-
|
435
|
+
|
341
436
|
if eliminate_empty
|
342
|
-
JSON.pretty_generate(to_hash.reject {|k, v| v.is_a?(Enumerable) && v.empty?})
|
437
|
+
JSON.pretty_generate(to_hash.reject { |k, v| v.is_a?(Enumerable) && v.empty? })
|
343
438
|
else
|
344
439
|
JSON.pretty_generate(to_hash)
|
345
440
|
end
|
@@ -347,20 +442,20 @@ module GoodData
|
|
347
442
|
|
348
443
|
def to_hash
|
349
444
|
{
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
445
|
+
:title => @title,
|
446
|
+
:datasets => @datasets,
|
447
|
+
:uploads => @uploads,
|
448
|
+
:dashboards => @dashboards,
|
449
|
+
:metrics => @metrics,
|
450
|
+
:reports => @reports,
|
451
|
+
:users => @users,
|
452
|
+
:assert_tests => @assert_tests,
|
453
|
+
:date_dimensions => @date_dimensions
|
359
454
|
}
|
360
455
|
end
|
361
456
|
|
362
457
|
def get_dataset(name)
|
363
|
-
datasets.find {|d| d.name == name}
|
458
|
+
datasets.find { |d| d.name == name }
|
364
459
|
end
|
365
460
|
|
366
461
|
end
|
@@ -381,8 +476,8 @@ module GoodData
|
|
381
476
|
|
382
477
|
def to_hash
|
383
478
|
{
|
384
|
-
|
385
|
-
|
479
|
+
:name => @name,
|
480
|
+
:tabs => @tabs.map { |tab| tab.to_hash }
|
386
481
|
}
|
387
482
|
end
|
388
483
|
end
|
@@ -400,8 +495,8 @@ module GoodData
|
|
400
495
|
|
401
496
|
def to_hash
|
402
497
|
{
|
403
|
-
|
404
|
-
|
498
|
+
:title => @title,
|
499
|
+
:items => @stuff
|
405
500
|
}
|
406
501
|
end
|
407
502
|
|
@@ -412,7 +507,7 @@ module GoodData
|
|
412
507
|
attr_accessor :data
|
413
508
|
|
414
509
|
class << self
|
415
|
-
|
510
|
+
|
416
511
|
def create_from_data(blueprint)
|
417
512
|
sc = SchemaBuilder.new
|
418
513
|
sc.data = blueprint.to_hash
|
@@ -423,8 +518,8 @@ module GoodData
|
|
423
518
|
|
424
519
|
def initialize(name=nil)
|
425
520
|
@data = {
|
426
|
-
|
427
|
-
|
521
|
+
:name => name,
|
522
|
+
:columns => []
|
428
523
|
}
|
429
524
|
end
|
430
525
|
|
@@ -442,31 +537,31 @@ module GoodData
|
|
442
537
|
end
|
443
538
|
|
444
539
|
def add_anchor(name, options={})
|
445
|
-
add_column({
|
540
|
+
add_column({:type => :anchor, :name => name}.merge(options))
|
446
541
|
self
|
447
542
|
end
|
448
543
|
|
449
544
|
def add_attribute(name, options={})
|
450
|
-
add_column({
|
545
|
+
add_column({:type => :attribute, :name => name}.merge(options))
|
451
546
|
self
|
452
547
|
end
|
453
548
|
|
454
549
|
def add_fact(name, options={})
|
455
|
-
add_column({
|
550
|
+
add_column({:type => :fact, :name => name}.merge(options))
|
456
551
|
self
|
457
552
|
end
|
458
553
|
|
459
554
|
def add_label(name, options={})
|
460
|
-
add_column({
|
555
|
+
add_column({:type => :label, :name => name}.merge(options))
|
461
556
|
self
|
462
557
|
end
|
463
558
|
|
464
559
|
def add_date(name, options={})
|
465
|
-
add_column({
|
560
|
+
add_column({:type => :date, :name => name}.merge(options))
|
466
561
|
end
|
467
562
|
|
468
563
|
def add_reference(name, options={})
|
469
|
-
add_column({
|
564
|
+
add_column({:type => :reference, :name => name}.merge(options))
|
470
565
|
end
|
471
566
|
|
472
567
|
def to_json
|
@@ -484,20 +579,21 @@ module GoodData
|
|
484
579
|
end
|
485
580
|
|
486
581
|
class ProjectCreator
|
487
|
-
|
582
|
+
|
488
583
|
class << self
|
489
584
|
def migrate(options={})
|
490
585
|
|
491
586
|
spec = options[:spec] || fail("You need to provide spec for migration")
|
492
587
|
spec = spec.to_hash
|
493
|
-
|
494
|
-
token = options[:token]
|
588
|
+
|
589
|
+
token = options[:token]
|
495
590
|
project = options[:project] || GoodData::Project.create(:title => spec[:title], :auth_token => token)
|
591
|
+
fail("You need to specify token for project creation") if token.nil? && project.nil?
|
496
592
|
|
497
593
|
begin
|
498
594
|
GoodData.with_project(project) do |p|
|
499
|
-
migrate_date_dimensions(p, spec[:date_dimensions] || [])
|
500
|
-
migrate_datasets(p, spec
|
595
|
+
# migrate_date_dimensions(p, spec[:date_dimensions] || [])
|
596
|
+
migrate_datasets(p, spec)
|
501
597
|
load(p, spec)
|
502
598
|
migrate_metrics(p, spec[:metrics] || [])
|
503
599
|
migrate_reports(p, spec[:reports] || [])
|
@@ -516,9 +612,31 @@ module GoodData
|
|
516
612
|
end
|
517
613
|
|
518
614
|
def migrate_datasets(project, spec)
|
519
|
-
|
520
|
-
|
521
|
-
|
615
|
+
bp = ProjectBlueprint.new(spec)
|
616
|
+
# schema = Schema.load(schema) unless schema.respond_to?(:to_maql_create)
|
617
|
+
# project = GoodData.project unless project
|
618
|
+
result = GoodData.post("/gdc/projects/#{GoodData.project.pid}/model/diff", bp.to_wire_model)
|
619
|
+
link = result["asyncTask"]["link"]["poll"]
|
620
|
+
response = GoodData.get(link, :process => false)
|
621
|
+
# pp response
|
622
|
+
while response.code != 200
|
623
|
+
sleep 1
|
624
|
+
GoodData.connection.retryable(:tries => 3, :on => RestClient::InternalServerError) do
|
625
|
+
sleep 1
|
626
|
+
response = GoodData.get(link, :process => false)
|
627
|
+
# pp response
|
628
|
+
end
|
629
|
+
end
|
630
|
+
response = GoodData.get(link)
|
631
|
+
ldm_links = GoodData.get project.md[LDM_CTG]
|
632
|
+
ldm_uri = Links.new(ldm_links)[LDM_MANAGE_CTG]
|
633
|
+
chunks = response["projectModelDiff"]["updateScripts"].find_all {|script| script["updateScript"]["preserveData"] == true && script["updateScript"]["cascadeDrops"] == false}.map {|x| x["updateScript"]["maqlDdlChunks"]}.flatten
|
634
|
+
chunks.each do |chunk|
|
635
|
+
GoodData.post ldm_uri, { 'manage' => { 'maql' => chunk } }
|
636
|
+
end
|
637
|
+
|
638
|
+
bp.datasets.each do |ds|
|
639
|
+
schema = ds.to_schema
|
522
640
|
GoodData::ProjectMetadata["manifest_#{schema.name}"] = schema.to_manifest.to_json
|
523
641
|
end
|
524
642
|
end
|
@@ -549,9 +667,11 @@ module GoodData
|
|
549
667
|
end
|
550
668
|
|
551
669
|
def load(project, spec)
|
552
|
-
spec
|
553
|
-
|
554
|
-
|
670
|
+
if spec.has_key?(:uploads)
|
671
|
+
spec[:uploads].each do |load|
|
672
|
+
schema = GoodData::Model::Schema.new(spec[:datasets].detect {|d| d[:name] == load[:dataset]})
|
673
|
+
project.upload(load[:source], schema, load[:mode])
|
674
|
+
end
|
555
675
|
end
|
556
676
|
end
|
557
677
|
|
@@ -563,7 +683,7 @@ module GoodData
|
|
563
683
|
end
|
564
684
|
end
|
565
685
|
end
|
566
|
-
|
686
|
+
|
567
687
|
class MdObject
|
568
688
|
attr_accessor :name, :title
|
569
689
|
|
@@ -590,7 +710,7 @@ module GoodData
|
|
590
710
|
# model abstractions.
|
591
711
|
#
|
592
712
|
class Schema < MdObject
|
593
|
-
attr_reader :fields, :attributes, :facts, :folders, :references, :labels, :name, :title
|
713
|
+
attr_reader :fields, :attributes, :facts, :folders, :references, :labels, :name, :title, :anchor
|
594
714
|
|
595
715
|
def self.load(file)
|
596
716
|
Schema.new JSON.load(open(file))
|
@@ -599,13 +719,13 @@ module GoodData
|
|
599
719
|
def initialize(config, name = nil)
|
600
720
|
super()
|
601
721
|
@fields = []
|
602
|
-
@attributes =
|
603
|
-
@facts =
|
722
|
+
@attributes = []
|
723
|
+
@facts = []
|
604
724
|
@folders = {
|
605
|
-
|
606
|
-
|
725
|
+
:facts => {},
|
726
|
+
:attributes => {}
|
607
727
|
}
|
608
|
-
@references =
|
728
|
+
@references = []
|
609
729
|
@labels = []
|
610
730
|
|
611
731
|
config[:name] = name unless config[:name]
|
@@ -626,7 +746,7 @@ module GoodData
|
|
626
746
|
when "date"
|
627
747
|
add_date c
|
628
748
|
when "anchor"
|
629
|
-
|
749
|
+
set_anchor c
|
630
750
|
when "label"
|
631
751
|
add_label c
|
632
752
|
when "reference"
|
@@ -635,7 +755,7 @@ module GoodData
|
|
635
755
|
fail "Unexpected type #{c[:type]} in #{c.inspect}"
|
636
756
|
end
|
637
757
|
end
|
638
|
-
@
|
758
|
+
@anchor = Anchor.new(nil, self) unless @anchor
|
639
759
|
end
|
640
760
|
|
641
761
|
def type_prefix
|
@@ -654,7 +774,7 @@ module GoodData
|
|
654
774
|
#
|
655
775
|
def to_maql_drop
|
656
776
|
maql = ""
|
657
|
-
[
|
777
|
+
[attributes, facts].each do |obj|
|
658
778
|
maql += obj.to_maql_drop
|
659
779
|
end
|
660
780
|
maql += "DROP {#{self.identifier}};\n"
|
@@ -666,7 +786,7 @@ module GoodData
|
|
666
786
|
def to_maql_create
|
667
787
|
maql = "# Create the '#{self.title}' data set\n"
|
668
788
|
maql += "CREATE DATASET {#{self.identifier}} VISUAL (TITLE \"#{self.title}\");\n\n"
|
669
|
-
[ attributes, facts, { 1 => @
|
789
|
+
[ attributes, facts, { 1 => @anchor } ].each do |objects|
|
670
790
|
objects.values.each do |obj|
|
671
791
|
maql += "# Create '#{obj.title}' and add it to the '#{self.title}' data set.\n"
|
672
792
|
maql += obj.to_maql_create
|
@@ -709,26 +829,37 @@ module GoodData
|
|
709
829
|
#
|
710
830
|
def to_manifest(mode="FULL")
|
711
831
|
{
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
832
|
+
'dataSetSLIManifest' => {
|
833
|
+
'parts' => fields.reduce([]) { |memo, f| val = f.to_manifest_part(mode); memo << val unless val.nil?; memo },
|
834
|
+
'dataSet' => self.identifier,
|
835
|
+
'file' => 'data.csv', # should be configurable
|
836
|
+
'csvParams' => {
|
837
|
+
'quoteChar' => '"',
|
838
|
+
'escapeChar' => '"',
|
839
|
+
'separatorChar' => ',',
|
840
|
+
'endOfLine' => "\n"
|
841
|
+
}
|
721
842
|
}
|
722
|
-
}
|
723
843
|
}
|
724
844
|
end
|
725
845
|
|
846
|
+
def to_wire_model
|
847
|
+
{
|
848
|
+
"dataset" => {
|
849
|
+
"identifier" => identifier,
|
850
|
+
"title" => title,
|
851
|
+
"anchor" => @anchor.to_wire_model,
|
852
|
+
"facts" => facts.map {|f| f.to_wire_model},
|
853
|
+
"attributes" => attributes.map {|a| a.to_wire_model},
|
854
|
+
"references" => references.map {|r| r.is_a?(DateReference) ? r.schema_ref : type_prefix + "." + r.schema_ref }}}
|
855
|
+
end
|
856
|
+
|
726
857
|
private
|
727
858
|
|
728
859
|
def add_attribute(column)
|
729
860
|
attribute = Attribute.new column, self
|
730
861
|
fields << attribute
|
731
|
-
|
862
|
+
attributes << attribute
|
732
863
|
add_attribute_folder(attribute.folder)
|
733
864
|
# folders[AttributeFolder.new(attribute.folder)] = 1 if attribute.folder
|
734
865
|
end
|
@@ -742,7 +873,7 @@ module GoodData
|
|
742
873
|
def add_fact(column)
|
743
874
|
fact = Fact.new column, self
|
744
875
|
fields << fact
|
745
|
-
|
876
|
+
facts << fact
|
746
877
|
add_fact_folder(fact.folder)
|
747
878
|
# folders[FactFolder.new(fact.folder)] = 1 if fact.folder
|
748
879
|
end
|
@@ -762,24 +893,23 @@ module GoodData
|
|
762
893
|
def add_reference(column)
|
763
894
|
reference = Reference.new(column, self)
|
764
895
|
fields << reference
|
765
|
-
|
896
|
+
references << reference
|
766
897
|
end
|
767
898
|
|
768
899
|
def add_date(column)
|
769
900
|
date = DateColumn.new column, self
|
770
901
|
@fields << date
|
771
902
|
date.parts.values.each { |p| @fields << p }
|
772
|
-
date.facts.each { |f|
|
773
|
-
date.attributes.each { |a|
|
774
|
-
date.references.each {|r|
|
903
|
+
date.facts.each { |f| facts << f }
|
904
|
+
date.attributes.each { |a| attributes << a }
|
905
|
+
date.references.each {|r| references << r}
|
775
906
|
end
|
776
907
|
|
777
|
-
def
|
778
|
-
@
|
779
|
-
@fields << @
|
908
|
+
def set_anchor(column)
|
909
|
+
@anchor = Anchor.new column, self
|
910
|
+
@fields << @anchor
|
780
911
|
end
|
781
912
|
|
782
|
-
def add_to_hash(hash, obj); hash[obj.identifier] = obj; end
|
783
913
|
end
|
784
914
|
|
785
915
|
##
|
@@ -792,10 +922,10 @@ module GoodData
|
|
792
922
|
def initialize(hash, schema)
|
793
923
|
super()
|
794
924
|
raise ArgumentError.new("Schema must be provided, got #{schema.class}") unless schema.is_a? Schema
|
795
|
-
@name
|
796
|
-
@title
|
797
|
-
@folder
|
798
|
-
@schema
|
925
|
+
@name = hash[:name] || raise("Data set fields must have their names defined")
|
926
|
+
@title = hash[:title] || hash[:name].humanize
|
927
|
+
@folder = hash[:folder]
|
928
|
+
@schema = schema
|
799
929
|
end
|
800
930
|
|
801
931
|
##
|
@@ -838,13 +968,19 @@ module GoodData
|
|
838
968
|
# GoodData attribute abstraction
|
839
969
|
#
|
840
970
|
class Attribute < Column
|
841
|
-
attr_reader :primary_label
|
971
|
+
attr_reader :primary_label, :labels
|
842
972
|
|
843
|
-
def type_prefix
|
844
|
-
|
973
|
+
def type_prefix;
|
974
|
+
ATTRIBUTE_PREFIX;
|
975
|
+
end
|
976
|
+
|
977
|
+
def folder_prefix;
|
978
|
+
ATTRIBUTE_FOLDER_PREFIX;
|
979
|
+
end
|
845
980
|
|
846
981
|
def initialize(hash, schema)
|
847
982
|
super hash, schema
|
983
|
+
@labels = []
|
848
984
|
@primary_label = Label.new hash, self, schema
|
849
985
|
end
|
850
986
|
|
@@ -852,7 +988,9 @@ module GoodData
|
|
852
988
|
@table ||= "d_" + @schema.name + "_" + name
|
853
989
|
end
|
854
990
|
|
855
|
-
def key
|
991
|
+
def key;
|
992
|
+
"#{@name}#{FK_SUFFIX}";
|
993
|
+
end
|
856
994
|
|
857
995
|
def to_maql_create
|
858
996
|
maql = "CREATE ATTRIBUTE {#{identifier}} VISUAL (#{visual})" \
|
@@ -863,12 +1001,31 @@ module GoodData
|
|
863
1001
|
|
864
1002
|
def to_manifest_part(mode)
|
865
1003
|
{
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
1004
|
+
'referenceKey' => 1,
|
1005
|
+
'populates' => [@primary_label.identifier],
|
1006
|
+
'mode' => mode,
|
1007
|
+
'columnName' => name
|
1008
|
+
}
|
1009
|
+
end
|
1010
|
+
|
1011
|
+
def to_wire_model
|
1012
|
+
{
|
1013
|
+
"attribute" => {
|
1014
|
+
"identifier" => identifier,
|
1015
|
+
"title" => title,
|
1016
|
+
"labels" => labels.map do |l|
|
1017
|
+
{
|
1018
|
+
"label" => {
|
1019
|
+
"identifier" => l.identifier,
|
1020
|
+
"title" => l.title,
|
1021
|
+
"type" => "GDC.text"
|
1022
|
+
}
|
1023
|
+
}
|
1024
|
+
end
|
1025
|
+
}
|
870
1026
|
}
|
871
1027
|
end
|
1028
|
+
|
872
1029
|
end
|
873
1030
|
|
874
1031
|
##
|
@@ -877,12 +1034,17 @@ module GoodData
|
|
877
1034
|
# field
|
878
1035
|
#
|
879
1036
|
class Label < Column
|
1037
|
+
|
1038
|
+
attr_accessor :attribute
|
1039
|
+
|
880
1040
|
def type_prefix ; 'label' ; end
|
881
1041
|
|
882
1042
|
# def initialize(hash, schema)
|
883
1043
|
def initialize(hash, attribute, schema)
|
884
1044
|
super hash, schema
|
885
|
-
|
1045
|
+
attribute = attribute.nil? ? schema.fields.find {|field| field.name === hash[:reference]} : attribute
|
1046
|
+
@attribute = attribute
|
1047
|
+
attribute.labels << self
|
886
1048
|
end
|
887
1049
|
|
888
1050
|
def to_maql_create
|
@@ -893,9 +1055,9 @@ module GoodData
|
|
893
1055
|
|
894
1056
|
def to_manifest_part(mode)
|
895
1057
|
{
|
896
|
-
|
897
|
-
|
898
|
-
|
1058
|
+
'populates' => [identifier],
|
1059
|
+
'mode' => mode,
|
1060
|
+
'columnName' => name
|
899
1061
|
}
|
900
1062
|
end
|
901
1063
|
|
@@ -904,6 +1066,7 @@ module GoodData
|
|
904
1066
|
end
|
905
1067
|
|
906
1068
|
alias :inspect_orig :inspect
|
1069
|
+
|
907
1070
|
def inspect
|
908
1071
|
inspect_orig.sub(/>$/, " @attribute=" + @attribute.to_s.sub(/>$/, " @name=#{@attribute.name}") + '>')
|
909
1072
|
end
|
@@ -913,15 +1076,14 @@ module GoodData
|
|
913
1076
|
# A GoodData attribute that represents a data set's connection point or a data set
|
914
1077
|
# without a connection point
|
915
1078
|
#
|
916
|
-
class
|
1079
|
+
class Anchor < Attribute
|
917
1080
|
def initialize(column, schema)
|
918
1081
|
if column then
|
919
1082
|
super
|
920
1083
|
else
|
921
|
-
|
922
|
-
@
|
923
|
-
@
|
924
|
-
@schema = schema
|
1084
|
+
super({:type => "anchor", :name => 'id'}, schema)
|
1085
|
+
@labels = []
|
1086
|
+
@primary_label = nil
|
925
1087
|
end
|
926
1088
|
end
|
927
1089
|
|
@@ -938,15 +1100,24 @@ module GoodData
|
|
938
1100
|
end
|
939
1101
|
maql
|
940
1102
|
end
|
1103
|
+
|
941
1104
|
end
|
942
1105
|
|
943
1106
|
##
|
944
1107
|
# GoodData fact abstraction
|
945
1108
|
#
|
946
1109
|
class Fact < Column
|
947
|
-
def type_prefix
|
948
|
-
|
949
|
-
|
1110
|
+
def type_prefix;
|
1111
|
+
FACT_PREFIX;
|
1112
|
+
end
|
1113
|
+
|
1114
|
+
def column_prefix;
|
1115
|
+
FACT_COLUMN_PREFIX;
|
1116
|
+
end
|
1117
|
+
|
1118
|
+
def folder_prefix;
|
1119
|
+
FACT_FOLDER_PREFIX;
|
1120
|
+
end
|
950
1121
|
|
951
1122
|
def table
|
952
1123
|
@schema.table
|
@@ -963,9 +1134,18 @@ module GoodData
|
|
963
1134
|
|
964
1135
|
def to_manifest_part(mode)
|
965
1136
|
{
|
966
|
-
|
967
|
-
|
968
|
-
|
1137
|
+
'populates' => [identifier],
|
1138
|
+
'mode' => mode,
|
1139
|
+
'columnName' => name
|
1140
|
+
}
|
1141
|
+
end
|
1142
|
+
|
1143
|
+
def to_wire_model
|
1144
|
+
{
|
1145
|
+
"fact" => {
|
1146
|
+
"identifier" => identifier,
|
1147
|
+
"title" => title
|
1148
|
+
}
|
969
1149
|
}
|
970
1150
|
end
|
971
1151
|
end
|
@@ -974,13 +1154,16 @@ module GoodData
|
|
974
1154
|
# Reference to another data set
|
975
1155
|
#
|
976
1156
|
class Reference < Column
|
1157
|
+
|
1158
|
+
attr_accessor :reference, :schema_ref
|
1159
|
+
|
977
1160
|
def initialize(column, schema)
|
978
1161
|
super column, schema
|
979
1162
|
# pp column
|
980
|
-
@name
|
981
|
-
@reference
|
1163
|
+
@name = column[:name]
|
1164
|
+
@reference = column[:reference]
|
982
1165
|
@schema_ref = column[:dataset]
|
983
|
-
@schema
|
1166
|
+
@schema = schema
|
984
1167
|
end
|
985
1168
|
|
986
1169
|
##
|
@@ -992,7 +1175,9 @@ module GoodData
|
|
992
1175
|
@identifier ||= "#{ATTRIBUTE_PREFIX}.#{@schema_ref}.#{@reference}"
|
993
1176
|
end
|
994
1177
|
|
995
|
-
def key
|
1178
|
+
def key;
|
1179
|
+
"#{@name}_id";
|
1180
|
+
end
|
996
1181
|
|
997
1182
|
def label_column
|
998
1183
|
"#{LABEL_PREFIX}.#{@schema_ref}.#{@reference}"
|
@@ -1008,52 +1193,14 @@ module GoodData
|
|
1008
1193
|
|
1009
1194
|
def to_manifest_part(mode)
|
1010
1195
|
{
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
1196
|
+
'populates' => [label_column],
|
1197
|
+
'mode' => mode,
|
1198
|
+
'columnName' => name,
|
1199
|
+
'referenceKey' => 1
|
1015
1200
|
}
|
1016
1201
|
end
|
1017
1202
|
end
|
1018
1203
|
|
1019
|
-
##
|
1020
|
-
# Fact representation of a date.
|
1021
|
-
#
|
1022
|
-
# class DateFact < Fact
|
1023
|
-
#
|
1024
|
-
# attr_accessor :format, :output_format
|
1025
|
-
#
|
1026
|
-
# def initialize(column, schema)
|
1027
|
-
# super column, schema
|
1028
|
-
# @output_format = column["format"] || '("dd/MM/yyyy")'
|
1029
|
-
# @format = @output_format.gsub('yyyy', '%Y').gsub('MM', '%m').gsub('dd', '%d')
|
1030
|
-
# end
|
1031
|
-
#
|
1032
|
-
# def column_prefix ; DATE_COLUMN_PREFIX ; end
|
1033
|
-
# def type_prefix ; DATE_FACT_PREFIX ; end
|
1034
|
-
#
|
1035
|
-
# def to_csv_header(row)
|
1036
|
-
# "#{name}_fact"
|
1037
|
-
# end
|
1038
|
-
#
|
1039
|
-
# def to_csv_data(headers, row)
|
1040
|
-
# val = row[name]
|
1041
|
-
# val.nil?() ? nil : (Date.strptime(val, format) - BEGINNING_OF_TIMES).to_i
|
1042
|
-
# rescue ArgumentError
|
1043
|
-
# raise "Value \"#{val}\" for column \"#{name}\" did not match the format: #{format}. " +
|
1044
|
-
# "Perhaps you need to add or change the \"format\" key in the data set configuration."
|
1045
|
-
# end
|
1046
|
-
#
|
1047
|
-
# def to_manifest_part(mode)
|
1048
|
-
# {
|
1049
|
-
# 'populates' => [ identifier ],
|
1050
|
-
# 'mode' => mode,
|
1051
|
-
# 'columnName' => "#{name}_fact"
|
1052
|
-
# }
|
1053
|
-
# end
|
1054
|
-
#
|
1055
|
-
# end
|
1056
|
-
|
1057
1204
|
##
|
1058
1205
|
# Date as a reference to a date dimension
|
1059
1206
|
#
|
@@ -1074,11 +1221,11 @@ module GoodData
|
|
1074
1221
|
|
1075
1222
|
def to_manifest_part(mode)
|
1076
1223
|
{
|
1077
|
-
|
1078
|
-
|
1079
|
-
|
1080
|
-
|
1081
|
-
|
1224
|
+
'populates' => ["#{identifier}.#{DATE_ATTRIBUTE_DEFAULT_DISPLAY_FORM}"],
|
1225
|
+
'mode' => mode,
|
1226
|
+
'constraints' => {"date" => output_format},
|
1227
|
+
'columnName' => name,
|
1228
|
+
'referenceKey' => 1
|
1082
1229
|
}
|
1083
1230
|
end
|
1084
1231
|
|
@@ -1090,21 +1237,22 @@ module GoodData
|
|
1090
1237
|
# # maql += "INCLUDE TEMPLATE \"#{urn}\" MODIFY (IDENTIFIER \"#{name}\", TITLE \"#{title || name}\");\n"
|
1091
1238
|
# maql += super_maql
|
1092
1239
|
# end
|
1093
|
-
|
1094
1240
|
end
|
1095
1241
|
|
1096
1242
|
##
|
1097
1243
|
# Date field that's not connected to a date dimension
|
1098
1244
|
#
|
1099
1245
|
class DateAttribute < Attribute
|
1100
|
-
def key
|
1246
|
+
def key;
|
1247
|
+
"#{DATE_COLUMN_PREFIX}#{super}";
|
1248
|
+
end
|
1101
1249
|
|
1102
1250
|
def to_manifest_part(mode)
|
1103
1251
|
{
|
1104
|
-
|
1105
|
-
|
1106
|
-
|
1107
|
-
|
1252
|
+
'populates' => ['label.stuff.mmddyy'],
|
1253
|
+
"format" => "unknown",
|
1254
|
+
"mode" => mode,
|
1255
|
+
"referenceKey" => 1
|
1108
1256
|
}
|
1109
1257
|
end
|
1110
1258
|
end
|
@@ -1113,8 +1261,13 @@ module GoodData
|
|
1113
1261
|
# Fact representation of a time of a day
|
1114
1262
|
#
|
1115
1263
|
class TimeFact < Fact
|
1116
|
-
def column_prefix
|
1117
|
-
|
1264
|
+
def column_prefix;
|
1265
|
+
TIME_COLUMN_PREFIX;
|
1266
|
+
end
|
1267
|
+
|
1268
|
+
def type_prefix;
|
1269
|
+
TIME_FACT_PREFIX;
|
1270
|
+
end
|
1118
1271
|
end
|
1119
1272
|
|
1120
1273
|
##
|
@@ -1128,9 +1281,17 @@ module GoodData
|
|
1128
1281
|
# Time field that's not connected to a time-of-a-day dimension
|
1129
1282
|
#
|
1130
1283
|
class TimeAttribute < Attribute
|
1131
|
-
def type_prefix
|
1132
|
-
|
1133
|
-
|
1284
|
+
def type_prefix;
|
1285
|
+
TIME_ATTRIBUTE_PREFIX;
|
1286
|
+
end
|
1287
|
+
|
1288
|
+
def key;
|
1289
|
+
"#{TIME_COLUMN_PREFIX}#{super}";
|
1290
|
+
end
|
1291
|
+
|
1292
|
+
def table;
|
1293
|
+
@table ||= "#{super}_tm";
|
1294
|
+
end
|
1134
1295
|
end
|
1135
1296
|
|
1136
1297
|
##
|
@@ -1143,7 +1304,7 @@ module GoodData
|
|
1143
1304
|
|
1144
1305
|
def initialize(column, schema)
|
1145
1306
|
super column, schema
|
1146
|
-
@parts = {}
|
1307
|
+
@parts = {}; @facts = []; @attributes = []; @references = []
|
1147
1308
|
|
1148
1309
|
# @facts << @parts[:date_fact] = DateFact.new(column, schema)
|
1149
1310
|
if column[:dataset] then
|
@@ -1204,16 +1365,26 @@ module GoodData
|
|
1204
1365
|
# GoodData attribute folder abstraction
|
1205
1366
|
#
|
1206
1367
|
class AttributeFolder < Folder
|
1207
|
-
def type;
|
1208
|
-
|
1368
|
+
def type;
|
1369
|
+
"ATTRIBUTE";
|
1370
|
+
end
|
1371
|
+
|
1372
|
+
def type_prefix;
|
1373
|
+
"dim";
|
1374
|
+
end
|
1209
1375
|
end
|
1210
1376
|
|
1211
1377
|
##
|
1212
1378
|
# GoodData fact folder abstraction
|
1213
1379
|
#
|
1214
1380
|
class FactFolder < Folder
|
1215
|
-
def type;
|
1216
|
-
|
1381
|
+
def type;
|
1382
|
+
"FACT";
|
1383
|
+
end
|
1384
|
+
|
1385
|
+
def type_prefix;
|
1386
|
+
"ffld";
|
1387
|
+
end
|
1217
1388
|
end
|
1218
1389
|
|
1219
1390
|
class DateDimension < MdObject
|