gooddata 2.1.19 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/.gdc-ii-config.yaml +42 -1
  3. data/.github/workflows/build.yml +67 -0
  4. data/.github/workflows/pre-merge.yml +72 -0
  5. data/.pronto.yml +1 -0
  6. data/.rubocop.yml +2 -14
  7. data/CHANGELOG.md +47 -0
  8. data/Dockerfile +27 -14
  9. data/Dockerfile.jruby +5 -15
  10. data/Dockerfile.ruby +5 -7
  11. data/Gemfile +4 -2
  12. data/LICENSE +4409 -16
  13. data/README.md +6 -6
  14. data/Rakefile +1 -1
  15. data/SDK_VERSION +1 -1
  16. data/VERSION +1 -1
  17. data/bin/run_brick.rb +7 -0
  18. data/ci/mssql/pom.xml +62 -0
  19. data/ci/mysql/pom.xml +62 -0
  20. data/ci/redshift/pom.xml +4 -5
  21. data/docker-compose.lcm.yml +42 -4
  22. data/docker-compose.yml +42 -0
  23. data/gooddata.gemspec +21 -21
  24. data/k8s/charts/lcm-bricks/Chart.yaml +1 -1
  25. data/lcm.rake +11 -8
  26. data/lib/gooddata/bricks/base_pipeline.rb +26 -0
  27. data/lib/gooddata/bricks/brick.rb +0 -1
  28. data/lib/gooddata/bricks/middleware/aws_middleware.rb +35 -9
  29. data/lib/gooddata/bricks/middleware/execution_result_middleware.rb +3 -3
  30. data/lib/gooddata/bricks/pipeline.rb +2 -14
  31. data/lib/gooddata/cloud_resources/blobstorage/blobstorage_client.rb +98 -0
  32. data/lib/gooddata/cloud_resources/mssql/drivers/.gitkeepme +0 -0
  33. data/lib/gooddata/cloud_resources/mssql/mssql_client.rb +122 -0
  34. data/lib/gooddata/cloud_resources/mysql/drivers/.gitkeepme +0 -0
  35. data/lib/gooddata/cloud_resources/mysql/mysql_client.rb +121 -0
  36. data/lib/gooddata/cloud_resources/postgresql/postgresql_client.rb +0 -1
  37. data/lib/gooddata/cloud_resources/redshift/drivers/.gitkeepme +0 -0
  38. data/lib/gooddata/cloud_resources/redshift/redshift_client.rb +0 -2
  39. data/lib/gooddata/cloud_resources/snowflake/snowflake_client.rb +18 -1
  40. data/lib/gooddata/helpers/data_helper.rb +9 -4
  41. data/lib/gooddata/lcm/actions/base_action.rb +157 -0
  42. data/lib/gooddata/lcm/actions/collect_data_product.rb +2 -1
  43. data/lib/gooddata/lcm/actions/collect_meta.rb +3 -1
  44. data/lib/gooddata/lcm/actions/collect_projects_warning_status.rb +53 -0
  45. data/lib/gooddata/lcm/actions/collect_segment_clients.rb +14 -0
  46. data/lib/gooddata/lcm/actions/initialize_continue_on_error_option.rb +87 -0
  47. data/lib/gooddata/lcm/actions/migrate_gdc_date_dimension.rb +31 -4
  48. data/lib/gooddata/lcm/actions/provision_clients.rb +34 -5
  49. data/lib/gooddata/lcm/actions/synchronize_cas.rb +24 -4
  50. data/lib/gooddata/lcm/actions/synchronize_clients.rb +112 -11
  51. data/lib/gooddata/lcm/actions/synchronize_dataset_mappings.rb +89 -0
  52. data/lib/gooddata/lcm/actions/synchronize_etls_in_segment.rb +48 -11
  53. data/lib/gooddata/lcm/actions/synchronize_kd_dashboard_permission.rb +103 -0
  54. data/lib/gooddata/lcm/actions/synchronize_ldm.rb +79 -23
  55. data/lib/gooddata/lcm/actions/synchronize_ldm_layout.rb +98 -0
  56. data/lib/gooddata/lcm/actions/synchronize_pp_dashboard_permission.rb +108 -0
  57. data/lib/gooddata/lcm/actions/synchronize_schedules.rb +31 -1
  58. data/lib/gooddata/lcm/actions/synchronize_user_filters.rb +26 -18
  59. data/lib/gooddata/lcm/actions/synchronize_user_groups.rb +30 -4
  60. data/lib/gooddata/lcm/actions/synchronize_users.rb +11 -10
  61. data/lib/gooddata/lcm/actions/update_metric_formats.rb +202 -0
  62. data/lib/gooddata/lcm/data/delete_from_lcm_release.sql.erb +5 -0
  63. data/lib/gooddata/lcm/exceptions/lcm_execution_warning.rb +15 -0
  64. data/lib/gooddata/lcm/helpers/check_helper.rb +19 -0
  65. data/lib/gooddata/lcm/helpers/release_table_helper.rb +42 -8
  66. data/lib/gooddata/lcm/lcm2.rb +50 -4
  67. data/lib/gooddata/lcm/user_bricks_helper.rb +9 -0
  68. data/lib/gooddata/mixins/inspector.rb +1 -1
  69. data/lib/gooddata/mixins/md_object_query.rb +1 -0
  70. data/lib/gooddata/models/data_source.rb +5 -1
  71. data/lib/gooddata/models/dataset_mapping.rb +36 -0
  72. data/lib/gooddata/models/ldm_layout.rb +38 -0
  73. data/lib/gooddata/models/metadata/label.rb +26 -27
  74. data/lib/gooddata/models/project.rb +230 -30
  75. data/lib/gooddata/models/project_creator.rb +83 -6
  76. data/lib/gooddata/models/schedule.rb +13 -1
  77. data/lib/gooddata/models/segment.rb +2 -1
  78. data/lib/gooddata/models/user_filters/user_filter_builder.rb +162 -68
  79. data/lib/gooddata/rest/connection.rb +5 -3
  80. data/lib/gooddata/rest/phmap.rb +2 -0
  81. data/lib/gooddata.rb +1 -0
  82. data/lib/gooddata_brick_base.rb +35 -0
  83. data/sonar-project.properties +6 -0
  84. metadata +100 -68
  85. data/lib/gooddata/bricks/middleware/bulk_salesforce_middleware.rb +0 -37
  86. data/lib/gooddata/cloud_resources/redshift/drivers/log4j.properties +0 -15
@@ -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
@@ -5,6 +5,7 @@
5
5
  # LICENSE file in the root directory of this source tree.
6
6
 
7
7
  require_relative 'base_action'
8
+ require_relative '../helpers/helpers'
8
9
 
9
10
  using TrueExtensions
10
11
  using FalseExtensions
@@ -47,6 +48,15 @@ module GoodData
47
48
 
48
49
  description 'Enables handling of deprecated objects in the logical data model.'
49
50
  param :include_deprecated, instance_of(Type::BooleanType), required: false, default: false
51
+
52
+ description 'Abort on error'
53
+ param :abort_on_error, instance_of(Type::StringType), required: false
54
+
55
+ description 'Collect synced status'
56
+ param :collect_synced_status, instance_of(Type::BooleanType), required: false
57
+
58
+ description 'Sync failed list'
59
+ param :sync_failed_list, instance_of(Type::HashType), required: false
50
60
  end
51
61
 
52
62
  RESULT_HEADER = %i[from to status]
@@ -56,6 +66,8 @@ module GoodData
56
66
  results = []
57
67
  synchronize = []
58
68
  params.synchronize.map do |segment_info|
69
+ next if sync_failed_segment(segment_info[:segment_id], params)
70
+
59
71
  new_segment_info, segment_results = sync_segment_ldm(params, segment_info)
60
72
  results.concat(segment_results)
61
73
  synchronize << new_segment_info
@@ -72,69 +84,113 @@ module GoodData
72
84
  private
73
85
 
74
86
  def sync_segment_ldm(params, segment_info)
75
- results = []
87
+ collect_synced_status = collect_synced_status(params)
88
+ failed_projects = ThreadSafe::Array.new
89
+ results = ThreadSafe::Array.new
76
90
  client = params.gdc_gd_client
77
91
  exclude_fact_rule = params.exclude_fact_rule.to_b
78
92
  include_deprecated = params.include_deprecated.to_b
93
+ update_preference = params[:update_preference]
94
+ exist_fallback_to_hard_sync_config = !update_preference.nil? && !update_preference[:fallback_to_hard_sync].nil?
95
+ include_maql_fallback_hard_sync = exist_fallback_to_hard_sync_config && Helpers.to_bool('fallback_to_hard_sync', update_preference[:fallback_to_hard_sync])
96
+
79
97
  from_pid = segment_info[:from]
80
- from = params.development_client.projects(from_pid) || fail("Invalid 'from' project specified - '#{from_pid}'")
98
+ from = params.development_client.projects(from_pid)
99
+ unless from
100
+ segment_id = segment_info[:segment_id]
101
+ error_message = "Failed to sync LDM for segment #{segment_id}. Error: Invalid 'from' project specified - '#{from_pid}'"
102
+ fail(error_message) unless collect_synced_status
103
+
104
+ add_failed_segment(segment_id, error_message, short_name, params)
105
+ return [segment_info, results]
106
+ end
107
+
81
108
  GoodData.logger.info "Creating Blueprint, project: '#{from.title}', PID: #{from_pid}"
82
109
  blueprint = from.blueprint(include_ca: params.include_computed_attributes.to_b)
83
110
  segment_info[:from_blueprint] = blueprint
84
111
  maql_diff = nil
85
112
  previous_master = segment_info[:previous_master]
113
+ synchronize_ldm_mode = params[:synchronize_ldm].downcase
86
114
  diff_against_master = %w(diff_against_master_with_fallback diff_against_master)
87
- .include?(params[:synchronize_ldm].downcase)
88
- GoodData.logger.info "Synchronize LDM mode: '#{params[:synchronize_ldm].downcase}'"
89
- if previous_master && diff_against_master
90
- maql_diff_params = [:includeGrain]
91
- maql_diff_params << :excludeFactRule if exclude_fact_rule
92
- maql_diff_params << :includeDeprecated if include_deprecated
93
- maql_diff = previous_master.maql_diff(blueprint: blueprint, params: maql_diff_params)
115
+ .include?(synchronize_ldm_mode)
116
+ GoodData.logger.info "Synchronize LDM mode: '#{synchronize_ldm_mode}'"
117
+ if segment_info.key?(:previous_master) && diff_against_master
118
+ if previous_master
119
+ maql_diff_params = [:includeGrain]
120
+ maql_diff_params << :excludeFactRule if exclude_fact_rule
121
+ maql_diff_params << :includeDeprecated if include_deprecated
122
+ maql_diff_params << :includeMaqlFallbackHardSync if include_maql_fallback_hard_sync
123
+
124
+ maql_diff = previous_master.maql_diff(blueprint: blueprint, params: maql_diff_params)
125
+ else
126
+ maql_diff = {
127
+ "projectModelDiff" =>
128
+ {
129
+ "updateOperations" => [],
130
+ "updateScripts" => []
131
+ }
132
+ }
133
+ end
134
+
94
135
  chunks = maql_diff['projectModelDiff']['updateScripts']
95
136
  if chunks.empty?
96
137
  GoodData.logger.info "Synchronize LDM to clients will not proceed in mode \
97
- '#{params[:synchronize_ldm].downcase}' due to no LDM changes in the new master project. \
138
+ '#{synchronize_ldm_mode}' due to no LDM changes in the segment master project. \
98
139
  If you had changed LDM of clients manually, please use mode 'diff_against_clients' \
99
140
  to force synchronize LDM to clients"
100
141
  end
101
142
  end
102
143
 
103
144
  segment_info[:to] = segment_info[:to].pmap do |entry|
145
+ update_status = true
104
146
  pid = entry[:pid]
105
- to_project = client.projects(pid) || fail("Invalid 'to' project specified - '#{pid}'")
147
+ next if sync_failed_project(pid, params)
148
+
149
+ to_project = client.projects(pid)
150
+ unless to_project
151
+ process_failed_project(pid, "Invalid 'to' project specified - '#{pid}'", failed_projects, collect_synced_status)
152
+ next
153
+ end
106
154
 
107
155
  GoodData.logger.info "Updating from Blueprint, project: '#{to_project.title}', PID: #{pid}"
108
156
  begin
109
157
  entry[:ca_scripts] = to_project.update_from_blueprint(
110
158
  blueprint,
111
- update_preference: params[:update_preference],
159
+ update_preference: update_preference,
112
160
  exclude_fact_rule: exclude_fact_rule,
113
161
  execute_ca_scripts: false,
114
162
  maql_diff: maql_diff,
115
163
  include_deprecated: include_deprecated
116
164
  )
117
165
  rescue MaqlExecutionError => e
118
- GoodData.logger.info("Applying MAQL to project #{to_project.title} - #{pid} failed. Reason: #{e}")
119
- fail e unless previous_master && params[:synchronize_ldm] == 'diff_against_master_with_fallback'
120
- GoodData.logger.info("Restoring the client project #{to_project.title} from master.")
121
- entry[:ca_scripts] = to_project.update_from_blueprint(
122
- blueprint,
123
- update_preference: params[:update_preference],
124
- exclude_fact_rule: exclude_fact_rule,
125
- execute_ca_scripts: false,
126
- include_deprecated: include_deprecated
127
- )
166
+ if collect_synced_status
167
+ update_status = false
168
+ failed_message = "Applying MAQL to project #{to_project.title} - #{pid} failed. Reason: #{e}"
169
+ process_failed_project(pid, failed_message, failed_projects, collect_synced_status)
170
+ else
171
+ fail e unless previous_master && params[:synchronize_ldm] == 'diff_against_master_with_fallback'
172
+
173
+ GoodData.logger.info("Restoring the client project #{to_project.title} from master.")
174
+ entry[:ca_scripts] = to_project.update_from_blueprint(
175
+ blueprint,
176
+ update_preference: update_preference,
177
+ exclude_fact_rule: exclude_fact_rule,
178
+ execute_ca_scripts: false,
179
+ include_deprecated: include_deprecated
180
+ )
181
+ end
128
182
  end
129
183
 
130
184
  results << {
131
185
  from: from_pid,
132
186
  to: pid,
133
187
  status: 'ok'
134
- }
188
+ } if update_status
189
+
135
190
  entry
136
191
  end
137
192
 
193
+ process_failed_projects(failed_projects, short_name, params) if collect_synced_status
138
194
  [segment_info, results]
139
195
  end
140
196
  end
@@ -0,0 +1,98 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+ #
4
+ # Copyright (c) 2022 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
+
10
+ module GoodData
11
+ module LCM2
12
+ class SynchronizeLdmLayout < BaseAction
13
+ DESCRIPTION = 'Synchronize LDM Layout'
14
+
15
+ PARAMS = define_params(self) do
16
+ description 'Logger'
17
+ param :gdc_logger, instance_of(Type::GdLogger), required: true
18
+
19
+ description 'Client Used for Connecting to GD'
20
+ param :gdc_gd_client, instance_of(Type::GdClientType), required: true
21
+
22
+ description 'Client used to connecting to development domain'
23
+ param :development_client, instance_of(Type::GdClientType), required: true
24
+
25
+ description 'Synchronization Info'
26
+ param :synchronize, array_of(instance_of(Type::SynchronizationInfoType)), required: true, generated: 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
36
+ end
37
+
38
+ RESULT_HEADER = %i[from to status]
39
+
40
+ class << self
41
+ def call(params)
42
+ results = []
43
+
44
+ client = params.gdc_gd_client
45
+ development_client = params.development_client
46
+ gdc_logger = params.gdc_logger
47
+ collect_synced_status = collect_synced_status(params)
48
+ failed_projects = ThreadSafe::Array.new
49
+
50
+ params.synchronize.peach do |info|
51
+ from_project = info.from
52
+ to_projects = info.to
53
+
54
+ from = development_client.projects(from_project)
55
+ unless from
56
+ process_failed_project(from_project, "Invalid 'from' project specified - '#{from_project}'", failed_projects, collect_synced_status)
57
+ next
58
+ end
59
+
60
+ from_pid = from.pid
61
+ from_title = from.title
62
+ from_ldm_layout = from.ldm_layout
63
+
64
+ if from_ldm_layout&.dig('ldmLayout', 'layout').nil? || from_ldm_layout['ldmLayout']['layout'].empty?
65
+ gdc_logger.info "Project: '#{from_title}', PID: '#{from_pid}' has no ldm layout, skip synchronizing ldm layout."
66
+ else
67
+ to_projects.peach do |to|
68
+ pid = to[:pid]
69
+ to_project = client.projects(pid)
70
+ unless to_project
71
+ process_failed_project(pid, "Invalid 'from' project specified - '#{pid}'", failed_projects, collect_synced_status)
72
+ next
73
+ end
74
+
75
+ next if sync_failed_project(pid, params)
76
+
77
+ gdc_logger.info "Transferring ldm layout, from project: '#{from_title}', PID: '#{from_pid}', to project: '#{to_project.title}', PID: '#{to_project.pid}'"
78
+ res = to_project.save_ldm_layout(from_ldm_layout)
79
+
80
+ if res[:status] == 'OK' || !collect_synced_status
81
+ res[:from] = from_pid
82
+ results << res
83
+ else
84
+ warning_message = "Failed to transfer ldm layout from project: '#{from_pid}' to project: '#{to_project.title}'"
85
+ process_failed_project(pid, warning_message, failed_projects, collect_synced_status)
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ process_failed_projects(failed_projects, short_name, params) if collect_synced_status
92
+ # Return results
93
+ results.flatten
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,108 @@
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
+
10
+ require 'thread_safe'
11
+ require 'json'
12
+
13
+ module GoodData
14
+ module LCM2
15
+ class SynchronizePPDashboardPermissions < BaseAction
16
+ DESCRIPTION = 'Synchronize Pixel Perfect Dashboard Permission'
17
+
18
+ PARAMS = define_params(self) do
19
+ description 'Client Used for Connecting to GD'
20
+ param :gdc_gd_client, instance_of(Type::GdClientType), required: true
21
+
22
+ description 'Client used to connecting to development domain'
23
+ param :development_client, instance_of(Type::GdClientType), required: true
24
+
25
+ description 'Synchronization Info'
26
+ param :synchronize, array_of(instance_of(Type::SynchronizationInfoType)), required: true, generated: true
27
+
28
+ description 'Logger'
29
+ param :gdc_logger, instance_of(Type::GdLogger), required: true
30
+
31
+ description 'Additional Hidden Parameters'
32
+ param :additional_hidden_params, instance_of(Type::HashType), required: false
33
+
34
+ description 'Disable synchronizing dashboard permissions for Pixel Perfect dashboards'
35
+ param :disable_pp_dashboard_permission, instance_of(Type::BooleanType), required: false, default: false
36
+
37
+ description 'Abort on error'
38
+ param :abort_on_error, instance_of(Type::StringType), required: false
39
+
40
+ description 'Collect synced status'
41
+ param :collect_synced_status, instance_of(Type::BooleanType), required: false
42
+
43
+ description 'Sync failed list'
44
+ param :sync_failed_list, instance_of(Type::HashType), required: false
45
+ end
46
+
47
+ class << self
48
+ def call(params)
49
+ results = []
50
+ disable_pp_dashboard_permission = GoodData::Helpers.to_boolean(params.disable_pp_dashboard_permission)
51
+ collect_synced_status = collect_synced_status(params)
52
+ failed_projects = ThreadSafe::Array.new
53
+
54
+ if disable_pp_dashboard_permission
55
+ params.gdc_logger.info "Skip synchronize Pixel Perfect dashboard permission."
56
+ else
57
+ client = params.gdc_gd_client
58
+ development_client = params.development_client
59
+ failed_projects = ThreadSafe::Array.new
60
+
61
+ params.synchronize.peach do |info|
62
+ from_project = info.from
63
+ to_projects = info.to
64
+ sync_success = false
65
+
66
+ from = development_client.projects(from_project)
67
+ unless from
68
+ process_failed_project(from_project, "Invalid 'from' project specified - '#{from_project}'", failed_projects, collect_synced_status)
69
+ next
70
+ end
71
+
72
+ source_dashboards = from.dashboards
73
+
74
+ params.gdc_logger.info "Transferring Pixel Perfect Dashboard permission, from project: '#{from.title}', PID: '#{from.pid}' for dashboard(s): #{source_dashboards.map { |d| "#{d.title.inspect}" }.join(', ')}" # rubocop:disable Metrics/LineLength
75
+ to_projects.peach do |entry|
76
+ pid = entry[:pid]
77
+ next if sync_failed_project(pid, params)
78
+
79
+ to_project = client.projects(pid)
80
+ unless to_project
81
+ process_failed_project(pid, "Invalid 'to' project specified - '#{pid}'", failed_projects, collect_synced_status)
82
+ next
83
+ end
84
+
85
+ target_dashboards = to_project.dashboards
86
+ begin
87
+ GoodData::Project.transfer_dashboard_permission(from, to_project, source_dashboards, target_dashboards)
88
+ sync_success = true
89
+ rescue StandardError => err
90
+ process_failed_project(pid, err.message, failed_projects, collect_synced_status)
91
+ end
92
+ end
93
+
94
+ results << {
95
+ from_project_name: from.title,
96
+ from_project_pid: from.pid,
97
+ status: 'ok'
98
+ } if sync_success
99
+ end
100
+ end
101
+
102
+ process_failed_projects(failed_projects, short_name, params) if collect_synced_status
103
+ results
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -61,12 +61,13 @@ module GoodData
61
61
  to_projects = info.to
62
62
 
63
63
  from = development_client.projects(from_project) || fail("Invalid 'from' project specified - '#{from_project}'")
64
+ has_cycle_trigger = exist_cycle_trigger(from)
64
65
  to_projects.peach do |entry|
65
66
  pid = entry[:pid]
66
67
  to_project = client.projects(pid) || fail("Invalid 'to' project specified - '#{pid}'")
67
68
 
68
69
  params.gdc_logger.info "Transferring Schedules, from project: '#{from.title}', PID: '#{from.pid}', to project: '#{to_project.title}', PID: '#{to_project.pid}'"
69
- res = GoodData::Project.transfer_schedules(from, to_project).sort_by do |item|
70
+ res = GoodData::Project.transfer_schedules(from, to_project, has_cycle_trigger).sort_by do |item|
70
71
  item[:status]
71
72
  end
72
73
 
@@ -97,6 +98,35 @@ module GoodData
97
98
  # Return results
98
99
  results
99
100
  end
101
+
102
+ private
103
+
104
+ def exist_cycle_trigger(project)
105
+ schedules = project.schedules
106
+ triggers = {}
107
+ schedules.each do |schedule|
108
+ triggers[schedule.obj_id] = schedule.trigger_id if schedule.trigger_id
109
+ end
110
+
111
+ triggers.each do |schedule_id, trigger_id|
112
+ checking_id = trigger_id
113
+ if checking_id == schedule_id
114
+ return true
115
+ else
116
+ max_step = triggers.length
117
+ count = 1
118
+ loop do
119
+ checking_id = triggers[checking_id]
120
+ count += 1
121
+ break if !checking_id || count > max_step
122
+
123
+ return true if checking_id == schedule_id
124
+ end
125
+ end
126
+ end
127
+
128
+ false
129
+ end
100
130
  end
101
131
  end
102
132
  end