gooddata 0.6.18 → 0.6.19
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/.gitignore +2 -1
- data/.travis.yml +8 -19
- data/Guardfile +5 -0
- data/README.md +1 -3
- data/bin/gooddata +1 -1
- data/gooddata.gemspec +6 -4
- data/lib/gooddata.rb +1 -1
- data/lib/gooddata/bricks/middleware/aws_middleware.rb +24 -0
- data/lib/gooddata/cli/commands/console_cmd.rb +1 -1
- data/lib/gooddata/cli/commands/project_cmd.rb +29 -9
- data/lib/gooddata/cli/hooks.rb +9 -3
- data/lib/gooddata/commands/datawarehouse.rb +1 -7
- data/lib/gooddata/commands/project.rb +4 -3
- data/lib/gooddata/core/logging.rb +14 -2
- data/lib/gooddata/exceptions/execution_limit_exceeded.rb +9 -0
- data/lib/gooddata/exceptions/uncomputable_report.rb +8 -0
- data/lib/gooddata/exceptions/validation_error.rb +1 -1
- data/lib/gooddata/goodzilla/goodzilla.rb +5 -1
- data/lib/gooddata/helpers/data_helper.rb +40 -9
- data/lib/gooddata/mixins/md_finders.rb +35 -0
- data/lib/gooddata/models/blueprint/anchor_field.rb +46 -0
- data/lib/gooddata/models/blueprint/attribute_field.rb +25 -0
- data/lib/gooddata/models/blueprint/blueprint.rb +7 -0
- data/lib/gooddata/models/blueprint/blueprint_field.rb +66 -0
- data/lib/gooddata/models/{dashboard_builder.rb → blueprint/dashboard_builder.rb} +0 -0
- data/lib/gooddata/models/{schema_blueprint.rb → blueprint/dataset_blueprint.rb} +176 -117
- data/lib/gooddata/models/blueprint/date_dimension.rb +10 -0
- data/lib/gooddata/models/blueprint/fact_field.rb +16 -0
- data/lib/gooddata/models/blueprint/label_field.rb +39 -0
- data/lib/gooddata/models/{project_blueprint.rb → blueprint/project_blueprint.rb} +366 -168
- data/lib/gooddata/models/blueprint/project_builder.rb +79 -0
- data/lib/gooddata/models/blueprint/reference_field.rb +39 -0
- data/lib/gooddata/models/blueprint/schema_blueprint.rb +156 -0
- data/lib/gooddata/models/blueprint/schema_builder.rb +85 -0
- data/lib/gooddata/models/{to_manifest.rb → blueprint/to_manifest.rb} +25 -20
- data/lib/gooddata/models/{to_wire.rb → blueprint/to_wire.rb} +33 -52
- data/lib/gooddata/models/datawarehouse.rb +2 -2
- data/lib/gooddata/models/domain.rb +3 -2
- data/lib/gooddata/models/execution.rb +2 -2
- data/lib/gooddata/models/execution_detail.rb +7 -2
- data/lib/gooddata/models/from_wire.rb +60 -71
- data/lib/gooddata/models/from_wire_parse.rb +125 -125
- data/lib/gooddata/models/metadata.rb +14 -0
- data/lib/gooddata/models/metadata/dashboard.rb +2 -2
- data/lib/gooddata/models/metadata/label.rb +1 -1
- data/lib/gooddata/models/metadata/report.rb +6 -5
- data/lib/gooddata/models/metadata/report_definition.rb +44 -59
- data/lib/gooddata/models/model.rb +131 -43
- data/lib/gooddata/models/process.rb +13 -11
- data/lib/gooddata/models/profile.rb +12 -1
- data/lib/gooddata/models/project.rb +223 -19
- data/lib/gooddata/models/project_creator.rb +4 -15
- data/lib/gooddata/models/schedule.rb +1 -0
- data/lib/gooddata/models/user_filters/user_filter_builder.rb +2 -2
- data/lib/gooddata/rest/client.rb +18 -18
- data/lib/gooddata/rest/connection.rb +113 -94
- data/lib/gooddata/version.rb +1 -1
- data/lib/templates/project/model/model.rb.erb +15 -16
- data/spec/data/blueprints/additional_dataset_module.json +32 -0
- data/spec/data/blueprints/big_blueprint_not_pruned.json +2079 -0
- data/spec/data/blueprints/invalid_blueprint.json +103 -0
- data/spec/data/blueprints/m_n_model.json +104 -0
- data/spec/data/blueprints/model_module.json +25 -0
- data/spec/data/blueprints/test_blueprint.json +38 -0
- data/spec/data/blueprints/test_project_model_spec.json +106 -0
- data/spec/data/gd_gse_data_manifest.json +34 -34
- data/spec/data/manifests/test_blueprint.json +32 -0
- data/spec/data/{manifest_test_project.json → manifests/test_project.json} +9 -18
- data/spec/data/wire_models/test_blueprint.json +63 -0
- data/spec/data/wire_test_project.json +5 -5
- data/spec/environment/default.rb +33 -0
- data/spec/environment/develop.rb +26 -0
- data/spec/environment/environment.rb +14 -0
- data/spec/environment/hotfix.rb +17 -0
- data/spec/environment/production.rb +31 -0
- data/spec/environment/release.rb +17 -0
- data/spec/helpers/blueprint_helper.rb +10 -7
- data/spec/helpers/cli_helper.rb +24 -22
- data/spec/helpers/connection_helper.rb +27 -25
- data/spec/helpers/crypto_helper.rb +7 -5
- data/spec/helpers/csv_helper.rb +5 -3
- data/spec/helpers/process_helper.rb +15 -10
- data/spec/helpers/project_helper.rb +40 -33
- data/spec/helpers/schedule_helper.rb +15 -9
- data/spec/helpers/spec_helper.rb +11 -0
- data/spec/integration/blueprint_updates_spec.rb +93 -0
- data/spec/integration/command_datawarehouse_spec.rb +2 -1
- data/spec/integration/command_projects_spec.rb +9 -8
- data/spec/integration/create_from_template_spec.rb +1 -1
- data/spec/integration/create_project_spec.rb +1 -1
- data/spec/integration/full_process_schedule_spec.rb +1 -1
- data/spec/integration/full_project_spec.rb +91 -30
- data/spec/integration/over_to_user_filters_spec.rb +24 -28
- data/spec/integration/partial_md_export_import_spec.rb +4 -4
- data/spec/integration/project_spec.rb +1 -1
- data/spec/integration/rest_spec.rb +1 -1
- data/spec/integration/user_filters_spec.rb +19 -24
- data/spec/integration/variables_spec.rb +7 -9
- data/spec/logging_in_logging_out_spec.rb +1 -1
- data/spec/spec_helper.rb +10 -1
- data/spec/unit/bricks/middleware/aws_middelware_spec.rb +47 -0
- data/spec/unit/core/connection_spec.rb +2 -2
- data/spec/unit/core/logging_spec.rb +12 -4
- data/spec/unit/helpers/data_helper_spec.rb +60 -0
- data/spec/unit/models/blueprint/attributes_spec.rb +24 -0
- data/spec/unit/models/blueprint/dataset_spec.rb +116 -0
- data/spec/unit/models/blueprint/labels_spec.rb +39 -0
- data/spec/unit/models/blueprint/project_blueprint_spec.rb +643 -0
- data/spec/unit/models/blueprint/reference_spec.rb +24 -0
- data/spec/unit/models/{schema_builder_spec.rb → blueprint/schema_builder_spec.rb} +12 -4
- data/spec/unit/models/blueprint/to_wire_spec.rb +169 -0
- data/spec/unit/models/domain_spec.rb +13 -2
- data/spec/unit/models/from_wire_spec.rb +277 -98
- data/spec/unit/models/metadata_spec.rb +22 -4
- data/spec/unit/models/model_spec.rb +49 -39
- data/spec/unit/models/profile_spec.rb +1 -0
- data/spec/unit/models/project_spec.rb +7 -7
- data/spec/unit/models/schedule_spec.rb +20 -0
- data/spec/unit/models/to_manifest_spec.rb +31 -11
- data/spec/unit/rest/polling_spec.rb +86 -0
- metadata +102 -30
- data/lib/gooddata/models/project_builder.rb +0 -136
- data/lib/gooddata/models/schema_builder.rb +0 -77
- data/out.txt +0 -0
- data/spec/data/additional_dataset_module.json +0 -18
- data/spec/data/blueprint_invalid.json +0 -38
- data/spec/data/m_n_model/blueprint.json +0 -76
- data/spec/data/model_module.json +0 -18
- data/spec/data/test_project_model_spec.json +0 -76
- data/spec/unit/models/attribute_column_spec.rb +0 -7
- data/spec/unit/models/project_blueprint_spec.rb +0 -239
- data/spec/unit/models/to_wire_spec.rb +0 -71
|
@@ -38,6 +38,11 @@ module GoodData
|
|
|
38
38
|
@json = data.to_hash
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
+
def add_tag(a_tag)
|
|
42
|
+
self.tags = tag_set.add(a_tag).to_a.join(' ')
|
|
43
|
+
self
|
|
44
|
+
end
|
|
45
|
+
|
|
41
46
|
def delete
|
|
42
47
|
if saved? # rubocop:disable Style/GuardClause
|
|
43
48
|
client.delete(uri)
|
|
@@ -70,6 +75,11 @@ module GoodData
|
|
|
70
75
|
@project ||= Project[uri.gsub(%r{\/obj\/\d+$}, ''), :client => client]
|
|
71
76
|
end
|
|
72
77
|
|
|
78
|
+
def remove_tag(a_tag)
|
|
79
|
+
self.tags = tag_set.delete(a_tag).to_a.join(' ')
|
|
80
|
+
self
|
|
81
|
+
end
|
|
82
|
+
|
|
73
83
|
def saved?
|
|
74
84
|
res = uri.nil?
|
|
75
85
|
!res
|
|
@@ -132,6 +142,10 @@ module GoodData
|
|
|
132
142
|
x.save
|
|
133
143
|
end
|
|
134
144
|
|
|
145
|
+
def tag_set
|
|
146
|
+
tags.scan(/\w+/).to_set
|
|
147
|
+
end
|
|
148
|
+
|
|
135
149
|
def ==(other)
|
|
136
150
|
other.respond_to?(:uri) && other.uri == uri && other.respond_to?(:to_hash) && other.to_hash == to_hash
|
|
137
151
|
end
|
|
@@ -88,9 +88,9 @@ module GoodData
|
|
|
88
88
|
fail "Wrong format provied \"#{format}\". Only supports formats #{supported_formats.join(', ')}" unless supported_formats.include?(format)
|
|
89
89
|
tab = options[:tab] || ''
|
|
90
90
|
|
|
91
|
-
req_uri = "/gdc/projects/#{
|
|
91
|
+
req_uri = "/gdc/projects/#{project.pid}/clientexport"
|
|
92
92
|
x = client.post(req_uri, 'clientExport' => { 'url' => "https://secure.gooddata.com/dashboard.html#project=#{GoodData.project.uri}&dashboard=#{uri}&tab=#{tab}&export=1", 'name' => title })
|
|
93
|
-
|
|
93
|
+
client.poll_on_code(x['asyncTask']['link']['poll'], options.merge(process: false))
|
|
94
94
|
end
|
|
95
95
|
|
|
96
96
|
def tabs
|
|
@@ -33,7 +33,7 @@ module GoodData
|
|
|
33
33
|
result = client.get(uri + "/?id=#{element_id}")
|
|
34
34
|
items = result['attributeElements']['elements']
|
|
35
35
|
if items.empty?
|
|
36
|
-
fail
|
|
36
|
+
fail(AttributeElementNotFound, element_id)
|
|
37
37
|
else
|
|
38
38
|
items.first['title']
|
|
39
39
|
end
|
|
@@ -26,7 +26,7 @@ module GoodData
|
|
|
26
26
|
p = options[:project]
|
|
27
27
|
fail ArgumentError, 'No :project specified' if p.nil?
|
|
28
28
|
|
|
29
|
-
project =
|
|
29
|
+
project = client.projects(p)
|
|
30
30
|
fail ArgumentError, 'Wrong :project specified' if project.nil?
|
|
31
31
|
|
|
32
32
|
title = options[:title]
|
|
@@ -71,6 +71,7 @@ module GoodData
|
|
|
71
71
|
def definitions
|
|
72
72
|
content['definitions'].pmap { |uri| project.report_definitions(uri) }
|
|
73
73
|
end
|
|
74
|
+
alias_method :report_definitions, :definitions
|
|
74
75
|
|
|
75
76
|
# Gets list of uris of report definitions (versions) of this report.
|
|
76
77
|
#
|
|
@@ -92,12 +93,12 @@ module GoodData
|
|
|
92
93
|
# Computes the report and returns the result. If it is not computable returns nil.
|
|
93
94
|
#
|
|
94
95
|
# @return [GoodData::DataResult] Returns the result
|
|
95
|
-
def execute
|
|
96
|
+
def execute(options = {})
|
|
96
97
|
fail 'You have to save the report before executing. If you do not want to do that please use GoodData::ReportDefinition' unless saved?
|
|
97
98
|
result = client.post '/gdc/xtab2/executor3', 'report_req' => { 'report' => uri }
|
|
98
99
|
data_result_uri = result['execResult']['dataResult']
|
|
99
100
|
|
|
100
|
-
result = client.poll_on_response(data_result_uri) do |body|
|
|
101
|
+
result = client.poll_on_response(data_result_uri, options) do |body|
|
|
101
102
|
body && body['taskState'] && body['taskState']['status'] == 'WAIT'
|
|
102
103
|
end
|
|
103
104
|
|
|
@@ -119,10 +120,10 @@ module GoodData
|
|
|
119
120
|
# either 'csv', 'xls', 'xlsx' or 'pdf'.
|
|
120
121
|
#
|
|
121
122
|
# @return [String] Returns data
|
|
122
|
-
def export(format)
|
|
123
|
+
def export(format, options = {})
|
|
123
124
|
result = client.post('/gdc/xtab2/executor3', 'report_req' => { 'report' => uri })
|
|
124
125
|
result1 = client.post('/gdc/exporter/executor', :result_req => { :format => format, :result => result })
|
|
125
|
-
|
|
126
|
+
client.poll_on_code(result1['uri'], options.merge(process: false))
|
|
126
127
|
end
|
|
127
128
|
|
|
128
129
|
# Returns the newest (current version) report definition uri
|
|
@@ -131,57 +131,12 @@ module GoodData
|
|
|
131
131
|
|
|
132
132
|
begin
|
|
133
133
|
unsaved_metrics.each(&:save)
|
|
134
|
-
|
|
135
|
-
data_result(execute_inline(rd, options), options)
|
|
134
|
+
GoodData::ReportDefinition.create(options).execute
|
|
136
135
|
ensure
|
|
137
136
|
unsaved_metrics.each { |m| m.delete if m && m.saved? }
|
|
138
137
|
end
|
|
139
138
|
end
|
|
140
139
|
|
|
141
|
-
def execute_inline(rd, opts = { :client => GoodData.connection, :project => GoodData.project })
|
|
142
|
-
client = opts[:client]
|
|
143
|
-
project = opts[:project]
|
|
144
|
-
|
|
145
|
-
rd = rd.respond_to?(:json) ? rd.json : rd
|
|
146
|
-
data = {
|
|
147
|
-
report_req: {
|
|
148
|
-
definitionContent: {
|
|
149
|
-
content: rd,
|
|
150
|
-
projectMetadata: project.links['metadata']
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
uri = "/gdc/app/projects/#{project.pid}/execute"
|
|
155
|
-
client.post(uri, data)
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
# TODO: refactor the method. It should be instance method
|
|
159
|
-
# Method used for getting a data_result from a wire representation of
|
|
160
|
-
# @param result [Hash, Object] Wire data from JSON
|
|
161
|
-
# @return [GoodData::ReportDataResult]
|
|
162
|
-
def data_result(result, options = { :client => GoodData.connection })
|
|
163
|
-
client = options[:client]
|
|
164
|
-
fail ArgumentError, 'No :client specified' if client.nil?
|
|
165
|
-
|
|
166
|
-
data_result_uri = result['execResult']['dataResult']
|
|
167
|
-
result = client.poll_on_response(data_result_uri) do |body|
|
|
168
|
-
body && body['taskState'] && body['taskState']['status'] == 'WAIT'
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
if result.empty?
|
|
172
|
-
client.create(EmptyResult, result)
|
|
173
|
-
else
|
|
174
|
-
client.create(ReportDataResult, result)
|
|
175
|
-
end
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
# Return true if the report definition is a chart
|
|
179
|
-
#
|
|
180
|
-
# @return [Boolean] Return true if report definition is a chart
|
|
181
|
-
def chart?
|
|
182
|
-
!table?
|
|
183
|
-
end
|
|
184
|
-
|
|
185
140
|
def create(options = { :client => GoodData.connection, :project => GoodData.project })
|
|
186
141
|
client = options[:client]
|
|
187
142
|
fail ArgumentError, 'No :client specified' if client.nil?
|
|
@@ -253,6 +208,13 @@ module GoodData
|
|
|
253
208
|
self
|
|
254
209
|
end
|
|
255
210
|
|
|
211
|
+
# Return true if the report definition is a chart
|
|
212
|
+
#
|
|
213
|
+
# @return [Boolean] Return true if report definition is a chart
|
|
214
|
+
def chart?
|
|
215
|
+
!table?
|
|
216
|
+
end
|
|
217
|
+
|
|
256
218
|
def labels
|
|
257
219
|
attribute_parts.map { |part| project.labels(part['attribute']['uri']) }
|
|
258
220
|
end
|
|
@@ -265,26 +227,25 @@ module GoodData
|
|
|
265
227
|
metric_parts.map { |i| project.metrics(i['uri']) }
|
|
266
228
|
end
|
|
267
229
|
|
|
268
|
-
def execute(opts = {
|
|
269
|
-
client = opts[:client]
|
|
270
|
-
fail ArgumentError, 'No :client specified' if client.nil?
|
|
271
|
-
|
|
272
|
-
p = opts[:project]
|
|
273
|
-
fail ArgumentError, 'No :project specified' if p.nil?
|
|
274
|
-
|
|
275
|
-
project = client.projects(p)
|
|
276
|
-
fail ArgumentError, 'Wrong :project specified' if project.nil?
|
|
277
|
-
|
|
278
|
-
opts = { client: client, project: project }
|
|
230
|
+
def execute(opts = {})
|
|
279
231
|
result = if saved?
|
|
280
232
|
pars = {
|
|
281
233
|
'report_req' => { 'reportDefinition' => uri }
|
|
282
234
|
}
|
|
283
235
|
client.post '/gdc/xtab2/executor3', pars
|
|
284
236
|
else
|
|
285
|
-
|
|
237
|
+
data = {
|
|
238
|
+
report_req: {
|
|
239
|
+
definitionContent: {
|
|
240
|
+
content: to_hash,
|
|
241
|
+
projectMetadata: project.links['metadata']
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
uri = "/gdc/app/projects/#{project.pid}/execute"
|
|
246
|
+
client.post(uri, data)
|
|
286
247
|
end
|
|
287
|
-
|
|
248
|
+
data_result(result, opts)
|
|
288
249
|
end
|
|
289
250
|
|
|
290
251
|
def filters
|
|
@@ -394,5 +355,29 @@ module GoodData
|
|
|
394
355
|
def table?
|
|
395
356
|
content['format'] == 'grid'
|
|
396
357
|
end
|
|
358
|
+
|
|
359
|
+
private
|
|
360
|
+
|
|
361
|
+
def data_result(result, options = {})
|
|
362
|
+
data_result_uri = result['execResult']['dataResult']
|
|
363
|
+
begin
|
|
364
|
+
result = client.poll_on_response(data_result_uri, options) do |body|
|
|
365
|
+
body && body['taskState'] && body['taskState']['status'] == 'WAIT'
|
|
366
|
+
end
|
|
367
|
+
rescue RestClient::BadRequest => e
|
|
368
|
+
resp = JSON.parse(e.response)
|
|
369
|
+
if GoodData::Helpers.get_path(resp, %w(error component)) == 'MD::DataResult'
|
|
370
|
+
raise GoodData::UncomputableReport
|
|
371
|
+
else
|
|
372
|
+
raise e
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
if result.empty?
|
|
377
|
+
client.create(EmptyResult, result)
|
|
378
|
+
else
|
|
379
|
+
client.create(ReportDataResult, result)
|
|
380
|
+
end
|
|
381
|
+
end
|
|
397
382
|
end
|
|
398
383
|
end
|
|
@@ -7,6 +7,7 @@ require_relative 'metadata/metadata'
|
|
|
7
7
|
require_relative 'links'
|
|
8
8
|
require_relative 'module_constants'
|
|
9
9
|
require_relative 'user_filters/user_filters'
|
|
10
|
+
require_relative 'blueprint/blueprint'
|
|
10
11
|
|
|
11
12
|
require 'fileutils'
|
|
12
13
|
require 'multi_json'
|
|
@@ -19,53 +20,113 @@ require 'zip'
|
|
|
19
20
|
#
|
|
20
21
|
module GoodData
|
|
21
22
|
module Model
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
# See https://confluence.intgdc.com/display/plat/Catalog+of+Attribute+Types
|
|
24
|
+
GD_TYPES = [
|
|
25
|
+
# Common Types
|
|
26
|
+
'GDC.link',
|
|
27
|
+
'GDC.text',
|
|
28
|
+
'GDC.time',
|
|
29
|
+
|
|
30
|
+
# Common Date Attribute Types
|
|
31
|
+
'GDC.time.year',
|
|
32
|
+
'GDC.time.quarter',
|
|
33
|
+
'GDC.time.month',
|
|
34
|
+
'GDC.time.week',
|
|
35
|
+
'GDC.time.date',
|
|
36
|
+
|
|
37
|
+
# Specific Date Attribute Types
|
|
38
|
+
'GDC.time.day_in_euweek',
|
|
39
|
+
'GDC.time.day_in_week',
|
|
40
|
+
'GDC.time.day_in_month',
|
|
41
|
+
'GDC.time.day_in_quarter',
|
|
42
|
+
'GDC.time.day_in_year',
|
|
43
|
+
'GDC.time.euweek_in_quarter',
|
|
44
|
+
'GDC.time.week_in_quarter',
|
|
45
|
+
'GDC.time.euweek_in_year',
|
|
46
|
+
'GDC.time.week_in_year',
|
|
47
|
+
'GDC.time.month_in_quarter',
|
|
48
|
+
'GDC.time.month_in_year',
|
|
49
|
+
'GDC.time.quarter_in_year',
|
|
50
|
+
|
|
51
|
+
# Legacy Date Attribute Types - Possibly Obsolete
|
|
52
|
+
'GDC.time.dayOfWeek',
|
|
53
|
+
'GDC.time.dayOfMonth',
|
|
54
|
+
'GDC.time.dayOfQuarter',
|
|
55
|
+
'GDC.time.dayOfYear',
|
|
56
|
+
'GDC.time.weekOfYear',
|
|
57
|
+
'GDC.time.monthOfYear',
|
|
58
|
+
'GDC.time.quarterOfYear',
|
|
59
|
+
|
|
60
|
+
# Types for Geo
|
|
61
|
+
'GDC.geo.pin', # Geo pushpin
|
|
62
|
+
'GDC.geo.ausstates.name', # Australia States (Name)
|
|
63
|
+
'GDC.geo.ausstates.code', # Australia States (ISO code)
|
|
64
|
+
'GDC.geo.usstates.name', # US States (Name)
|
|
65
|
+
'GDC.geo.usstates.geo_id', # US States (US Census ID)
|
|
66
|
+
'GDC.geo.usstates.code', # US States (2-letter code)
|
|
67
|
+
'GDC.geo.uscounties.geo_id', # US Counties (US Census ID)
|
|
68
|
+
'GDC.geo.worldcountries.name', # World countries (Name)
|
|
69
|
+
'GDC.geo.worldcountries.iso2', # World countries (ISO a2)
|
|
70
|
+
'GDC.geo.worldcountries.iso3', # World countries (ISO a3)
|
|
71
|
+
'GDC.geo.czdistricts.name', # Czech Districts (Name)
|
|
72
|
+
'GDC.geo.czdistricts.name_no_diacritics', # Czech Districts
|
|
73
|
+
'GDC.geo.czdistricts.nuts4', # Czech Districts (NUTS 4)
|
|
74
|
+
'GDC.geo.czdistricts.knok', # Czech Districts (KNOK)
|
|
75
|
+
|
|
76
|
+
# Day Display Forms
|
|
77
|
+
'GDC.time.day', # yyyy-MM-dd
|
|
78
|
+
'GDC.time.day_us', # MM/dd/yyyy
|
|
79
|
+
'GDC.time.day_eu', # dd/MM/yyyy
|
|
80
|
+
'GDC.time.day_iso', # dd-MM-yyyy
|
|
81
|
+
'GDC.time.day_us_long', # EEE, MMM d, yyyy
|
|
82
|
+
'GDC.time.day_us_noleading', # M/d/yy
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
GD_DATA_TYPES = ['BIGINT', 'DOUBLE', 'INTEGER', 'INT', /^VARCHAR\(\d{1,3}\)$/i, /^DECIMAL\(\d{1,3},\s*\d{1,3}\)$/i]
|
|
86
|
+
|
|
87
|
+
DEFAULT_FACT_DATATYPE = 'DECIMAL(12,2)'
|
|
88
|
+
DEFAULT_ATTRIBUTE_DATATYPE = 'VARCHAR(128)'
|
|
89
|
+
|
|
90
|
+
DEFAULT_TYPE = 'GDC.text'
|
|
91
|
+
|
|
92
|
+
DEFAULT_DATE_FORMAT = 'MM/dd/yyyy'
|
|
26
93
|
|
|
27
94
|
class << self
|
|
28
95
|
def title(item)
|
|
29
|
-
item[:title] || item[:
|
|
96
|
+
item[:title] || item[:id].titleize
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def column_name(item)
|
|
100
|
+
item[:column_name] || item[:id]
|
|
30
101
|
end
|
|
31
102
|
|
|
32
103
|
def description(item)
|
|
33
104
|
item[:description]
|
|
34
105
|
end
|
|
35
106
|
|
|
36
|
-
def
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
when :primary_label
|
|
51
|
-
"label.#{dataset[:name]}.#{column[:name]}"
|
|
52
|
-
when :label
|
|
53
|
-
"label.#{dataset[:name]}.#{column[:reference]}.#{column[:name]}"
|
|
54
|
-
when :date_ref
|
|
55
|
-
"#{dataset[:name]}.date.mdyy"
|
|
56
|
-
when :dataset
|
|
57
|
-
"dataset.#{dataset[:name]}"
|
|
58
|
-
when :date
|
|
59
|
-
'DATE'
|
|
60
|
-
when :reference
|
|
61
|
-
'REF'
|
|
62
|
-
else
|
|
63
|
-
fail "Unknown type #{column[:type].to_sym}"
|
|
107
|
+
def check_gd_type(value)
|
|
108
|
+
GD_TYPES.any? { |v| v == value }
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def check_gd_data_type(value)
|
|
112
|
+
GD_DATA_TYPES.any? do |v|
|
|
113
|
+
case v
|
|
114
|
+
when Regexp
|
|
115
|
+
v =~ value
|
|
116
|
+
when String
|
|
117
|
+
v == (value && value.upcase)
|
|
118
|
+
else
|
|
119
|
+
fail 'Unkown predicate'
|
|
120
|
+
end
|
|
64
121
|
end
|
|
65
122
|
end
|
|
66
123
|
|
|
67
|
-
def
|
|
68
|
-
|
|
124
|
+
def normalize_gd_data_type(type)
|
|
125
|
+
if type && type.upcase == 'INTEGER'
|
|
126
|
+
'INT'
|
|
127
|
+
else
|
|
128
|
+
type
|
|
129
|
+
end
|
|
69
130
|
end
|
|
70
131
|
|
|
71
132
|
# Load given file into a data set described by the given schema
|
|
@@ -96,6 +157,7 @@ module GoodData
|
|
|
96
157
|
|
|
97
158
|
path = path.path if path.respond_to? :path
|
|
98
159
|
inline_data = path.is_a?(String) ? false : true
|
|
160
|
+
csv_header = nil
|
|
99
161
|
|
|
100
162
|
# create a temporary zip file
|
|
101
163
|
dir = Dir.mktmpdir
|
|
@@ -104,12 +166,14 @@ module GoodData
|
|
|
104
166
|
# TODO: make sure schema columns match CSV column names
|
|
105
167
|
zip.get_output_stream('upload_info.json') { |f| f.puts JSON.pretty_generate(manifest) }
|
|
106
168
|
if inline_data
|
|
169
|
+
csv_header = path.first
|
|
107
170
|
zip.get_output_stream('data.csv') do |f|
|
|
108
171
|
path.each do |row|
|
|
109
172
|
f.puts row.to_csv
|
|
110
173
|
end
|
|
111
174
|
end
|
|
112
175
|
else
|
|
176
|
+
csv_header = File.open(path, &:gets).split(',')
|
|
113
177
|
zip.add('data.csv', path)
|
|
114
178
|
end
|
|
115
179
|
end
|
|
@@ -122,18 +186,36 @@ module GoodData
|
|
|
122
186
|
|
|
123
187
|
# kick the load
|
|
124
188
|
pull = { 'pullIntegration' => File.basename(dir) }
|
|
125
|
-
link = project.md.links('etl')['
|
|
126
|
-
task = client.post
|
|
189
|
+
link = project.md.links('etl')['pull2']
|
|
190
|
+
task = client.post(link, pull, :info_message => "Starting the data load from user storage to dataset '#{dataset}'.")
|
|
127
191
|
|
|
128
|
-
res = client.poll_on_response(task['
|
|
129
|
-
body['
|
|
192
|
+
res = client.poll_on_response(task['pull2Task']['links']['poll'], :info_message => 'Getting status of the dataload task.') do |body|
|
|
193
|
+
body['wTaskStatus']['status'] == 'RUNNING' || body['wTaskStatus']['status'] == 'PREPARED'
|
|
130
194
|
end
|
|
131
195
|
|
|
132
|
-
if res['
|
|
196
|
+
if res['wTaskStatus']['status'] == 'ERROR' # rubocop:disable Style/GuardClause
|
|
133
197
|
s = StringIO.new
|
|
134
198
|
client.download_from_user_webdav(File.basename(dir) + '/upload_status.json', s, :client => client, :project => project)
|
|
135
199
|
js = MultiJson.load(s.string)
|
|
136
|
-
|
|
200
|
+
manifest_cols = manifest['dataSetSLIManifest']['parts'].map { |c| c['columnName'] }
|
|
201
|
+
|
|
202
|
+
# extract some human readable error message from the webdav file
|
|
203
|
+
manifest_extra = manifest_cols - csv_header
|
|
204
|
+
csv_extra = csv_header - manifest_cols
|
|
205
|
+
|
|
206
|
+
error_message = begin
|
|
207
|
+
js['error']['message'] % js['error']['parameters']
|
|
208
|
+
rescue NoMethodError, ArgumentError
|
|
209
|
+
''
|
|
210
|
+
end
|
|
211
|
+
m = "Load failed with error '#{error_message}'.\n"
|
|
212
|
+
m += "Columns that should be there (manifest) but aren't in uploaded csv: #{manifest_extra}\n" unless manifest_extra.empty?
|
|
213
|
+
m += "Columns that are in csv but shouldn't be there (manifest): #{csv_extra}\n" unless csv_extra.empty?
|
|
214
|
+
m += "Columns in the uploaded csv: #{csv_header}\n"
|
|
215
|
+
m += "Columns in the manifest: #{manifest_cols}\n"
|
|
216
|
+
m += "Original message:\n#{JSON.pretty_generate(js)}\n"
|
|
217
|
+
m += "Manifest used for uploading:\n#{JSON.pretty_generate(manifest)}"
|
|
218
|
+
fail m
|
|
137
219
|
end
|
|
138
220
|
end
|
|
139
221
|
|
|
@@ -143,8 +225,14 @@ module GoodData
|
|
|
143
225
|
d = a_schema_blueprint.deep_dup
|
|
144
226
|
d[:columns] = d[:columns] + b_schema_blueprint[:columns]
|
|
145
227
|
d[:columns].uniq!
|
|
146
|
-
columns_that_failed_to_merge = d[:columns].group_by { |x| x[:
|
|
147
|
-
|
|
228
|
+
columns_that_failed_to_merge = d[:columns].group_by { |x| [:reference, :date].include?(x[:type]) ? x[:dataset] : x[:id] }.map { |k, v| [k, v.count, v] }.select { |x| x[1] > 1 }
|
|
229
|
+
unless columns_that_failed_to_merge.empty?
|
|
230
|
+
columns_that_failed_to_merge.each do |error|
|
|
231
|
+
GoodData.logger.error "Columns #{error[0]} failed to merge. There are #{error[1]} conflicting columns. When merging columns with the same name they have to be identical."
|
|
232
|
+
GoodData.logger.error error[2]
|
|
233
|
+
end
|
|
234
|
+
fail "Columns #{columns_that_failed_to_merge.first} failed to merge. There are #{columns_that_failed_to_merge[1]} conflicting columns. #{columns_that_failed_to_merge[2]} When merging columns with the same name they have to be identical." unless columns_that_failed_to_merge.empty?
|
|
235
|
+
end
|
|
148
236
|
d
|
|
149
237
|
end
|
|
150
238
|
end
|