gooddata 0.6.53 → 0.6.54

Sign up to get free protection for your applications and to get access to all the features.
Files changed (139) hide show
  1. checksums.yaml +5 -5
  2. data/.flayignore +6 -0
  3. data/.gitignore +1 -0
  4. data/.pronto.yml +3 -0
  5. data/.rspec +2 -0
  6. data/.rubocop.yml +4 -1
  7. data/CHANGELOG.md +18 -0
  8. data/CONTRIBUTING.md +14 -1
  9. data/DEPENDENCIES.md +324 -253
  10. data/Dockerfile.jruby +5 -7
  11. data/Dockerfile.ruby +8 -8
  12. data/Rakefile +24 -0
  13. data/ci.rake +47 -0
  14. data/docker-compose.yml +34 -0
  15. data/gooddata.gemspec +8 -2
  16. data/lib/gooddata/bricks/middleware/restforce_middleware.rb +0 -3
  17. data/lib/gooddata/helpers/data_helper.rb +10 -7
  18. data/lib/gooddata/helpers/global_helpers_params.rb +8 -3
  19. data/lib/gooddata/lcm/actions/apply_custom_maql.rb +2 -1
  20. data/lib/gooddata/lcm/actions/associate_clients.rb +10 -1
  21. data/lib/gooddata/lcm/actions/collect_client_projects.rb +78 -0
  22. data/lib/gooddata/lcm/actions/collect_clients.rb +20 -6
  23. data/lib/gooddata/lcm/actions/collect_data_product.rb +62 -0
  24. data/lib/gooddata/lcm/actions/collect_dynamic_schedule_params.rb +62 -0
  25. data/lib/gooddata/lcm/actions/{collect_attrs.rb → collect_ldm_objects.rb} +3 -3
  26. data/lib/gooddata/lcm/actions/collect_meta.rb +6 -3
  27. data/lib/gooddata/lcm/actions/collect_segment_clients.rb +2 -1
  28. data/lib/gooddata/lcm/actions/collect_segments.rb +6 -7
  29. data/lib/gooddata/lcm/actions/collect_tagged_objects.rb +7 -4
  30. data/lib/gooddata/lcm/actions/create_segment_masters.rb +7 -3
  31. data/lib/gooddata/lcm/actions/ensure_data_product.rb +53 -0
  32. data/lib/gooddata/lcm/actions/ensure_technical_users_domain.rb +6 -2
  33. data/lib/gooddata/lcm/actions/ensure_technical_users_project.rb +30 -18
  34. data/lib/gooddata/lcm/actions/execute_schedules.rb +128 -0
  35. data/lib/gooddata/lcm/actions/provision_clients.rb +32 -21
  36. data/lib/gooddata/lcm/actions/purge_clients.rb +25 -39
  37. data/lib/gooddata/lcm/actions/rename_existing_client_projects.rb +70 -0
  38. data/lib/gooddata/lcm/actions/segments_filter.rb +6 -0
  39. data/lib/gooddata/lcm/actions/synchronize_cas.rb +11 -0
  40. data/lib/gooddata/lcm/actions/synchronize_clients.rb +2 -1
  41. data/lib/gooddata/lcm/actions/synchronize_etls_in_segment.rb +34 -15
  42. data/lib/gooddata/lcm/actions/synchronize_ldm.rb +10 -1
  43. data/lib/gooddata/lcm/actions/synchronize_new_segments.rb +2 -1
  44. data/lib/gooddata/lcm/actions/synchronize_processes.rb +4 -7
  45. data/lib/gooddata/lcm/actions/synchronize_tag_objects.rb +8 -5
  46. data/lib/gooddata/lcm/actions/synchronize_user_filters.rb +224 -0
  47. data/lib/gooddata/lcm/actions/synchronize_user_groups.rb +53 -0
  48. data/lib/gooddata/lcm/actions/synchronize_users.rb +324 -0
  49. data/lib/gooddata/lcm/dsl/type_dsl.rb +1 -0
  50. data/lib/gooddata/lcm/helpers/check_helper.rb +4 -0
  51. data/lib/gooddata/lcm/helpers/tags_helper.rb +4 -3
  52. data/lib/gooddata/lcm/lcm2.rb +33 -1
  53. data/lib/gooddata/lcm/types/complex/segment.rb +3 -0
  54. data/lib/gooddata/lcm/types/complex/update_preference.rb +8 -2
  55. data/lib/gooddata/lcm/types/special/array.rb +1 -3
  56. data/lib/gooddata/lcm/types/special/enum.rb +1 -3
  57. data/lib/gooddata/mixins/md_id_to_uri.rb +0 -1
  58. data/lib/gooddata/mixins/md_json.rb +2 -2
  59. data/lib/gooddata/models/blueprint/project_blueprint.rb +15 -0
  60. data/lib/gooddata/models/blueprint/to_wire.rb +1 -0
  61. data/lib/gooddata/models/client.rb +21 -9
  62. data/lib/gooddata/models/data_product.rb +149 -0
  63. data/lib/gooddata/models/domain.rb +26 -72
  64. data/lib/gooddata/models/from_wire.rb +2 -0
  65. data/lib/gooddata/models/metadata/report.rb +9 -3
  66. data/lib/gooddata/models/metadata/report_definition.rb +2 -2
  67. data/lib/gooddata/models/model.rb +1 -1
  68. data/lib/gooddata/models/process.rb +4 -0
  69. data/lib/gooddata/models/project.rb +58 -35
  70. data/lib/gooddata/models/project_creator.rb +13 -0
  71. data/lib/gooddata/models/segment.rb +63 -16
  72. data/lib/gooddata/models/style_setting.rb +2 -15
  73. data/lib/gooddata/models/user_group.rb +2 -0
  74. data/lib/gooddata/rest/connection.rb +32 -9
  75. data/lib/gooddata/rest/object_factory.rb +0 -25
  76. data/lib/gooddata/version.rb +1 -1
  77. data/spec/data/blueprints/invalid_blueprint.json +2 -2
  78. data/spec/data/blueprints/test_project_model_spec.json +1 -1
  79. data/spec/data/dynamic_schedule_params_table.csv +7 -0
  80. data/spec/data/workspace_table.csv +3 -3
  81. data/spec/environment/staging.rb +3 -3
  82. data/spec/integration/ads_output_stage_spec.rb +0 -10
  83. data/spec/integration/clients_spec.rb +1 -1
  84. data/spec/{unit → integration}/commands/command_projects_spec.rb +0 -0
  85. data/spec/{unit → integration}/core/connection_spec.rb +0 -0
  86. data/spec/{unit → integration}/core/logging_spec.rb +0 -0
  87. data/spec/{unit → integration}/core/project_spec.rb +0 -0
  88. data/spec/integration/date_dim_switch_spec.rb +13 -0
  89. data/spec/integration/full_process_schedule_spec.rb +2 -2
  90. data/spec/integration/helpers_spec.rb +16 -0
  91. data/spec/integration/lcm_spec.rb +12 -2
  92. data/spec/integration/mixins/id_to_uri_spec.rb +44 -0
  93. data/spec/integration/models/data_product_spec.rb +71 -0
  94. data/spec/{unit → integration}/models/domain_spec.rb +2 -2
  95. data/spec/{unit → integration}/models/invitation_spec.rb +0 -0
  96. data/spec/{unit → integration}/models/membership_spec.rb +0 -0
  97. data/spec/{unit → integration}/models/params_spec.rb +0 -0
  98. data/spec/{unit → integration}/models/profile_spec.rb +0 -0
  99. data/spec/{unit → integration}/models/project_role_spec.rb +0 -0
  100. data/spec/integration/models/project_spec.rb +225 -0
  101. data/spec/{unit → integration}/models/schedule_spec.rb +0 -0
  102. data/spec/{unit → integration}/models/unit_project_spec.rb +0 -0
  103. data/spec/integration/project_spec.rb +40 -5
  104. data/spec/integration/segments_spec.rb +27 -26
  105. data/spec/integration/user_filters_spec.rb +1 -1
  106. data/spec/spec_helper.rb +15 -19
  107. data/spec/unit/actions/associate_clients_spec.rb +47 -0
  108. data/spec/unit/actions/collect_client_projects_spec.rb +47 -0
  109. data/spec/unit/actions/collect_clients_spec.rb +27 -0
  110. data/spec/unit/actions/collect_data_product_spec.rb +64 -0
  111. data/spec/unit/actions/collect_dynamic_schedule_params_spec.rb +56 -0
  112. data/spec/unit/actions/collect_meta_spec.rb +4 -4
  113. data/spec/unit/actions/collect_segment_clients_spec.rb +44 -3
  114. data/spec/unit/actions/collect_tagged_objects_spec.rb +20 -4
  115. data/spec/unit/actions/create_segment_masters_spec.rb +64 -0
  116. data/spec/unit/actions/ensure_data_product_spec.rb +38 -0
  117. data/spec/unit/actions/ensure_technical_users_domain_spec.rb +51 -0
  118. data/spec/unit/actions/ensure_technical_users_project_spec.rb +72 -0
  119. data/spec/unit/actions/execute_schedules_spec.rb +94 -0
  120. data/spec/unit/actions/provision_clients_spec.rb +45 -0
  121. data/spec/unit/actions/purge_clients_spec.rb +47 -0
  122. data/spec/unit/actions/rename_existing_client_projects_spec.rb +54 -0
  123. data/spec/unit/actions/segments_filter_spec.rb +46 -0
  124. data/spec/unit/actions/shared_examples_for_user_actions.rb +10 -0
  125. data/spec/unit/actions/synchronize_cas_spec.rb +58 -0
  126. data/spec/unit/actions/synchronize_etls_in_segment_spec.rb +174 -13
  127. data/spec/unit/actions/synchronize_ldm_spec.rb +57 -0
  128. data/spec/unit/actions/synchronize_user_filters_spec.rb +142 -0
  129. data/spec/unit/actions/synchronize_user_groups_spec.rb +49 -0
  130. data/spec/unit/actions/synchronize_users_spec.rb +76 -0
  131. data/spec/unit/helpers/data_helper_spec.rb +17 -0
  132. data/spec/unit/helpers/global_helpers_spec.rb +16 -0
  133. data/spec/unit/helpers_spec.rb +0 -6
  134. data/spec/unit/models/blueprint/project_blueprint_spec.rb +21 -4
  135. data/spec/unit/models/project_creator_spec.rb +16 -0
  136. data/spec/unit/models/project_spec.rb +66 -197
  137. metadata +202 -100
  138. data/PULL_REQUEST_TEMPLATE.md +0 -5
  139. data/lib/gooddata/bricks/middleware/params_inspect_middleware.rb +0 -21
@@ -14,8 +14,6 @@ module GoodData
14
14
  class Domain < Rest::Resource
15
15
  attr_reader :name
16
16
 
17
- ProvisioningResult = Struct.new('ProvisioningResult', :id, :status, :project_uri, :error)
18
-
19
17
  USER_LANGUAGES = {
20
18
  'en-US' => 'English',
21
19
  'nl-NL' => 'Dutch',
@@ -95,6 +93,10 @@ module GoodData
95
93
  tmp = user_data[:timezone]
96
94
  data[:timezone] = tmp if tmp && !tmp.empty?
97
95
 
96
+ # Optional ip whitelist
97
+ tmp = user_data[:ip_whitelist]
98
+ data[:ipWhitelist] = tmp if tmp
99
+
98
100
  c = client(opts)
99
101
 
100
102
  # TODO: It will be nice if the API will return us user just newly created
@@ -323,19 +325,11 @@ Available values for setting language are: #{available_languages}."
323
325
  # if it exists.
324
326
  #
325
327
  # @param id [String] Id of client that you are looking for
328
+ # @param data_product [DataProduct] data product object in which the clients are located
326
329
  # @return [Object] Raw response
327
330
  #
328
- def clients(id = :all)
329
- if id == :all
330
- res = client.get("/gdc/domains/#{name}/clients")
331
- res_clients = (res['clients'] && res['clients']['items']) || []
332
- res_clients.map { |res_client| client.create(GoodData::Client, res_client) }
333
- else
334
- res = client.get("/gdc/domains/#{name}/clients/#{id}")
335
- error = GoodData::Helpers.interpolate_error_message(res)
336
- raise error if error
337
- client.create(GoodData::Client, res)
338
- end
331
+ def clients(id = :all, data_product = nil)
332
+ GoodData::Client[id, data_product: data_product, domain: self]
339
333
  end
340
334
 
341
335
  alias_method :create_user, :add_user
@@ -344,8 +338,17 @@ Available values for setting language are: #{available_languages}."
344
338
  GoodData::Domain.create_users(list, name, { client: client }.merge(options))
345
339
  end
346
340
 
347
- def segments(id = :all)
348
- GoodData::Segment[id, domain: self]
341
+ def data_products(id = :all)
342
+ GoodData::DataProduct[id, domain: self]
343
+ end
344
+
345
+ def create_data_product(data)
346
+ data_product = GoodData::DataProduct.create(data, domain: self, client: client)
347
+ data_product.save
348
+ end
349
+
350
+ def segments(id = :all, data_product = nil)
351
+ GoodData::Segment[id, data_product: data_product, domain: self]
349
352
  end
350
353
 
351
354
  # Creates new segment in current domain from parameters passed
@@ -429,29 +432,10 @@ Available values for setting language are: #{available_languages}."
429
432
  #
430
433
  # @return [Enumerator] Returns Enumerator of results
431
434
  def provision_client_projects(segments = nil)
432
- body = if segments
433
- {
434
- provisionClientProjects: {
435
- segments: segments.is_a?(Array) ? segments : [segments]
436
- }
437
- }
438
- end
439
-
440
- res = client.post(segments_uri + '/provisionClientProjects', body)
441
- res = client.poll_on_code(res['asyncTask']['links']['poll'])
442
- failed_count = GoodData::Helpers.get_path(res, %w(clientProjectProvisioningResult failed count), 0)
443
- created_count = GoodData::Helpers.get_path(res, %w(clientProjectProvisioningResult created count), 0)
444
- return Enumerator.new([]) if failed_count + created_count == 0 # rubocop:disable Style/NumericPredicate
445
- Enumerator.new do |y|
446
- uri = GoodData::Helpers.get_path(res, %w(clientProjectProvisioningResult links details))
447
- loop do
448
- result = client.get(uri)
449
- (GoodData::Helpers.get_path(result, %w(clientProjectProvisioningResultDetails items)) || []).each do |item|
450
- y << ProvisioningResult.new(item['id'], item['status'], item['project'], item['error'])
451
- end
452
- uri = GoodData::Helpers.get_path(res, %w(clientProjectProvisioningResultDetails paging next))
453
- break if uri.nil?
454
- end
435
+ segments = segments.is_a?(Array) ? segments : [segments]
436
+ segments.each do |segment_id|
437
+ segment = segments(segment_id)
438
+ segment.provision_client_projects
455
439
  end
456
440
  end
457
441
 
@@ -460,7 +444,7 @@ Available values for setting language are: #{available_languages}."
460
444
  client_id = datum[:id]
461
445
  settings = datum[:settings]
462
446
  settings.each do |setting|
463
- GoodData::Client.update_setting(setting[:name], setting[:value], domain: self, client_id: client_id)
447
+ GoodData::Client.update_setting(setting[:name], setting[:value], domain: self, client_id: client_id, data_product_id: datum[:data_product_id])
464
448
  end
465
449
  end
466
450
  nil
@@ -468,39 +452,9 @@ Available values for setting language are: #{available_languages}."
468
452
  alias_method :add_clients_settings, :update_clients_settings
469
453
 
470
454
  def update_clients(data, options = {})
471
- if options[:delete_extra] && options[:delete_extra_in_segments]
472
- fail 'Options delete_extra and delete_extra_in_segments are mutually exclusive.'
473
- end
474
-
475
- delete_projects = options[:delete_projects] == false ? false : true
476
- payload = data.map do |datum|
477
- {
478
- :client => {
479
- :id => datum[:id],
480
- :segment => segments_uri + '/segments/' + datum[:segment]
481
- }
482
- }.tap do |h|
483
- h[:client][:project] = datum[:project] if datum.key?(:project)
484
- end
485
- end
486
- if options[:delete_extra] == true
487
- res = client.post(segments_uri + '/updateClients?deleteExtra=true', updateClients: { items: payload })
488
- elsif options[:delete_extra_in_segments]
489
- segments_to_delete_in = options[:delete_extra_in_segments]
490
- .map { |segment| CGI.escape(segment) }
491
- .join(',')
492
- uri = segments_uri + "/updateClients?deleteExtraInSegments=#{segments_to_delete_in}"
493
- res = client.post(uri, updateClients: { items: payload })
494
- else
495
- res = client.post(segments_uri + '/updateClients', updateClients: { items: payload })
496
- end
497
- data = GoodData::Helpers.get_path(res, ['updateClientsResponse'])
498
- if data
499
- result = data.flat_map { |k, v| v.map { |h| GoodData::Helpers.symbolize_keys(h.merge('type' => k)) } }
500
- result.select { |r| r[:status] == 'DELETED' }.peach { |r| r[:originalProject] && client.delete(r[:originalProject]) } if delete_projects
501
- result
502
- else
503
- []
455
+ data.group_by(&:data_product_id).each do |data_product_id, client_update_data|
456
+ data_product = data_products(data_product_id)
457
+ data_product.update_clients(client_update_data, options)
504
458
  end
505
459
  end
506
460
 
@@ -108,6 +108,7 @@ module GoodData
108
108
  d[:id] = date_dim['dateDimension']['name']
109
109
  d[:title] = date_dim['dateDimension']['title']
110
110
  d[:urn] = date_dim['dateDimension']['urn']
111
+ d[:identifier_prefix] = date_dim['dateDimension']['identifierPrefix']
111
112
  end
112
113
  end
113
114
 
@@ -125,6 +126,7 @@ module GoodData
125
126
  f[:description] = fact['fact']['description'] if fact['fact']['description']
126
127
  f[:folder] = fact['fact']['folder']
127
128
  f[:gd_data_type] = fact['fact']['dataType'] || GoodData::Model::DEFAULT_FACT_DATATYPE
129
+ f[:restricted] = fact['fact']['restricted'] if fact['fact']['restricted']
128
130
  end
129
131
  end
130
132
  end
@@ -72,7 +72,7 @@ module GoodData
72
72
  end
73
73
  end
74
74
 
75
- if result.empty?
75
+ if result.to_s.empty?
76
76
  ReportDataResult.new(data: [], top: 0, left: 0)
77
77
  else
78
78
  ReportDataResult.from_xtab(result)
@@ -144,10 +144,16 @@ module GoodData
144
144
 
145
145
  # Computes the report and returns the result. If it is not computable returns nil.
146
146
  #
147
+ # @option options [Time] :time Force the platform to simutale the result at this time
147
148
  # @return [GoodData::DataResult] Returns the result
148
149
  def execute(options = {})
150
+ time = options[:time]
151
+
152
+ report_req = { 'report' => uri }
153
+ report_req['timestamp'] = time.to_i if time
154
+
149
155
  fail 'You have to save the report before executing. If you do not want to do that please use GoodData::ReportDefinition' unless saved?
150
- result = client.post '/gdc/xtab2/executor3', 'report_req' => { 'report' => uri }
156
+ result = client.post "/gdc/projects/#{project.pid}/execute", 'report_req' => report_req
151
157
  GoodData::Report.data_result(result, options.merge(client: client))
152
158
  end
153
159
 
@@ -163,7 +169,7 @@ module GoodData
163
169
  #
164
170
  # @return [String] Returns data
165
171
  def export(format, options = {})
166
- result = client.post('/gdc/xtab2/executor3', 'report_req' => { 'report' => uri })
172
+ result = client.post("/gdc/projects/#{project.pid}/execute", 'report_req' => { 'report' => uri })
167
173
  result1 = client.post('/gdc/exporter/executor', :result_req => { :format => format, :result => result })
168
174
  client.poll_on_code(result1['uri'], options.merge(process: false))
169
175
  end
@@ -228,7 +228,7 @@ module GoodData
228
228
  pars = {
229
229
  'report_req' => { 'reportDefinition' => uri }
230
230
  }
231
- client.post '/gdc/xtab2/executor3', pars
231
+ client.post "/gdc/projects/#{project.pid}/execute", pars
232
232
  else
233
233
  data = {
234
234
  report_req: {
@@ -238,7 +238,7 @@ module GoodData
238
238
  }
239
239
  }
240
240
  }
241
- uri = "/gdc/app/projects/#{project.pid}/execute"
241
+ uri = "/gdc/projects/#{project.pid}/execute"
242
242
  client.post(uri, data)
243
243
  end
244
244
  GoodData::Report.data_result(result, opts.merge(client: client))
@@ -86,7 +86,7 @@ module GoodData
86
86
  'GDC.time.day_us_noleading', # M/d/yy
87
87
  ]
88
88
 
89
- GD_DATA_TYPES = ['BIGINT', 'DOUBLE', 'INTEGER', 'INT', /^VARCHAR\(\d{1,4}\)$/i, /^DECIMAL\(\d{1,3},\s*\d{1,3}\)$/i]
89
+ GD_DATA_TYPES = ['BIGINT', 'DOUBLE', 'INTEGER', 'INT', /^VARCHAR\(([1-9]\d{0,3}|10000)\)$/i, /^DECIMAL\(\d{1,3},\s*\d{1,3}\)$/i]
90
90
 
91
91
  DEFAULT_FACT_DATATYPE = 'DECIMAL(12,2)'
92
92
  DEFAULT_ATTRIBUTE_DATATYPE = 'VARCHAR(128)'
@@ -125,6 +125,10 @@ module GoodData
125
125
 
126
126
  File.delete(deployed_path) if File.exist?(deployed_path)
127
127
 
128
+ if res['asyncTask']
129
+ res = client.poll_on_response(res['asyncTask']['links']['poll']) { |body| body['asyncTask'] }
130
+ end
131
+
128
132
  process = client.create(Process, res, project: project)
129
133
  puts HighLine.color("Deploy DONE #{path}", HighLine::GREEN) if verbose
130
134
  process
@@ -256,39 +256,60 @@ module GoodData
256
256
  def transfer_processes(from_project, to_project, options = {})
257
257
  options = GoodData::Helpers.symbolize_keys(options)
258
258
  to_project_processes = to_project.processes
259
- from_project.processes.uniq(&:name).each do |process|
259
+ result = from_project.processes.uniq(&:name).map do |process|
260
260
  fail "The process name #{process.name} must be unique in transfered project #{to_project}" if to_project_processes.count { |p| p.name == process.name } > 1
261
+ next if process.type == :dataload
261
262
  to_process = to_project_processes.find { |p| p.name == process.name }
262
263
 
263
- if process.path
264
- to_process.delete if to_process
265
- GoodData::Process.deploy_from_appstore(process.path, name: process.name, client: to_project.client, project: to_project)
266
- elsif process.type != :dataload
267
- Dir.mktmpdir('etl_transfer') do |dir|
268
- dir = Pathname(dir)
269
- filename = dir + 'process.zip'
270
- File.open(filename, 'w') do |f|
271
- f << process.download
272
- end
273
-
274
- to_process ? to_process.deploy(filename, type: process.type, name: process.name) : to_project.deploy_process(filename, type: process.type, name: process.name)
275
- end
276
- end
264
+ to_process = if process.path
265
+ to_process.delete if to_process
266
+ GoodData::Process.deploy_from_appstore(process.path, name: process.name, client: to_project.client, project: to_project)
267
+ else
268
+ Dir.mktmpdir('etl_transfer') do |dir|
269
+ dir = Pathname(dir)
270
+ filename = dir + 'process.zip'
271
+ File.open(filename, 'w') do |f|
272
+ f << process.download
273
+ end
274
+
275
+ if to_process
276
+ to_process.deploy(filename, type: process.type, name: process.name)
277
+ else
278
+ to_project.deploy_process(filename, type: process.type, name: process.name)
279
+ end
280
+ end
281
+ end
282
+
283
+ {
284
+ from: from_project.pid,
285
+ to: to_project.pid,
286
+ name: process.name,
287
+ status: to_process ? 'successful' : 'failed'
288
+ }
277
289
  end
278
290
 
279
291
  transfer_output_stage(from_project, to_project, options)
292
+ result << {
293
+ from: from_project.pid,
294
+ to: to_project.pid,
295
+ name: 'Automated Data Distribution',
296
+ status: 'successful'
297
+ }
280
298
 
281
299
  res = (from_project.processes + to_project.processes).map { |p| [p, p.name, p.type] }
282
300
  res.group_by { |x| [x[1], x[2]] }
283
301
  .select { |_, procs| procs.length == 1 && procs[2] != :dataload }
284
302
  .flat_map { |_, procs| procs.select { |p| p[0].project.pid == to_project.pid }.map { |p| p[0] } }
285
303
  .peach(&:delete)
304
+
305
+ result.compact
286
306
  end
287
307
 
288
308
  def transfer_user_groups(from_project, to_project)
289
- from_project.user_groups.each do |ug|
309
+ from_project.user_groups.map do |ug|
290
310
  # migrate groups
291
311
  new_group = to_project.user_groups.select { |group| group.name == ug.name }.first
312
+ new_group_status = new_group ? 'modified' : 'created'
292
313
  new_group ||= UserGroup.create(:name => ug.name, :description => ug.description, :project => to_project)
293
314
  new_group.project = to_project
294
315
  new_group.description = ug.description
@@ -303,6 +324,13 @@ module GoodData
303
324
  permission = grantee['aclEntryURI']['permission']
304
325
  new_dashboard.grant(:member => new_group, :permission => permission)
305
326
  end
327
+
328
+ {
329
+ from: from_project.pid,
330
+ to: to_project.pid,
331
+ user_group: new_group.name,
332
+ status: new_group_status
333
+ }
306
334
  end
307
335
  end
308
336
 
@@ -314,9 +342,14 @@ module GoodData
314
342
  # the master, client in which case we take its project, string in which
315
343
  # case we treat is as an project object or directly project.
316
344
  def transfer_schedules(from_project, to_project)
317
- cache = to_project
318
- .processes.sort_by(&:name)
319
- .zip(from_project.processes.sort_by(&:name))
345
+ to_project_processes = to_project.processes.sort_by(&:name)
346
+ from_project_processes = from_project.processes.sort_by(&:name)
347
+
348
+ GoodData.logger.debug("Processes in from project #{from_project.pid}: #{from_project_processes.map(&:name).join(', ')}")
349
+ GoodData.logger.debug("Processes in to project #{to_project.pid}: #{to_project_processes.map(&:name).join(', ')}")
350
+
351
+ cache = to_project_processes
352
+ .zip(from_project_processes)
320
353
  .flat_map do |remote, local|
321
354
  local.schedules.map do |schedule|
322
355
  [remote, local, schedule]
@@ -574,10 +607,11 @@ module GoodData
574
607
  #
575
608
  # @return [GoodData::ProjectRole] Project role if found
576
609
  def blueprint(options = {})
577
- result = client.get("/gdc/projects/#{pid}/model/view", params: { includeDeprecated: true, includeGrain: true, includeCA: true })
610
+ options = { include_ca: true }.merge(options)
611
+ result = client.get("/gdc/projects/#{pid}/model/view", params: { includeDeprecated: true, includeGrain: true, includeCA: options[:include_ca] })
578
612
  polling_url = result['asyncTask']['link']['poll']
579
613
  model = client.poll_on_code(polling_url, options)
580
- bp = GoodData::Model::FromWire.from_wire(model, { include_ca: true }.merge(options))
614
+ bp = GoodData::Model::FromWire.from_wire(model, options)
581
615
  bp.title = title
582
616
  bp
583
617
  end
@@ -730,6 +764,7 @@ module GoodData
730
764
  def data_permissions(id = :all)
731
765
  GoodData::MandatoryUserFilter[id, client: client, project: self]
732
766
  end
767
+ alias_method :user_filters, :data_permissions
733
768
 
734
769
  # Deletes project
735
770
  def delete
@@ -1075,7 +1110,7 @@ module GoodData
1075
1110
  # @param [String] key key of the value to be stored
1076
1111
  # @return [String] val value to be stored
1077
1112
  def set_metadata(key, val)
1078
- GoodData::ProjectMetadata[key, client: client, project: self] = val
1113
+ GoodData::ProjectMetadata.[]=(key, { client: client, project: self }, val)
1079
1114
  end
1080
1115
 
1081
1116
  # Helper for getting metrics of a project
@@ -1185,7 +1220,7 @@ module GoodData
1185
1220
  :partialMDImport => {
1186
1221
  :token => token,
1187
1222
  :overwriteNewer => '1',
1188
- :updateLDMObjects => '0',
1223
+ :updateLDMObjects => '1',
1189
1224
  :importAttributeProperties => '1'
1190
1225
  }
1191
1226
  }
@@ -1512,18 +1547,6 @@ module GoodData
1512
1547
  data['links']['self'] if data && data['links'] && data['links']['self']
1513
1548
  end
1514
1549
 
1515
- # List of user filters within this project
1516
- #
1517
- # @return [Array<GoodData::MandatoryUserFilter>] List of mandatory user
1518
- def user_filters
1519
- url = "/gdc/md/#{pid}/userfilters"
1520
-
1521
- tmp = client.get(url)
1522
- tmp['userFilters']['items'].pmap do |filter|
1523
- client.create(GoodData::MandatoryUserFilter, filter, project: self)
1524
- end
1525
- end
1526
-
1527
1550
  # List of users in project
1528
1551
  #
1529
1552
  #
@@ -127,6 +127,19 @@ module GoodData
127
127
  preference = GoodData::Helpers.symbolize_keys(opts[:update_preference] || {})
128
128
  preference = Hash[preference.map { |k, v| [k, GoodData::Helpers.to_boolean(v)] }]
129
129
 
130
+ # will use new parameters instead of the old ones
131
+ if preference.empty? || [:allow_cascade_drops, :keep_data].any? { |k| preference.key?(k) }
132
+ if [:cascade_drops, :preserve_data].any? { |k| preference.key?(k) }
133
+ fail "Please do not mix old parameters (:cascade_drops, :preserve_data) with the new ones (:allow_cascade_drops, :keep_data)."
134
+ end
135
+ preference = { allow_cascade_drops: false, keep_data: true }.merge(preference)
136
+
137
+ new_preference = {}
138
+ new_preference[:cascade_drops] = false unless preference[:allow_cascade_drops]
139
+ new_preference[:preserve_data] = true if preference[:keep_data]
140
+ preference = new_preference
141
+ end
142
+
130
143
  # first is cascadeDrops, second is preserveData
131
144
  rules = [
132
145
  { priority: 1, cascade_drops: false, preserve_data: true },
@@ -15,10 +15,15 @@ require_relative '../mixins/uri_getter'
15
15
 
16
16
  module GoodData
17
17
  class Segment < Rest::Resource
18
- SYNCHRONIZE_URI = '/gdc/domains/%s/segments/%s/synchronizeClients'
18
+ SYNCHRONIZE_URI = '/gdc/domains/%s/dataproducts/%s/segments/%s/synchronizeClients'
19
+
20
+ #
21
+ ProvisioningResult = Struct.new('ProvisioningResult', :id, :status, :project_uri, :error)
19
22
 
20
23
  attr_accessor :domain
21
24
 
25
+ attr_writer :data_product
26
+
22
27
  data_property_reader 'id'
23
28
 
24
29
  include Mixin::Links
@@ -43,10 +48,12 @@ module GoodData
43
48
  client = domain.client
44
49
  fail ArgumentError, 'No client specified' if client.nil?
45
50
 
51
+ data_product = opts[:data_product]
52
+
46
53
  if id == :all
47
54
  GoodData::Segment.all(opts)
48
55
  else
49
- result = client.get(domain.segments_uri + "/segments/#{CGI.escape(id)}")
56
+ result = client.get(base_uri(domain, data_product) + "/segments/#{CGI.escape(id)}")
50
57
  client.create(GoodData::Segment, result.merge('domain' => domain))
51
58
  end
52
59
  end
@@ -60,8 +67,17 @@ module GoodData
60
67
  fail 'Domain has to be passed in options' unless domain
61
68
  client = domain.client
62
69
 
63
- results = client.get(domain.segments_uri + '/segments')
64
- GoodData::Helpers.get_path(results, %w(segments items)).map { |i| client.create(GoodData::Segment, i.merge('domain' => domain)) }
70
+ data_product = opts[:data_product]
71
+ results = client.get(base_uri(domain, data_product) + '/segments')
72
+ GoodData::Helpers.get_path(results, %w(segments items)).map { |i| client.create(GoodData::Segment, i, domain: domain) }
73
+ end
74
+
75
+ def base_uri(domain, data_product)
76
+ if data_product
77
+ GoodData::DataProduct::ONE_DATA_PRODUCT_PATH % { domain_name: domain.name, id: data_product.data_product_id }
78
+ else
79
+ domain.segments_uri
80
+ end
65
81
  end
66
82
 
67
83
  # Creates new segment from parameters passed
@@ -71,9 +87,9 @@ module GoodData
71
87
  # @return [GoodData::Segment] New Segment instance
72
88
  def create(data = {}, options = {})
73
89
  segment_id = data[:segment_id]
74
- fail 'Custom ID has to be provided' if segment_id.blank?
90
+ fail 'segment_id has to be provided' if segment_id.blank?
75
91
  client = options[:client]
76
- segment = client.create(GoodData::Segment, GoodData::Helpers.stringify_keys(SEGMENT_TEMPLATE).merge('domain' => options[:domain]))
92
+ segment = client.create(GoodData::Segment, GoodData::Helpers.stringify_keys(SEGMENT_TEMPLATE), options)
77
93
  segment.tap do |s|
78
94
  s.segment_id = segment_id
79
95
  s.master_project = data[:master_project]
@@ -84,9 +100,19 @@ module GoodData
84
100
  def initialize(data)
85
101
  super
86
102
  @domain = data.delete('domain')
103
+ @data_product = nil
87
104
  @json = data
88
105
  end
89
106
 
107
+ def data_product
108
+ if @data_product
109
+ @data_product
110
+ else
111
+ json = client.get(data['links']['dataProduct'])
112
+ @data_product = client.create(GoodData::DataProduct, json)
113
+ end
114
+ end
115
+
90
116
  # Segment id getter for the Segment. Called segment_id since id is a reserved word in ruby world
91
117
  #
92
118
  # @return [String] Segment id
@@ -159,17 +185,17 @@ module GoodData
159
185
  if uri
160
186
  client.put(uri, json)
161
187
  else
162
- res = client.post(domain.segments_uri + '/segments', json)
188
+ res = client.post(self.class.base_uri(domain, @data_product ? data_product : nil) + '/segments', json)
163
189
  @json = res
164
190
  end
165
191
  self
166
192
  end
167
193
 
168
- # Runs async process that walks thorugh segments and provisions projects if necessary.
194
+ # Runs async process that walks through segments and provisions projects if necessary.
169
195
  #
170
196
  # @return [Array] Returns array of results
171
197
  def synchronize_clients
172
- sync_uri = SYNCHRONIZE_URI % [domain.obj_id, id]
198
+ sync_uri = SYNCHRONIZE_URI % [domain.obj_id, data_product.data_product_id, id]
173
199
  res = client.post sync_uri, nil
174
200
 
175
201
  # wait until the instance is created
@@ -180,7 +206,7 @@ module GoodData
180
206
  client.create(ClientSynchronizationResult, res)
181
207
  end
182
208
 
183
- def synchronize_processes(projects = [], options = { dataproduct: 'default' })
209
+ def synchronize_processes(projects = [])
184
210
  projects = [projects] unless projects.is_a?(Array)
185
211
  projects = projects.map do |p|
186
212
  p = p.pid if p.respond_to?('pid')
@@ -198,7 +224,8 @@ module GoodData
198
224
  }
199
225
  end
200
226
 
201
- uri = '/gdc/internal/lcm/domains/%s/dataproducts/%s/segments/%s/syncProcesses' % [domain.obj_id, options[:dataproduct], id]
227
+ uri = '/gdc/internal/lcm/domains/%{domain}/dataproducts/%{dataproduct}/segments/%{segment}/syncProcesses'
228
+ uri = uri % { domain: domain.name, dataproduct: data_product.data_product_id, segment: segment_id }
202
229
  res = client.post(uri, body)
203
230
 
204
231
  client.poll_on_response(GoodData::Helpers.get_path(res, %w(asyncTask link poll)), sleep_interval: 1) do |r|
@@ -216,11 +243,31 @@ module GoodData
216
243
  self
217
244
  rescue RestClient::BadRequest => e
218
245
  payload = GoodData::Helpers.parse_http_exception(e)
219
- case GoodData::Helpers.get_path(payload)
220
- when 'gdc.c4.conflict.domain.segment.contains_clients'
221
- throw SegmentNotEmpty
222
- else
223
- raise e
246
+ e = SegmentNotEmpty if GoodData::Helpers.get_path(payload) == 'gdc.c4.conflict.domain.segment.contains_clients'
247
+ raise e
248
+ end
249
+
250
+ def provision_client_projects(segments = [])
251
+ body = {
252
+ provisionClientProjects: {
253
+ segments: segments.empty? ? [segment_id] : segments
254
+ }
255
+ }
256
+ res = client.post(GoodData::DataProduct::ONE_DATA_PRODUCT_PATH % { domain_name: domain.name, id: data_product.data_product_id } + '/provisionClientProjects', body)
257
+ res = client.poll_on_code(res['asyncTask']['links']['poll'])
258
+ failed_count = GoodData::Helpers.get_path(res, %w(clientProjectProvisioningResult failed count), 0)
259
+ created_count = GoodData::Helpers.get_path(res, %w(clientProjectProvisioningResult created count), 0)
260
+ return Enumerator.new([]) if (failed_count + created_count).zero?
261
+ Enumerator.new do |y|
262
+ uri = GoodData::Helpers.get_path(res, %w(clientProjectProvisioningResult links details))
263
+ loop do
264
+ result = client.get(uri)
265
+ (GoodData::Helpers.get_path(result, %w(clientProjectProvisioningResultDetails items)) || []).each do |item|
266
+ y << ProvisioningResult.new(item['id'], item['status'], item['project'], item['error'])
267
+ end
268
+ uri = GoodData::Helpers.get_path(res, %w(clientProjectProvisioningResultDetails paging next))
269
+ break if uri.nil?
270
+ end
224
271
  end
225
272
  end
226
273
  end