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.
Files changed (133) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/.travis.yml +8 -19
  4. data/Guardfile +5 -0
  5. data/README.md +1 -3
  6. data/bin/gooddata +1 -1
  7. data/gooddata.gemspec +6 -4
  8. data/lib/gooddata.rb +1 -1
  9. data/lib/gooddata/bricks/middleware/aws_middleware.rb +24 -0
  10. data/lib/gooddata/cli/commands/console_cmd.rb +1 -1
  11. data/lib/gooddata/cli/commands/project_cmd.rb +29 -9
  12. data/lib/gooddata/cli/hooks.rb +9 -3
  13. data/lib/gooddata/commands/datawarehouse.rb +1 -7
  14. data/lib/gooddata/commands/project.rb +4 -3
  15. data/lib/gooddata/core/logging.rb +14 -2
  16. data/lib/gooddata/exceptions/execution_limit_exceeded.rb +9 -0
  17. data/lib/gooddata/exceptions/uncomputable_report.rb +8 -0
  18. data/lib/gooddata/exceptions/validation_error.rb +1 -1
  19. data/lib/gooddata/goodzilla/goodzilla.rb +5 -1
  20. data/lib/gooddata/helpers/data_helper.rb +40 -9
  21. data/lib/gooddata/mixins/md_finders.rb +35 -0
  22. data/lib/gooddata/models/blueprint/anchor_field.rb +46 -0
  23. data/lib/gooddata/models/blueprint/attribute_field.rb +25 -0
  24. data/lib/gooddata/models/blueprint/blueprint.rb +7 -0
  25. data/lib/gooddata/models/blueprint/blueprint_field.rb +66 -0
  26. data/lib/gooddata/models/{dashboard_builder.rb → blueprint/dashboard_builder.rb} +0 -0
  27. data/lib/gooddata/models/{schema_blueprint.rb → blueprint/dataset_blueprint.rb} +176 -117
  28. data/lib/gooddata/models/blueprint/date_dimension.rb +10 -0
  29. data/lib/gooddata/models/blueprint/fact_field.rb +16 -0
  30. data/lib/gooddata/models/blueprint/label_field.rb +39 -0
  31. data/lib/gooddata/models/{project_blueprint.rb → blueprint/project_blueprint.rb} +366 -168
  32. data/lib/gooddata/models/blueprint/project_builder.rb +79 -0
  33. data/lib/gooddata/models/blueprint/reference_field.rb +39 -0
  34. data/lib/gooddata/models/blueprint/schema_blueprint.rb +156 -0
  35. data/lib/gooddata/models/blueprint/schema_builder.rb +85 -0
  36. data/lib/gooddata/models/{to_manifest.rb → blueprint/to_manifest.rb} +25 -20
  37. data/lib/gooddata/models/{to_wire.rb → blueprint/to_wire.rb} +33 -52
  38. data/lib/gooddata/models/datawarehouse.rb +2 -2
  39. data/lib/gooddata/models/domain.rb +3 -2
  40. data/lib/gooddata/models/execution.rb +2 -2
  41. data/lib/gooddata/models/execution_detail.rb +7 -2
  42. data/lib/gooddata/models/from_wire.rb +60 -71
  43. data/lib/gooddata/models/from_wire_parse.rb +125 -125
  44. data/lib/gooddata/models/metadata.rb +14 -0
  45. data/lib/gooddata/models/metadata/dashboard.rb +2 -2
  46. data/lib/gooddata/models/metadata/label.rb +1 -1
  47. data/lib/gooddata/models/metadata/report.rb +6 -5
  48. data/lib/gooddata/models/metadata/report_definition.rb +44 -59
  49. data/lib/gooddata/models/model.rb +131 -43
  50. data/lib/gooddata/models/process.rb +13 -11
  51. data/lib/gooddata/models/profile.rb +12 -1
  52. data/lib/gooddata/models/project.rb +223 -19
  53. data/lib/gooddata/models/project_creator.rb +4 -15
  54. data/lib/gooddata/models/schedule.rb +1 -0
  55. data/lib/gooddata/models/user_filters/user_filter_builder.rb +2 -2
  56. data/lib/gooddata/rest/client.rb +18 -18
  57. data/lib/gooddata/rest/connection.rb +113 -94
  58. data/lib/gooddata/version.rb +1 -1
  59. data/lib/templates/project/model/model.rb.erb +15 -16
  60. data/spec/data/blueprints/additional_dataset_module.json +32 -0
  61. data/spec/data/blueprints/big_blueprint_not_pruned.json +2079 -0
  62. data/spec/data/blueprints/invalid_blueprint.json +103 -0
  63. data/spec/data/blueprints/m_n_model.json +104 -0
  64. data/spec/data/blueprints/model_module.json +25 -0
  65. data/spec/data/blueprints/test_blueprint.json +38 -0
  66. data/spec/data/blueprints/test_project_model_spec.json +106 -0
  67. data/spec/data/gd_gse_data_manifest.json +34 -34
  68. data/spec/data/manifests/test_blueprint.json +32 -0
  69. data/spec/data/{manifest_test_project.json → manifests/test_project.json} +9 -18
  70. data/spec/data/wire_models/test_blueprint.json +63 -0
  71. data/spec/data/wire_test_project.json +5 -5
  72. data/spec/environment/default.rb +33 -0
  73. data/spec/environment/develop.rb +26 -0
  74. data/spec/environment/environment.rb +14 -0
  75. data/spec/environment/hotfix.rb +17 -0
  76. data/spec/environment/production.rb +31 -0
  77. data/spec/environment/release.rb +17 -0
  78. data/spec/helpers/blueprint_helper.rb +10 -7
  79. data/spec/helpers/cli_helper.rb +24 -22
  80. data/spec/helpers/connection_helper.rb +27 -25
  81. data/spec/helpers/crypto_helper.rb +7 -5
  82. data/spec/helpers/csv_helper.rb +5 -3
  83. data/spec/helpers/process_helper.rb +15 -10
  84. data/spec/helpers/project_helper.rb +40 -33
  85. data/spec/helpers/schedule_helper.rb +15 -9
  86. data/spec/helpers/spec_helper.rb +11 -0
  87. data/spec/integration/blueprint_updates_spec.rb +93 -0
  88. data/spec/integration/command_datawarehouse_spec.rb +2 -1
  89. data/spec/integration/command_projects_spec.rb +9 -8
  90. data/spec/integration/create_from_template_spec.rb +1 -1
  91. data/spec/integration/create_project_spec.rb +1 -1
  92. data/spec/integration/full_process_schedule_spec.rb +1 -1
  93. data/spec/integration/full_project_spec.rb +91 -30
  94. data/spec/integration/over_to_user_filters_spec.rb +24 -28
  95. data/spec/integration/partial_md_export_import_spec.rb +4 -4
  96. data/spec/integration/project_spec.rb +1 -1
  97. data/spec/integration/rest_spec.rb +1 -1
  98. data/spec/integration/user_filters_spec.rb +19 -24
  99. data/spec/integration/variables_spec.rb +7 -9
  100. data/spec/logging_in_logging_out_spec.rb +1 -1
  101. data/spec/spec_helper.rb +10 -1
  102. data/spec/unit/bricks/middleware/aws_middelware_spec.rb +47 -0
  103. data/spec/unit/core/connection_spec.rb +2 -2
  104. data/spec/unit/core/logging_spec.rb +12 -4
  105. data/spec/unit/helpers/data_helper_spec.rb +60 -0
  106. data/spec/unit/models/blueprint/attributes_spec.rb +24 -0
  107. data/spec/unit/models/blueprint/dataset_spec.rb +116 -0
  108. data/spec/unit/models/blueprint/labels_spec.rb +39 -0
  109. data/spec/unit/models/blueprint/project_blueprint_spec.rb +643 -0
  110. data/spec/unit/models/blueprint/reference_spec.rb +24 -0
  111. data/spec/unit/models/{schema_builder_spec.rb → blueprint/schema_builder_spec.rb} +12 -4
  112. data/spec/unit/models/blueprint/to_wire_spec.rb +169 -0
  113. data/spec/unit/models/domain_spec.rb +13 -2
  114. data/spec/unit/models/from_wire_spec.rb +277 -98
  115. data/spec/unit/models/metadata_spec.rb +22 -4
  116. data/spec/unit/models/model_spec.rb +49 -39
  117. data/spec/unit/models/profile_spec.rb +1 -0
  118. data/spec/unit/models/project_spec.rb +7 -7
  119. data/spec/unit/models/schedule_spec.rb +20 -0
  120. data/spec/unit/models/to_manifest_spec.rb +31 -11
  121. data/spec/unit/rest/polling_spec.rb +86 -0
  122. metadata +102 -30
  123. data/lib/gooddata/models/project_builder.rb +0 -136
  124. data/lib/gooddata/models/schema_builder.rb +0 -77
  125. data/out.txt +0 -0
  126. data/spec/data/additional_dataset_module.json +0 -18
  127. data/spec/data/blueprint_invalid.json +0 -38
  128. data/spec/data/m_n_model/blueprint.json +0 -76
  129. data/spec/data/model_module.json +0 -18
  130. data/spec/data/test_project_model_spec.json +0 -76
  131. data/spec/unit/models/attribute_column_spec.rb +0 -7
  132. data/spec/unit/models/project_blueprint_spec.rb +0 -239
  133. 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/#{GoodData.project.pid}/clientexport"
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
- GoodData.poll_on_code(x['asyncTask']['link']['poll'], process: false)
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 "Element id #{element_id} was not found"
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 = GoodData::Project[p, options]
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
- GoodData.poll_on_code(result1['uri'], process: false)
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
- rd = GoodData::ReportDefinition.create(options)
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 = { :client => GoodData.connection, :project => GoodData.project })
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
- ReportDefinition.execute_inline(self, opts)
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
- ReportDefinition.data_result(result, opts)
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
- GD_TYPES = %w(GDC.link GDC.text GDC.geo GDC.time)
23
- GD_DATA_TYPES = %w(INT VARCHAR DECIMAL)
24
-
25
- DEFAULT_FACT_DATATYPE = 'INT'
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[:name].titleize
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 identifier_for(dataset, column = nil, column2 = nil) # rubocop:disable UnusedMethodArgument
37
- return "dataset.#{dataset[:name]}" if column.nil?
38
- column = DatasetBlueprint.find_column_by_name(dataset, column) if column.is_a?(String)
39
- case column[:type].to_sym
40
- when :anchor_no_label
41
- "attr.#{dataset[:name]}.factsof"
42
- when :attribute
43
- "attr.#{dataset[:name]}.#{column[:name]}"
44
- when :anchor
45
- "attr.#{dataset[:name]}.#{column[:name]}"
46
- when :date_fact
47
- "dt.#{dataset[:name]}.#{column[:name]}"
48
- when :fact
49
- "fact.#{dataset[:name]}.#{column[:name]}"
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 check_gd_datatype(value)
68
- GD_TYPES.any? { |v| v == value }
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')['pull']
126
- task = client.post link, pull
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['pullTask']['uri']) do |body|
129
- body['taskStatus'] == 'RUNNING' || body['taskStatus'] == 'PREPARED'
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['taskStatus'] == 'ERROR' # rubocop:disable Style/GuardClause
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
- fail "Load Failed with error #{JSON.pretty_generate(js)}"
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[:name] }.map { |k, v| [k, v.count] }.select { |x| x[1] > 1 }
147
- 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?
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