gooddata 2.2.0-java → 2.3.0-java

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 (67) hide show
  1. checksums.yaml +5 -5
  2. data/.gdc-ii-config.yaml +42 -1
  3. data/.github/workflows/build.yml +14 -13
  4. data/.github/workflows/pre-merge.yml +13 -13
  5. data/.pronto.yml +1 -0
  6. data/.rubocop.yml +2 -14
  7. data/CHANGELOG.md +9 -0
  8. data/Dockerfile +13 -7
  9. data/Dockerfile.jruby +5 -5
  10. data/Dockerfile.ruby +5 -7
  11. data/Gemfile +4 -2
  12. data/README.md +5 -4
  13. data/Rakefile +1 -1
  14. data/SDK_VERSION +1 -1
  15. data/VERSION +1 -1
  16. data/bin/run_brick.rb +7 -0
  17. data/ci/mysql/pom.xml +6 -1
  18. data/ci/redshift/pom.xml +3 -4
  19. data/docker-compose.lcm.yml +42 -1
  20. data/docker-compose.yml +42 -0
  21. data/gooddata.gemspec +21 -22
  22. data/lcm.rake +9 -0
  23. data/lib/gooddata/bricks/base_pipeline.rb +26 -0
  24. data/lib/gooddata/bricks/brick.rb +0 -1
  25. data/lib/gooddata/bricks/middleware/execution_result_middleware.rb +3 -3
  26. data/lib/gooddata/bricks/pipeline.rb +2 -14
  27. data/lib/gooddata/cloud_resources/mysql/mysql_client.rb +18 -8
  28. data/lib/gooddata/cloud_resources/redshift/drivers/.gitkeepme +0 -0
  29. data/lib/gooddata/cloud_resources/redshift/redshift_client.rb +0 -2
  30. data/lib/gooddata/cloud_resources/snowflake/snowflake_client.rb +1 -1
  31. data/lib/gooddata/lcm/actions/base_action.rb +157 -0
  32. data/lib/gooddata/lcm/actions/collect_data_product.rb +2 -1
  33. data/lib/gooddata/lcm/actions/collect_projects_warning_status.rb +53 -0
  34. data/lib/gooddata/lcm/actions/collect_segment_clients.rb +14 -0
  35. data/lib/gooddata/lcm/actions/initialize_continue_on_error_option.rb +87 -0
  36. data/lib/gooddata/lcm/actions/migrate_gdc_date_dimension.rb +28 -2
  37. data/lib/gooddata/lcm/actions/provision_clients.rb +34 -5
  38. data/lib/gooddata/lcm/actions/synchronize_cas.rb +24 -4
  39. data/lib/gooddata/lcm/actions/synchronize_clients.rb +56 -4
  40. data/lib/gooddata/lcm/actions/synchronize_dataset_mappings.rb +28 -3
  41. data/lib/gooddata/lcm/actions/synchronize_etls_in_segment.rb +48 -11
  42. data/lib/gooddata/lcm/actions/synchronize_kd_dashboard_permission.rb +103 -0
  43. data/lib/gooddata/lcm/actions/synchronize_ldm.rb +60 -15
  44. data/lib/gooddata/lcm/actions/synchronize_ldm_layout.rb +98 -0
  45. data/lib/gooddata/lcm/actions/synchronize_pp_dashboard_permission.rb +108 -0
  46. data/lib/gooddata/lcm/actions/synchronize_schedules.rb +31 -1
  47. data/lib/gooddata/lcm/actions/synchronize_user_filters.rb +14 -9
  48. data/lib/gooddata/lcm/actions/synchronize_user_groups.rb +30 -4
  49. data/lib/gooddata/lcm/actions/synchronize_users.rb +11 -10
  50. data/lib/gooddata/lcm/actions/update_metric_formats.rb +21 -4
  51. data/lib/gooddata/lcm/exceptions/lcm_execution_warning.rb +15 -0
  52. data/lib/gooddata/lcm/helpers/check_helper.rb +19 -0
  53. data/lib/gooddata/lcm/lcm2.rb +45 -4
  54. data/lib/gooddata/lcm/user_bricks_helper.rb +9 -0
  55. data/lib/gooddata/mixins/inspector.rb +1 -1
  56. data/lib/gooddata/models/ldm_layout.rb +38 -0
  57. data/lib/gooddata/models/project.rb +197 -22
  58. data/lib/gooddata/models/project_creator.rb +83 -6
  59. data/lib/gooddata/models/segment.rb +2 -1
  60. data/lib/gooddata/models/user_filters/user_filter_builder.rb +104 -15
  61. data/lib/gooddata/rest/connection.rb +5 -3
  62. data/lib/gooddata/rest/phmap.rb +1 -0
  63. data/lib/gooddata.rb +1 -0
  64. data/lib/gooddata_brick_base.rb +35 -0
  65. data/sonar-project.properties +6 -0
  66. metadata +60 -55
  67. data/lib/gooddata/cloud_resources/redshift/drivers/log4j.properties +0 -15
@@ -19,6 +19,18 @@ module GoodData
19
19
 
20
20
  description 'Synchronization Info'
21
21
  param :synchronize, array_of(instance_of(Type::SynchronizationInfoType)), required: true, generated: true
22
+
23
+ description 'Abort on error'
24
+ param :abort_on_error, instance_of(Type::StringType), required: false
25
+
26
+ description 'Logger'
27
+ param :gdc_logger, instance_of(Type::GdLogger), required: false
28
+
29
+ description 'Collect synced status'
30
+ param :collect_synced_status, instance_of(Type::BooleanType), required: false
31
+
32
+ description 'Sync failed list'
33
+ param :sync_failed_list, instance_of(Type::HashType), required: false
22
34
  end
23
35
 
24
36
  RESULT_HEADER = %i[from to status]
@@ -27,6 +39,8 @@ module GoodData
27
39
  def call(params)
28
40
  results = []
29
41
  params.synchronize.map do |segment_info|
42
+ next if sync_failed_segment(segment_info[:segment_id], params)
43
+
30
44
  result = migrate_date_dimension(params, segment_info)
31
45
  results.concat(result)
32
46
  end
@@ -47,8 +61,13 @@ module GoodData
47
61
  # check latest master and previous master
48
62
  master_upgrade_datasets = get_upgrade_dates(latest_blueprint, previous_blueprint) if params[:synchronize_ldm].downcase == 'diff_against_master' && previous_blueprint
49
63
  unless master_upgrade_datasets&.empty?
64
+ collect_synced_status = collect_synced_status(params)
65
+ failed_projects = ThreadSafe::Array.new
66
+
50
67
  segment_info[:to].pmap do |entry|
51
68
  pid = entry[:pid]
69
+ next if sync_failed_project(pid, params)
70
+
52
71
  to_project = client.projects(pid) || fail("Invalid 'to' project specified - '#{pid}'")
53
72
  GoodData.logger.info "Migrating date dimension, project: '#{to_project.title}', PID: #{pid}"
54
73
  to_blueprint = to_project.blueprint
@@ -56,13 +75,18 @@ module GoodData
56
75
  next if upgrade_datasets.empty?
57
76
 
58
77
  message = get_upgrade_message(upgrade_datasets)
78
+ failed_message = "Failed to migrate date dimension for project #{pid}"
79
+ update_status = to_project.upgrade_custom_v2(message)
80
+ process_failed_project(pid, failed_message, failed_projects, collect_synced_status) if collect_synced_status && update_status != 'OK'
59
81
 
60
82
  results << {
61
83
  from: segment_info[:from],
62
84
  to: pid,
63
- status: to_project.upgrade_custom_v2(message)
85
+ status: update_status
64
86
  }
65
87
  end
88
+
89
+ process_failed_projects(failed_projects, short_name, params) if collect_synced_status
66
90
  end
67
91
 
68
92
  results
@@ -104,8 +128,10 @@ module GoodData
104
128
  get_date_dimensions(blueprint).any? { |e| e[:urn] == DATE_DIMENSION_CUSTOM_V2 }
105
129
  end
106
130
 
131
+ # Get date dimension from blue print. Return nil if date dimension not existing
107
132
  def get_date_dimension(blueprint, id)
108
- GoodData::Model::ProjectBlueprint.find_date_dimension(blueprint, id)
133
+ date_dimensions = get_date_dimensions(blueprint)
134
+ date_dimensions.find { |d| d[:id] == id }
109
135
  end
110
136
 
111
137
  def get_date_dimensions(blueprint)
@@ -29,7 +29,16 @@ module GoodData
29
29
  param :data_product, instance_of(Type::GDDataProductType), required: false
30
30
 
31
31
  description 'Logger'
32
- param :gdc_logger, instance_of(Type::GdLogger), required: true
32
+ param :gdc_logger, instance_of(Type::GdLogger), required: false
33
+
34
+ description 'Abort on error'
35
+ param :abort_on_error, instance_of(Type::StringType), required: false
36
+
37
+ description 'Collect synced status'
38
+ param :collect_synced_status, instance_of(Type::BooleanType), required: false
39
+
40
+ description 'Sync failed list'
41
+ param :sync_failed_list, instance_of(Type::HashType), required: false
33
42
  end
34
43
 
35
44
  RESULT_HEADER = [
@@ -45,6 +54,8 @@ module GoodData
45
54
  synchronize_projects = []
46
55
  data_product = params.data_product
47
56
  client = params.gdc_gd_client
57
+ collect_synced_status = collect_synced_status(params)
58
+ continue_on_error = continue_on_error(params)
48
59
  domain_name = params.organization || params.domain
49
60
  fail "Either organisation or domain has to be specified in params" unless domain_name
50
61
  domain = client.domain(domain_name) || fail("Invalid domain name specified - #{domain_name}")
@@ -52,6 +63,8 @@ module GoodData
52
63
  invalid_client_ids = []
53
64
  begin
54
65
  results = params.segments.map do |segment|
66
+ next if sync_failed_segment(segment.segment_id, params)
67
+
55
68
  segment_object = domain.segments(segment.segment_id, data_product)
56
69
  tmp = segment_object.provision_client_projects.map do |m|
57
70
  Hash[m.each_pair.to_a].merge(type: :provision_result)
@@ -65,10 +78,21 @@ module GoodData
65
78
  unless entry[:project_uri]
66
79
  error_message = "There was error during provisioning clients: #{entry[:error]}" unless error_message
67
80
  invalid_client_ids << entry[:id]
81
+ if collect_synced_status
82
+ failed_message = "Failed to provision client #{entry[:id]} in segment #{segment.segment_id}. Error: #{entry[:error]}"
83
+ add_failed_client(entry[:id], failed_message, short_name, params)
84
+ end
68
85
  next
69
86
  end
87
+
88
+ project_id = entry[:project_uri].split('/').last
89
+ if collect_synced_status && entry[:status] == 'CREATED' && entry[:id]
90
+ # Update project client mappings when there are create new clients during provision clients of the segment
91
+ add_new_clients_to_project_client_mapping(project_id, entry[:id], segment.segment_id, params)
92
+ end
93
+
70
94
  {
71
- pid: entry[:project_uri].split('/').last,
95
+ pid: project_id,
72
96
  client_id: entry[:id]
73
97
  }
74
98
  end.compact
@@ -93,15 +117,20 @@ module GoodData
93
117
  end
94
118
 
95
119
  params.gdc_logger.debug "Deleted clients: #{deleted_client_ids.join(', ')}"
96
- raise error_message unless error_message['TooManyProjectsCreatedException'] || error_message['Max number registered projects']
120
+ unless error_message['TooManyProjectsCreatedException'] || error_message['Max number registered projects']
121
+ raise error_message unless continue_on_error
122
+
123
+ next
124
+ end
125
+
97
126
  break tmp
98
127
  end
99
128
 
100
129
  tmp
101
130
  end
102
131
  rescue => e
103
- params.gdc_logger.error "Problem occurs when provisioning clients."
104
- raise e
132
+ params.gdc_logger.error "Problem occurs when provisioning clients. Error: #{e}"
133
+ raise e unless continue_on_error
105
134
  end
106
135
 
107
136
  results.flatten! if results
@@ -32,6 +32,15 @@ module GoodData
32
32
 
33
33
  description 'Additional Hidden Parameters'
34
34
  param :additional_hidden_params, instance_of(Type::HashType), required: false
35
+
36
+ description 'Abort on error'
37
+ param :abort_on_error, instance_of(Type::StringType), required: false
38
+
39
+ description 'Collect synced status'
40
+ param :collect_synced_status, instance_of(Type::BooleanType), required: false
41
+
42
+ description 'Sync failed list'
43
+ param :sync_failed_list, instance_of(Type::HashType), required: false
35
44
  end
36
45
 
37
46
  class << self
@@ -46,6 +55,8 @@ module GoodData
46
55
  return results unless include_ca
47
56
 
48
57
  client = params.gdc_gd_client
58
+ collect_synced_status = collect_synced_status(params)
59
+ failed_projects = ThreadSafe::Array.new
49
60
 
50
61
  params.synchronize.each do |info|
51
62
  from = info.from
@@ -58,24 +69,33 @@ module GoodData
58
69
  next unless ca_scripts
59
70
 
60
71
  pid = entry[:pid]
72
+ next if sync_failed_project(pid, params)
73
+
61
74
  ca_chunks = ca_scripts[:maqlDdlChunks]
62
- to_project = client.projects(pid) || fail("Invalid 'to' project specified - '#{pid}'")
63
- params.gdc_logger.info "Synchronizing Computed Attributes to project: '#{to_project.title}', PID: #{pid}"
75
+ to_project = client.projects(pid)
76
+ unless to_project
77
+ process_failed_project(pid, "Invalid 'to' project specified - '#{pid}'", failed_projects, collect_synced_status)
78
+ next
79
+ end
64
80
 
81
+ params.gdc_logger.info "Synchronizing Computed Attributes to project: '#{to_project.title}', PID: #{pid}"
82
+ error_message = nil
65
83
  begin
66
84
  ca_chunks.each { |chunk| to_project.execute_maql(chunk) }
67
85
  rescue => e
68
- raise "Error occured when executing MAQL, project: \"#{to_project.title}\" reason: \"#{e.message}\", chunks: #{ca_chunks.inspect}"
86
+ error_message = "Error occurred when executing MAQL for project: \"#{to_project.title}\" reason: \"#{e.message}\", chunks: #{ca_chunks.inspect}"
87
+ process_failed_project(pid, error_message, failed_projects, collect_synced_status)
69
88
  end
70
89
 
71
90
  results << {
72
91
  from: from,
73
92
  to: pid,
74
- status: 'ok'
93
+ status: error_message.nil? ? 'ok' : 'failed'
75
94
  }
76
95
  end
77
96
  end
78
97
 
98
+ process_failed_projects(failed_projects, short_name, params) if collect_synced_status
79
99
  results
80
100
  end
81
101
  end
@@ -38,6 +38,18 @@ module GoodData
38
38
 
39
39
  description 'Additional Hidden Parameters'
40
40
  param :additional_hidden_params, instance_of(Type::HashType), required: false
41
+
42
+ description 'Abort on error'
43
+ param :abort_on_error, instance_of(Type::StringType), required: false
44
+
45
+ description 'Logger'
46
+ param :gdc_logger, instance_of(Type::GdLogger), required: false
47
+
48
+ description 'Collect synced status'
49
+ param :collect_synced_status, instance_of(Type::BooleanType), required: false
50
+
51
+ description 'Sync failed list'
52
+ param :sync_failed_list, instance_of(Type::HashType), required: false
41
53
  end
42
54
 
43
55
  RESULT_HEADER = [
@@ -65,6 +77,8 @@ module GoodData
65
77
  end
66
78
 
67
79
  results = segments.map do |segment|
80
+ next if sync_failed_segment(segment.segment_id, params)
81
+
68
82
  if params.ads_client
69
83
  master_projects = GoodData::LCM2::Helpers.get_master_project_list_from_ads(params.release_table_name, params.ads_client, segment.segment_id)
70
84
  else
@@ -83,8 +97,7 @@ module GoodData
83
97
  failed_count = sync_result['failedClients']['count']
84
98
 
85
99
  if failed_count.to_i > 0
86
- fail("#{failed_count} clients failed to synchronize. " \
87
- "Details: #{sync_result['links']['details']}")
100
+ error_handle(segment, res, collect_synced_status(params), params)
88
101
  end
89
102
 
90
103
  if keep_only_previous_masters_count >= 0
@@ -102,8 +115,8 @@ module GoodData
102
115
 
103
116
  {
104
117
  segment: segment.id,
105
- master_pid: master.pid,
106
- master_name: master.title,
118
+ master_pid: master.nil? ? '' : master.pid,
119
+ master_name: master.nil? ? '' : master.title,
107
120
  successful_count: sync_result['successfulClients']['count']
108
121
  }
109
122
  end
@@ -147,6 +160,45 @@ module GoodData
147
160
  end
148
161
  end
149
162
  # rubocop:enable Metrics/ParameterLists
163
+
164
+ private
165
+
166
+ def error_handle(segment, error_result, continue_on_error, params)
167
+ sync_result = error_result.json['synchronizationResult']
168
+ success_count = sync_result['successfulClients']['count'].to_i
169
+ failed_count = sync_result['failedClients']['count'].to_i
170
+
171
+ # Synchronize failure for all clients in segment
172
+ if continue_on_error && success_count.zero? && failed_count.positive?
173
+ segment_warning_message = "Failed to synchronize clients for #{segment.segment_id} segment. Details: #{sync_result['links']['details']}"
174
+ add_failed_segment(segment.segment_id, segment_warning_message, short_name, params)
175
+ return
176
+ end
177
+
178
+ summary_error_message = "#{failed_count} clients failed to synchronize. Details: #{sync_result['links']['details']}"
179
+ fail(summary_error_message) unless continue_on_error
180
+
181
+ GoodData.logger.warn summary_error_message
182
+ if error_result.details # rubocop:disable Style/SafeNavigation
183
+ error_result.details.items.each do |item|
184
+ next if item['status'] == 'OK'
185
+
186
+ error_client_id = item['id']
187
+ error_message = error_message(item, segment)
188
+ add_failed_client(error_client_id, error_message, short_name, params)
189
+ end
190
+ end
191
+ end
192
+
193
+ def error_message(error_item, segment)
194
+ error_client_id = error_item['id']
195
+ error_message = "Failed to synchronize #{error_client_id} client in #{segment.segment_id} segment."
196
+ error_message = "#{error_message}. Detail: #{error_item['error']['message']}" if error_item['error'] && error_item['error']['message']
197
+
198
+ error_message = "#{error_message}. Error items: #{error_item['error']['parameters']}" if error_item['error'] && error_item['error']['parameters']
199
+
200
+ error_message
201
+ end
150
202
  end
151
203
  end
152
204
  end
@@ -24,6 +24,15 @@ module GoodData
24
24
 
25
25
  description 'Logger'
26
26
  param :gdc_logger, instance_of(Type::GdLogger), required: true
27
+
28
+ description 'Abort on error'
29
+ param :abort_on_error, instance_of(Type::StringType), required: false
30
+
31
+ description 'Collect synced status'
32
+ param :collect_synced_status, instance_of(Type::BooleanType), required: false
33
+
34
+ description 'Sync failed list'
35
+ param :sync_failed_list, instance_of(Type::HashType), required: false
27
36
  end
28
37
 
29
38
  RESULT_HEADER = %i[from to count status]
@@ -31,6 +40,8 @@ module GoodData
31
40
  class << self
32
41
  def call(params)
33
42
  results = []
43
+ collect_synced_status = collect_synced_status(params)
44
+ failed_projects = ThreadSafe::Array.new
34
45
 
35
46
  client = params.gdc_gd_client
36
47
  development_client = params.development_client
@@ -39,22 +50,36 @@ module GoodData
39
50
  from_project = info.from
40
51
  to_projects = info.to
41
52
 
42
- from = development_client.projects(from_project) || fail("Invalid 'from' project specified - '#{from_project}'")
53
+ from = development_client.projects(from_project)
54
+ unless from
55
+ process_failed_project(from_project, "Invalid 'from' project specified - '#{from_project}'", failed_projects, collect_synced_status)
56
+ next
57
+ end
58
+
43
59
  dataset_mapping = from.dataset_mapping
44
60
  if dataset_mapping&.dig('datasetMappings', 'items').nil? || dataset_mapping['datasetMappings']['items'].empty?
45
61
  params.gdc_logger.info "Project: '#{from.title}', PID: '#{from.pid}' has no model mapping, skip synchronizing model mapping."
46
62
  else
47
63
  to_projects.peach do |to|
48
64
  pid = to[:pid]
49
- to_project = client.projects(pid) || fail("Invalid 'to' project specified - '#{pid}'")
65
+ next if sync_failed_project(pid, params)
66
+
67
+ to_project = client.projects(pid)
68
+ process_failed_project(pid, "Invalid 'to' project specified - '#{pid}'", failed_projects, collect_synced_status) unless to_project
50
69
 
51
- params.gdc_logger.info "Transferring model mapping, from project: '#{from.title}', PID: '#{from.pid}', to project: '#{to_project.title}', PID: '#{to_project.pid}'"
70
+ message_to_project = "to project: '#{to_project.title}', PID: '#{to_project.pid}'"
71
+ params.gdc_logger.info "Transferring model mapping, from project: '#{from.title}', PID: '#{from.pid}', #{message_to_project}"
52
72
  res = to_project.update_dataset_mapping(dataset_mapping)
53
73
  res[:from] = from.pid
54
74
  results << res
75
+
76
+ failed_message = "Failed to transfer model mapping from project '#{from.pid}' to project '#{to_project.pid}'"
77
+ process_failed_project(pid, failed_message, failed_projects, collect_synced_status) if collect_synced_status && res[:status] != 'OK'
55
78
  end
56
79
  end
57
80
  end
81
+
82
+ process_failed_projects(failed_projects, short_name, params) if collect_synced_status
58
83
  # Return results
59
84
  results.flatten
60
85
  end
@@ -44,6 +44,15 @@ module GoodData
44
44
 
45
45
  description 'Delete extra process schedule flag'
46
46
  param :delete_extra_process_schedule, instance_of(Type::BooleanType), required: false, default: true
47
+
48
+ description 'Abort on error'
49
+ param :abort_on_error, instance_of(Type::StringType), required: false
50
+
51
+ description 'Collect synced status'
52
+ param :collect_synced_status, instance_of(Type::BooleanType), required: false
53
+
54
+ description 'Sync failed list'
55
+ param :sync_failed_list, instance_of(Type::HashType), required: false
47
56
  end
48
57
 
49
58
  # will be updated later based on the way etl synchronization
@@ -59,6 +68,8 @@ module GoodData
59
68
  def call(params)
60
69
  client = params.gdc_gd_client
61
70
  data_product = params.data_product
71
+ collect_synced_status = collect_synced_status(params)
72
+ failed_projects = ThreadSafe::Array.new
62
73
 
63
74
  schedule_additional_params = params.schedule_additional_params || params.additional_params
64
75
  schedule_additional_hidden_params = params.schedule_additional_hidden_params || params.additional_hidden_params
@@ -68,6 +79,8 @@ module GoodData
68
79
  end
69
80
 
70
81
  results = synchronize_segments.pmap do |segment_id, synchronize|
82
+ next if collect_synced_status && sync_failed_segment(segment_id, params)
83
+
71
84
  segment = data_product.segments.find { |s| s.segment_id == segment_id }
72
85
  res = segment.synchronize_processes(
73
86
  synchronize.flat_map do |info|
@@ -80,7 +93,11 @@ module GoodData
80
93
  res = GoodData::Helpers.symbolize_keys(res)
81
94
 
82
95
  if res[:syncedResult][:errors]
83
- fail "Failed to sync processes/schedules for segment #{segment_id}. Error: #{res[:syncedResult][:errors].pretty_inspect}"
96
+ error_message = "Failed to sync processes/schedules for segment #{segment_id}. Error: #{res[:syncedResult][:errors].pretty_inspect}"
97
+ fail error_message unless collect_synced_status
98
+
99
+ add_failed_segment(segment_id, error_message, short_name, params)
100
+ next
84
101
  end
85
102
 
86
103
  if res[:syncedResult][:clients]
@@ -111,12 +128,19 @@ module GoodData
111
128
  hidden_params_for_all_schedules_in_all_projects = hidden_params_for_all_projects[:all_schedules]
112
129
 
113
130
  params.synchronize.peach do |info|
114
- from_project_etl_names = get_process_n_schedule_names(client, info.from) if delete_extra_process_schedule
131
+ from_project_etl_names = get_process_n_schedule_names(client, info.from, failed_projects, collect_synced_status) if delete_extra_process_schedule
132
+ next if delete_extra_process_schedule && from_project_etl_names.nil?
115
133
 
116
134
  to_projects = info.to
117
135
  to_projects.peach do |entry|
118
136
  pid = entry[:pid]
119
- to_project = client.projects(pid) || fail("Invalid 'to' project specified - '#{pid}'")
137
+ next if collect_synced_status && sync_failed_project(pid, params)
138
+
139
+ to_project = client.projects(pid)
140
+ unless to_project
141
+ process_failed_project(pid, "Invalid 'to' project specified - '#{pid}'", failed_projects, collect_synced_status)
142
+ next
143
+ end
120
144
 
121
145
  if delete_extra_process_schedule
122
146
  to_project_process_id_names = {}
@@ -140,24 +164,31 @@ module GoodData
140
164
  to_project.set_metadata('GOODOT_CUSTOM_PROJECT_ID', client_id) # TMA-210
141
165
 
142
166
  to_project.schedules.each do |schedule|
167
+ schedule_name = schedule.name
143
168
  if delete_extra_process_schedule
144
- unless from_project_etl_names[:schedules].include?([schedule.name, to_project_process_id_names[schedule.process_id]])
169
+ schedule_project_info = [schedule_name, to_project_process_id_names[schedule.process_id]]
170
+ unless from_project_etl_names[:schedules].include?(schedule_project_info)
145
171
  schedule.delete
146
172
  next
147
173
  end
148
174
  end
149
175
 
176
+ params_for_all_projects_schedule_name = params_for_all_projects[schedule_name]
177
+ params_for_this_client_schedule_name = params_for_this_client[schedule_name]
178
+ hidden_params_for_all_projects_schedule_name = hidden_params_for_all_projects[schedule_name]
179
+ hidden_params_for_this_client_schedule_name = hidden_params_for_this_client[schedule_name]
180
+
150
181
  schedule.update_params(schedule_additional_params) if schedule_additional_params
151
- schedule.update_params(params_for_all_schedules_in_all_projects) if params_for_all_schedules_in_all_projects
152
- schedule.update_params(params_for_all_projects[schedule.name]) if params_for_all_projects[schedule.name]
182
+ schedule.update_params(**params_for_all_schedules_in_all_projects) if params_for_all_schedules_in_all_projects
183
+ schedule.update_params(**params_for_all_projects_schedule_name) if params_for_all_projects_schedule_name
153
184
  schedule.update_params(params_for_all_schedules_in_this_client) if params_for_all_schedules_in_this_client
154
- schedule.update_params(params_for_this_client[schedule.name]) if params_for_this_client[schedule.name]
185
+ schedule.update_params(**params_for_this_client_schedule_name) if params_for_this_client_schedule_name
155
186
 
156
187
  schedule.update_hidden_params(schedule_additional_hidden_params) if schedule_additional_hidden_params
157
188
  schedule.update_hidden_params(hidden_params_for_all_schedules_in_all_projects) if hidden_params_for_all_schedules_in_all_projects
158
- schedule.update_hidden_params(hidden_params_for_all_projects[schedule.name]) if hidden_params_for_all_projects[schedule.name]
189
+ schedule.update_hidden_params(hidden_params_for_all_projects_schedule_name) if hidden_params_for_all_projects_schedule_name
159
190
  schedule.update_hidden_params(hidden_params_for_all_schedules_in_this_client) if hidden_params_for_all_schedules_in_this_client
160
- schedule.update_hidden_params(hidden_params_for_this_client[schedule.name]) if hidden_params_for_this_client[schedule.name]
191
+ schedule.update_hidden_params(hidden_params_for_this_client_schedule_name) if hidden_params_for_this_client_schedule_name
161
192
 
162
193
  schedule.enable
163
194
  schedule.save
@@ -165,13 +196,19 @@ module GoodData
165
196
  end
166
197
  end
167
198
 
199
+ process_failed_projects(failed_projects, short_name, params) if collect_synced_status
168
200
  results.flatten
169
201
  end
170
202
 
171
203
  private
172
204
 
173
- def get_process_n_schedule_names(client, project_id)
174
- project = client.projects(project_id) || fail("Invalid 'from' project specified - '#{project_id}'")
205
+ def get_process_n_schedule_names(client, project_id, failed_projects, continue_on_error)
206
+ project = client.projects(project_id)
207
+ unless project
208
+ process_failed_project(project_id, "Invalid 'from' project specified - '#{project_id}'", failed_projects, continue_on_error)
209
+ return nil
210
+ end
211
+
175
212
  processes = project.processes
176
213
  process_id_names = Hash[processes.map { |process| [process.process_id, process.name] }]
177
214
 
@@ -0,0 +1,103 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+ #
4
+ # Copyright (c) 2010-2021 GoodData Corporation. All rights reserved.
5
+ # This source code is licensed under the BSD-style license found in the
6
+ # LICENSE file in the root directory of this source tree.
7
+
8
+ require_relative 'base_action'
9
+ require 'thread_safe'
10
+
11
+ module GoodData
12
+ module LCM2
13
+ class SynchronizeKDDashboardPermissions < BaseAction
14
+ DESCRIPTION = 'Synchronize KD Dashboard Permission'
15
+
16
+ PARAMS = define_params(self) do
17
+ description 'Client used to connecting to development domain'
18
+ param :development_client, instance_of(Type::GdClientType), required: true
19
+
20
+ description 'Client Used for Connecting to GD'
21
+ param :gdc_gd_client, instance_of(Type::GdClientType), required: true
22
+
23
+ description 'Logger'
24
+ param :gdc_logger, instance_of(Type::GdLogger), required: true
25
+
26
+ description 'Additional Hidden Parameters'
27
+ param :additional_hidden_params, instance_of(Type::HashType), required: false
28
+
29
+ description 'Synchronization Info'
30
+ param :synchronize, array_of(instance_of(Type::SynchronizationInfoType)), required: true, generated: true
31
+
32
+ description 'Disable synchronizing dashboard permissions for AD/KD dashboards.'
33
+ param :disable_kd_dashboard_permission, instance_of(Type::BooleanType), required: false, default: false
34
+
35
+ description 'Abort on error'
36
+ param :abort_on_error, instance_of(Type::StringType), required: false
37
+
38
+ description 'Collect synced status'
39
+ param :collect_synced_status, instance_of(Type::BooleanType), required: false
40
+
41
+ description 'Sync failed list'
42
+ param :sync_failed_list, instance_of(Type::HashType), required: false
43
+ end
44
+
45
+ class << self
46
+ def call(params)
47
+ results = ThreadSafe::Array.new
48
+ collect_synced_status = collect_synced_status(params)
49
+ failed_projects = ThreadSafe::Array.new
50
+
51
+ disable_kd_dashboard_permission = GoodData::Helpers.to_boolean(params.disable_kd_dashboard_permission)
52
+
53
+ # rubocop:disable Style/UnlessElse
54
+ unless disable_kd_dashboard_permission
55
+ dashboard_type = "KD"
56
+ dev_client = params.development_client
57
+ gdc_client = params.gdc_gd_client
58
+
59
+ params.synchronize.peach do |param_info|
60
+ from_project_info = param_info.from
61
+ to_projects_info = param_info.to
62
+
63
+ from_project = dev_client.projects(from_project_info)
64
+ unless from_project
65
+ process_failed_project(from_project_info, "Invalid 'from' project specified - '#{from_project_info}'", failed_projects, collect_synced_status)
66
+ next
67
+ end
68
+
69
+ from_dashboards = from_project.analytical_dashboards
70
+
71
+ params.gdc_logger.info "Transferring #{dashboard_type} Dashboard permission, from project: '#{from_project.title}', PID: '#{from_project.pid}' for dashboard(s): #{from_dashboards.map { |d| "#{d.title.inspect}" }.join(', ')}" # rubocop:disable Metrics/LineLength
72
+ to_projects_info.peach do |item|
73
+ to_project_pid = item[:pid]
74
+ next if sync_failed_project(to_project_pid, params)
75
+
76
+ to_project = gdc_client.projects(to_project_pid)
77
+ unless to_project
78
+ process_failed_project(to_project_pid, "Invalid 'to' project specified - '#{to_project_pid}'", failed_projects, collect_synced_status)
79
+ next
80
+ end
81
+
82
+ to_dashboards = to_project.analytical_dashboards
83
+ GoodData::Project.transfer_dashboard_permission(from_project, to_project, from_dashboards, to_dashboards)
84
+ end
85
+
86
+ results << {
87
+ from_project_name: from_project.title,
88
+ from_project_pid: from_project.pid,
89
+ status: 'ok'
90
+ }
91
+ end
92
+ else
93
+ params.gdc_logger.info "Skip synchronize KD dashboard permission."
94
+ end
95
+
96
+ process_failed_projects(failed_projects, short_name, params) if collect_synced_status
97
+ results
98
+ # rubocop:enable Style/UnlessElse
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end