gooddata 0.6.53 → 0.6.54

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