gooddata 2.2.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
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 +64 -59
  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