gooddata 2.2.0-java → 2.3.0-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gdc-ii-config.yaml +42 -1
- data/.github/workflows/build.yml +14 -13
- data/.github/workflows/pre-merge.yml +13 -13
- data/.pronto.yml +1 -0
- data/.rubocop.yml +2 -14
- data/CHANGELOG.md +9 -0
- data/Dockerfile +13 -7
- data/Dockerfile.jruby +5 -5
- data/Dockerfile.ruby +5 -7
- data/Gemfile +4 -2
- data/README.md +5 -4
- data/Rakefile +1 -1
- data/SDK_VERSION +1 -1
- data/VERSION +1 -1
- data/bin/run_brick.rb +7 -0
- data/ci/mysql/pom.xml +6 -1
- data/ci/redshift/pom.xml +3 -4
- data/docker-compose.lcm.yml +42 -1
- data/docker-compose.yml +42 -0
- data/gooddata.gemspec +21 -22
- data/lcm.rake +9 -0
- data/lib/gooddata/bricks/base_pipeline.rb +26 -0
- data/lib/gooddata/bricks/brick.rb +0 -1
- data/lib/gooddata/bricks/middleware/execution_result_middleware.rb +3 -3
- data/lib/gooddata/bricks/pipeline.rb +2 -14
- data/lib/gooddata/cloud_resources/mysql/mysql_client.rb +18 -8
- data/lib/gooddata/cloud_resources/redshift/drivers/.gitkeepme +0 -0
- data/lib/gooddata/cloud_resources/redshift/redshift_client.rb +0 -2
- data/lib/gooddata/cloud_resources/snowflake/snowflake_client.rb +1 -1
- data/lib/gooddata/lcm/actions/base_action.rb +157 -0
- data/lib/gooddata/lcm/actions/collect_data_product.rb +2 -1
- data/lib/gooddata/lcm/actions/collect_projects_warning_status.rb +53 -0
- data/lib/gooddata/lcm/actions/collect_segment_clients.rb +14 -0
- data/lib/gooddata/lcm/actions/initialize_continue_on_error_option.rb +87 -0
- data/lib/gooddata/lcm/actions/migrate_gdc_date_dimension.rb +28 -2
- data/lib/gooddata/lcm/actions/provision_clients.rb +34 -5
- data/lib/gooddata/lcm/actions/synchronize_cas.rb +24 -4
- data/lib/gooddata/lcm/actions/synchronize_clients.rb +56 -4
- data/lib/gooddata/lcm/actions/synchronize_dataset_mappings.rb +28 -3
- data/lib/gooddata/lcm/actions/synchronize_etls_in_segment.rb +48 -11
- data/lib/gooddata/lcm/actions/synchronize_kd_dashboard_permission.rb +103 -0
- data/lib/gooddata/lcm/actions/synchronize_ldm.rb +60 -15
- data/lib/gooddata/lcm/actions/synchronize_ldm_layout.rb +98 -0
- data/lib/gooddata/lcm/actions/synchronize_pp_dashboard_permission.rb +108 -0
- data/lib/gooddata/lcm/actions/synchronize_schedules.rb +31 -1
- data/lib/gooddata/lcm/actions/synchronize_user_filters.rb +14 -9
- data/lib/gooddata/lcm/actions/synchronize_user_groups.rb +30 -4
- data/lib/gooddata/lcm/actions/synchronize_users.rb +11 -10
- data/lib/gooddata/lcm/actions/update_metric_formats.rb +21 -4
- data/lib/gooddata/lcm/exceptions/lcm_execution_warning.rb +15 -0
- data/lib/gooddata/lcm/helpers/check_helper.rb +19 -0
- data/lib/gooddata/lcm/lcm2.rb +45 -4
- data/lib/gooddata/lcm/user_bricks_helper.rb +9 -0
- data/lib/gooddata/mixins/inspector.rb +1 -1
- data/lib/gooddata/models/ldm_layout.rb +38 -0
- data/lib/gooddata/models/project.rb +197 -22
- data/lib/gooddata/models/project_creator.rb +83 -6
- data/lib/gooddata/models/segment.rb +2 -1
- data/lib/gooddata/models/user_filters/user_filter_builder.rb +104 -15
- data/lib/gooddata/rest/connection.rb +5 -3
- data/lib/gooddata/rest/phmap.rb +1 -0
- data/lib/gooddata.rb +1 -0
- data/lib/gooddata_brick_base.rb +35 -0
- data/sonar-project.properties +6 -0
- metadata +60 -55
- data/lib/gooddata/cloud_resources/redshift/drivers/log4j.properties +0 -15
@@ -35,14 +35,26 @@ module GoodData
|
|
35
35
|
|
36
36
|
description 'Localization query'
|
37
37
|
param :localization_query, instance_of(Type::StringType), required: false
|
38
|
+
|
39
|
+
description 'Abort on error'
|
40
|
+
param :abort_on_error, instance_of(Type::StringType), required: false
|
41
|
+
|
42
|
+
description 'Collect synced status'
|
43
|
+
param :collect_synced_status, instance_of(Type::BooleanType), required: false
|
44
|
+
|
45
|
+
description 'Sync failed list'
|
46
|
+
param :sync_failed_list, instance_of(Type::HashType), required: false
|
38
47
|
end
|
39
48
|
|
40
49
|
RESULT_HEADER = %i[action ok_clients error_clients]
|
41
50
|
|
42
51
|
class << self
|
43
52
|
def load_metric_data(params)
|
53
|
+
collect_synced_status = collect_synced_status(params)
|
54
|
+
|
44
55
|
if params&.dig(:input_source, :metric_format) && params[:input_source][:metric_format].present?
|
45
|
-
metric_input_source = validate_input_source(params[:input_source])
|
56
|
+
metric_input_source = validate_input_source(params[:input_source], collect_synced_status)
|
57
|
+
return nil unless metric_input_source
|
46
58
|
else
|
47
59
|
return nil
|
48
60
|
end
|
@@ -68,10 +80,14 @@ module GoodData
|
|
68
80
|
metrics_hash
|
69
81
|
end
|
70
82
|
|
71
|
-
def validate_input_source(input_source)
|
83
|
+
def validate_input_source(input_source, continue_on_error)
|
72
84
|
type = input_source[:type] if input_source&.dig(:type)
|
73
85
|
metric_format = input_source[:metric_format]
|
74
|
-
|
86
|
+
if type.blank?
|
87
|
+
raise "Incorrect configuration: 'type' of 'input_source' is required" unless continue_on_error
|
88
|
+
|
89
|
+
return nil
|
90
|
+
end
|
75
91
|
|
76
92
|
modified_input_source = input_source
|
77
93
|
case type
|
@@ -154,8 +170,9 @@ module GoodData
|
|
154
170
|
data_product_clients = data_product.clients
|
155
171
|
number_client_ok = 0
|
156
172
|
number_client_error = 0
|
173
|
+
collect_synced_status = collect_synced_status(params)
|
157
174
|
metric_group.each do |client_id, formats|
|
158
|
-
next
|
175
|
+
next if !updated_clients.include?(client_id) || (collect_synced_status && sync_failed_client(client_id, params))
|
159
176
|
|
160
177
|
client = data_product_clients.find { |c| c.id == client_id }
|
161
178
|
begin
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# (C) 2019-2022 GoodData Corporation
|
3
|
+
|
4
|
+
module GoodData
|
5
|
+
class LcmExecutionWarning < RuntimeError
|
6
|
+
DEFAULT_MSG = 'Existing errors during lcm execution'
|
7
|
+
|
8
|
+
attr_reader :summary_error
|
9
|
+
|
10
|
+
def initialize(summary_error, message = DEFAULT_MSG)
|
11
|
+
super(message)
|
12
|
+
@summary_error = summary_error
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -10,6 +10,9 @@ module GoodData
|
|
10
10
|
module LCM2
|
11
11
|
class Helpers
|
12
12
|
class << self
|
13
|
+
ABORT_ON_ERROR_PARAM = 'abort_on_error'.to_sym
|
14
|
+
COLLECT_SYNCED_STATUS = 'collect_synced_status'.to_sym
|
15
|
+
|
13
16
|
def check_params(specification, params)
|
14
17
|
specification.keys.each do |param_name|
|
15
18
|
value = params.send(param_name)
|
@@ -39,6 +42,22 @@ module GoodData
|
|
39
42
|
end
|
40
43
|
end
|
41
44
|
end
|
45
|
+
|
46
|
+
def continue_on_error(params)
|
47
|
+
params.include?(ABORT_ON_ERROR_PARAM) && !to_bool(ABORT_ON_ERROR_PARAM, params[ABORT_ON_ERROR_PARAM])
|
48
|
+
end
|
49
|
+
|
50
|
+
def collect_synced_status(params)
|
51
|
+
params.include?(COLLECT_SYNCED_STATUS) && to_bool(COLLECT_SYNCED_STATUS, params[COLLECT_SYNCED_STATUS])
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_bool(key, value)
|
55
|
+
return value if value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
56
|
+
return true if value =~ /^(true|t|yes|y|1)$/i
|
57
|
+
return false if value == '' || value =~ /^(false|f|no|n|0)$/i
|
58
|
+
|
59
|
+
raise ArgumentError, "Invalid '#{value}' boolean value for '#{key}' parameter"
|
60
|
+
end
|
42
61
|
end
|
43
62
|
end
|
44
63
|
end
|
data/lib/gooddata/lcm/lcm2.rb
CHANGED
@@ -17,6 +17,7 @@ require_relative 'actions/actions'
|
|
17
17
|
require_relative 'dsl/dsl'
|
18
18
|
require_relative 'helpers/helpers'
|
19
19
|
require_relative 'exceptions/lcm_execution_error'
|
20
|
+
require_relative 'exceptions/lcm_execution_warning'
|
20
21
|
|
21
22
|
using TrueExtensions
|
22
23
|
using FalseExtensions
|
@@ -97,10 +98,13 @@ module GoodData
|
|
97
98
|
ImportObjectCollections,
|
98
99
|
SynchronizeComputedAttributes,
|
99
100
|
SynchronizeDataSetMapping,
|
101
|
+
SynchronizeLdmLayout,
|
100
102
|
SynchronizeProcesses,
|
101
103
|
SynchronizeSchedules,
|
102
104
|
SynchronizeColorPalette,
|
103
105
|
SynchronizeUserGroups,
|
106
|
+
SynchronizePPDashboardPermissions,
|
107
|
+
SynchronizeKDDashboardPermissions,
|
104
108
|
SynchronizeNewSegments,
|
105
109
|
UpdateReleaseTable
|
106
110
|
],
|
@@ -122,13 +126,19 @@ module GoodData
|
|
122
126
|
CollectClients,
|
123
127
|
AssociateClients,
|
124
128
|
RenameExistingClientProjects,
|
129
|
+
InitializeContinueOnErrorOption,
|
125
130
|
ProvisionClients,
|
126
131
|
UpdateMetricFormats,
|
127
132
|
EnsureTechnicalUsersDomain,
|
128
133
|
EnsureTechnicalUsersProject,
|
129
134
|
CollectDymanicScheduleParams,
|
130
135
|
SynchronizeDataSetMapping,
|
131
|
-
|
136
|
+
SynchronizeLdmLayout,
|
137
|
+
SynchronizeUserGroups,
|
138
|
+
SynchronizePPDashboardPermissions,
|
139
|
+
SynchronizeKDDashboardPermissions,
|
140
|
+
SynchronizeETLsInSegment,
|
141
|
+
CollectProjectsWarningStatus
|
132
142
|
],
|
133
143
|
|
134
144
|
rollout: [
|
@@ -138,14 +148,20 @@ module GoodData
|
|
138
148
|
CollectSegmentClients,
|
139
149
|
EnsureTechnicalUsersDomain,
|
140
150
|
EnsureTechnicalUsersProject,
|
151
|
+
InitializeContinueOnErrorOption,
|
141
152
|
SynchronizeLdm,
|
142
153
|
SynchronizeDataSetMapping,
|
154
|
+
SynchronizeLdmLayout,
|
143
155
|
MigrateGdcDateDimension,
|
144
156
|
SynchronizeClients,
|
157
|
+
SynchronizeUserGroups,
|
158
|
+
SynchronizePPDashboardPermissions,
|
159
|
+
SynchronizeKDDashboardPermissions,
|
145
160
|
UpdateMetricFormats,
|
146
161
|
SynchronizeComputedAttributes,
|
147
162
|
CollectDymanicScheduleParams,
|
148
|
-
SynchronizeETLsInSegment
|
163
|
+
SynchronizeETLsInSegment,
|
164
|
+
CollectProjectsWarningStatus
|
149
165
|
],
|
150
166
|
|
151
167
|
users: [
|
@@ -340,7 +356,7 @@ module GoodData
|
|
340
356
|
# Invoke action
|
341
357
|
begin
|
342
358
|
out = run_action action, params
|
343
|
-
rescue Exception => e # rubocop:disable RescueException
|
359
|
+
rescue Exception => e # rubocop:disable Style/RescueException
|
344
360
|
errors << {
|
345
361
|
action: action,
|
346
362
|
err: e,
|
@@ -362,7 +378,7 @@ module GoodData
|
|
362
378
|
params.merge!(new_params)
|
363
379
|
|
364
380
|
# Print action result
|
365
|
-
print_action_result(action, res)
|
381
|
+
print_action_result(action, res) if action.send(:print_result, params)
|
366
382
|
|
367
383
|
# Store result for final summary
|
368
384
|
results << res
|
@@ -389,9 +405,28 @@ module GoodData
|
|
389
405
|
end
|
390
406
|
end
|
391
407
|
|
408
|
+
process_sync_failed_projects(params) if GoodData::LCM2::Helpers.collect_synced_status(params) && strict_mode
|
409
|
+
|
392
410
|
result
|
393
411
|
end
|
394
412
|
|
413
|
+
def process_sync_failed_projects(params)
|
414
|
+
sync_failed_list = params[:sync_failed_list]
|
415
|
+
sync_project_list = sync_failed_list[:project_client_mappings]
|
416
|
+
sync_failed_project_list = sync_failed_list[:failed_detailed_projects]
|
417
|
+
|
418
|
+
if sync_project_list && sync_failed_project_list && sync_project_list.size.positive? && sync_failed_project_list.size.positive?
|
419
|
+
failed_project = sync_failed_project_list[0]
|
420
|
+
summary_message = "Existing errors during execution. See log for details"
|
421
|
+
error_message = failed_project[:message]
|
422
|
+
if sync_project_list.size == sync_failed_project_list.size
|
423
|
+
raise GoodData::LcmExecutionError.new(summary_message, error_message)
|
424
|
+
else
|
425
|
+
raise GoodData::LcmExecutionWarning.new(summary_message, error_message)
|
426
|
+
end
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
395
430
|
def run_action(action, params)
|
396
431
|
begin
|
397
432
|
GoodData.gd_logger.start_action action, GoodData.gd_logger
|
@@ -401,6 +436,12 @@ module GoodData
|
|
401
436
|
BaseAction.check_params(action.const_get('PARAMS'), params)
|
402
437
|
params.setup_filters(action.const_get('PARAMS'))
|
403
438
|
out = action.send(:call, params)
|
439
|
+
rescue Exception => e # rubocop:disable Style/RescueException
|
440
|
+
# Log to splunk
|
441
|
+
GoodData.gd_logger.error("action=#{action} status=failed message=#{e} exception=#{e.backtrace}")
|
442
|
+
# Log to execution log
|
443
|
+
GoodData.logger.error("Execution #{action} failed. Error: #{e}. Detail:#{e.backtrace}")
|
444
|
+
raise e
|
404
445
|
ensure
|
405
446
|
params.clear_filters
|
406
447
|
GoodData.gd_logger.end_action GoodData.gd_logger
|
@@ -26,6 +26,15 @@ module GoodData
|
|
26
26
|
|
27
27
|
goodot_id.empty? ? client.id : goodot_id
|
28
28
|
end
|
29
|
+
|
30
|
+
def non_working_clients(domain_clients, working_client_ids)
|
31
|
+
non_working_clients = []
|
32
|
+
domain_clients.each do |c|
|
33
|
+
non_working_clients << c unless working_client_ids.include?(c.client_id.to_s)
|
34
|
+
end
|
35
|
+
|
36
|
+
non_working_clients
|
37
|
+
end
|
29
38
|
end
|
30
39
|
end
|
31
40
|
end
|
@@ -10,7 +10,7 @@ module GoodData
|
|
10
10
|
module Mixin
|
11
11
|
# When an RSpec test like this fails,
|
12
12
|
#
|
13
|
-
# @my_array.
|
13
|
+
# expect(@my_array).to == [@some_model, @some_model2]
|
14
14
|
#
|
15
15
|
# RSpec will call inspect on each of the objects to "help" you figure out
|
16
16
|
# what went wrong. Well, inspect will usually dump a TON OF SHIT and make trying
|
@@ -0,0 +1,38 @@
|
|
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
|
+
module GoodData
|
9
|
+
class LdmLayout
|
10
|
+
DEFAULT_EMPTY_LDM_LAYOUT = {
|
11
|
+
"ldmLayout" => {
|
12
|
+
"layout" => []
|
13
|
+
}
|
14
|
+
}
|
15
|
+
|
16
|
+
LDM_LAYOUT_URI = '/gdc/dataload/internal/projects/%<project_id>s/ldmLayout'
|
17
|
+
|
18
|
+
class << self
|
19
|
+
def get(opts = { :client => GoodData.connection, :project => GoodData.project })
|
20
|
+
client, project = GoodData.get_client_and_project(opts)
|
21
|
+
get_uri = LDM_LAYOUT_URI % { project_id: project.pid }
|
22
|
+
|
23
|
+
client.get(get_uri)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize(data)
|
28
|
+
@data = data
|
29
|
+
end
|
30
|
+
|
31
|
+
def save(opts)
|
32
|
+
client, project = GoodData.get_client_and_project(opts)
|
33
|
+
post_uri = LDM_LAYOUT_URI % { project_id: project.pid }
|
34
|
+
|
35
|
+
client.post(post_uri, @data, opts)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -31,6 +31,7 @@ require_relative 'project_log_formatter'
|
|
31
31
|
require_relative 'project_role'
|
32
32
|
require_relative 'blueprint/blueprint'
|
33
33
|
require_relative 'dataset_mapping'
|
34
|
+
require_relative 'ldm_layout'
|
34
35
|
|
35
36
|
require_relative 'metadata/scheduled_mail'
|
36
37
|
require_relative 'metadata/scheduled_mail/dashboard_attachment'
|
@@ -272,6 +273,29 @@ module GoodData
|
|
272
273
|
}
|
273
274
|
end
|
274
275
|
|
276
|
+
def get_ldm_layout(from_project)
|
277
|
+
GoodData::LdmLayout.get(:client => from_project.client, :project => from_project)
|
278
|
+
rescue StandardError => e
|
279
|
+
GoodData.logger.warn "An unexpected error when get ldm layout. Error: #{e.message}"
|
280
|
+
GoodData::LdmLayout::DEFAULT_EMPTY_LDM_LAYOUT
|
281
|
+
end
|
282
|
+
|
283
|
+
def save_ldm_layout(ldm_layout_json, to_project)
|
284
|
+
ldm_layout = GoodData::LdmLayout.new(ldm_layout_json)
|
285
|
+
begin
|
286
|
+
ldm_layout.save(:client => to_project.client, :project => to_project)
|
287
|
+
status = "OK"
|
288
|
+
rescue StandardError => e
|
289
|
+
GoodData.logger.warn "An unexpected error when save ldm layout. Error: #{e.message}"
|
290
|
+
status = "Failed"
|
291
|
+
end
|
292
|
+
|
293
|
+
{
|
294
|
+
to: to_project.pid,
|
295
|
+
status: status
|
296
|
+
}
|
297
|
+
end
|
298
|
+
|
275
299
|
# @param from_project The source project
|
276
300
|
# @param to_project The target project
|
277
301
|
# @param options Optional parameters
|
@@ -416,16 +440,6 @@ module GoodData
|
|
416
440
|
new_group.project = to_project
|
417
441
|
new_group.description = ug.description
|
418
442
|
new_group.save
|
419
|
-
# migrate dashboard "grantees"
|
420
|
-
dashboards = from_project.dashboards
|
421
|
-
dashboards.each do |dashboard|
|
422
|
-
new_dashboard = to_project.dashboards.select { |dash| dash.title == dashboard.title }.first
|
423
|
-
next unless new_dashboard
|
424
|
-
grantee = dashboard.grantees['granteeURIs']['items'].select { |item| item['aclEntryURI']['grantee'].split('/').last == ug.links['self'].split('/').last }.first
|
425
|
-
next unless grantee
|
426
|
-
permission = grantee['aclEntryURI']['permission']
|
427
|
-
new_dashboard.grant(:member => new_group, :permission => permission)
|
428
|
-
end
|
429
443
|
|
430
444
|
{
|
431
445
|
from: from_project.pid,
|
@@ -436,6 +450,68 @@ module GoodData
|
|
436
450
|
end
|
437
451
|
end
|
438
452
|
|
453
|
+
def transfer_dashboard_permission(from_project, to_project, source_dashboards, target_dashboards)
|
454
|
+
source_user_groups = from_project.user_groups
|
455
|
+
target_user_groups = to_project.user_groups
|
456
|
+
|
457
|
+
source_dashboards.each do |source_dashboard|
|
458
|
+
target_dashboard = target_dashboards.select { |dash| dash.title == source_dashboard.title }.first
|
459
|
+
next unless target_dashboard
|
460
|
+
|
461
|
+
begin
|
462
|
+
source_group_dashboards = dashboard_user_groups(source_user_groups, source_dashboard)
|
463
|
+
target_group_dashboards = dashboard_user_groups(target_user_groups, target_dashboard)
|
464
|
+
|
465
|
+
common_group_names = source_group_dashboards.flat_map { |s| target_group_dashboards.select { |t| t[:name] == s[:name] } }.map { |x| [x[:name], true] }.to_h
|
466
|
+
|
467
|
+
remove_user_groups_from_dashboard(target_group_dashboards, target_dashboard, common_group_names)
|
468
|
+
add_user_groups_to_dashboard(source_group_dashboards, target_dashboard, common_group_names, target_user_groups)
|
469
|
+
rescue StandardError => e
|
470
|
+
GoodData.logger.warn "Failed to synchronize dashboard permission from project: '#{from_project.title}', PID: '#{from_project.pid}' to project: '#{to_project.title}', PID: '#{to_project.pid}', dashboard: '#{target_dashboard.title}', dashboard uri: '#{target_dashboard.uri}' . Error: #{e.message} - #{e}" # rubocop:disable Metrics/LineLength
|
471
|
+
end
|
472
|
+
end
|
473
|
+
end
|
474
|
+
|
475
|
+
def dashboard_user_groups(user_groups, dashboard)
|
476
|
+
group_dashboards = []
|
477
|
+
dashboard_grantees = dashboard.grantees['granteeURIs']['items'].select { |item| item['aclEntryURI']['grantee'].include?('/usergroups/') }
|
478
|
+
|
479
|
+
dashboard_grantees.each do |dashboard_grantee|
|
480
|
+
permission = dashboard_grantee['aclEntryURI']['permission']
|
481
|
+
group_id = dashboard_grantee['aclEntryURI']['grantee'].split('/').last
|
482
|
+
user_group = user_groups.select { |group| group.links['self'].split('/').last == group_id }.first
|
483
|
+
next unless user_group
|
484
|
+
|
485
|
+
group_dashboards << {
|
486
|
+
name: user_group.name,
|
487
|
+
user_group: user_group,
|
488
|
+
permission: permission
|
489
|
+
}
|
490
|
+
end
|
491
|
+
group_dashboards
|
492
|
+
end
|
493
|
+
|
494
|
+
def remove_user_groups_from_dashboard(group_dashboards, dashboard, common_group_names)
|
495
|
+
group_dashboards.each do |group_dashboard|
|
496
|
+
group_name = group_dashboard[:name]
|
497
|
+
next if common_group_names && common_group_names[group_name]
|
498
|
+
|
499
|
+
dashboard.revoke(:member => group_dashboard[:user_group], :permission => group_dashboard[:permission])
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
503
|
+
def add_user_groups_to_dashboard(group_dashboards, dashboard, common_group_names, target_user_groups)
|
504
|
+
group_dashboards.each do |group_dashboard|
|
505
|
+
group_name = group_dashboard[:name]
|
506
|
+
next if common_group_names && common_group_names[group_name]
|
507
|
+
|
508
|
+
target_user_group = target_user_groups.select { |group| group.name == group_name }.first
|
509
|
+
next unless target_user_group
|
510
|
+
|
511
|
+
dashboard.grant(:member => target_user_group, :permission => group_dashboard[:permission])
|
512
|
+
end
|
513
|
+
end
|
514
|
+
|
439
515
|
# Clones project along with etl and schedules.
|
440
516
|
#
|
441
517
|
# @param client [GoodData::Rest::Client] GoodData client to be used for connection
|
@@ -443,7 +519,7 @@ module GoodData
|
|
443
519
|
# Object to be cloned from. Can be either segment in which case we take
|
444
520
|
# the master, client in which case we take its project, string in which
|
445
521
|
# case we treat is as an project object or directly project.
|
446
|
-
def transfer_schedules(from_project, to_project)
|
522
|
+
def transfer_schedules(from_project, to_project, has_cycle_trigger = false)
|
447
523
|
to_project_processes = to_project.processes.sort_by(&:name)
|
448
524
|
from_project_processes = from_project.processes.sort_by(&:name)
|
449
525
|
from_project_processes.reject!(&:add_v2_component?)
|
@@ -496,15 +572,23 @@ module GoodData
|
|
496
572
|
end
|
497
573
|
|
498
574
|
results = []
|
575
|
+
update_trigger_schedules = []
|
499
576
|
loop do # rubocop:disable Metrics/BlockLength
|
500
577
|
break if stack.empty?
|
501
578
|
state, changed_schedule = stack.shift
|
579
|
+
lazy_update_trigger_info = false
|
502
580
|
if state == :added
|
503
581
|
schedule_spec = changed_schedule
|
504
582
|
if schedule_spec[:after] && !schedule_cache[schedule_spec[:after]]
|
505
|
-
|
506
|
-
|
583
|
+
if has_cycle_trigger
|
584
|
+
# The schedule is triggered by another schedule
|
585
|
+
lazy_update_trigger_info = true
|
586
|
+
else
|
587
|
+
stack << [state, schedule_spec]
|
588
|
+
next
|
589
|
+
end
|
507
590
|
end
|
591
|
+
|
508
592
|
remote_process, process_spec = cache.find do |_remote, local, schedule|
|
509
593
|
(schedule_spec[:process_id] == local.process_id) && (schedule.name == schedule_spec[:name])
|
510
594
|
end
|
@@ -517,8 +601,21 @@ module GoodData
|
|
517
601
|
if process_spec.type != :dataload
|
518
602
|
executable = schedule_spec[:executable] || (process_spec.type == :ruby ? 'main.rb' : 'main.grf')
|
519
603
|
end
|
604
|
+
|
520
605
|
params = schedule_parameters(schedule_spec)
|
521
|
-
|
606
|
+
|
607
|
+
if lazy_update_trigger_info
|
608
|
+
# Temporary update nil for trigger info. The trigger info will be update late after transfer all schedules
|
609
|
+
created_schedule = remote_process.create_schedule(nil, executable, params)
|
610
|
+
update_trigger_schedules << {
|
611
|
+
state: :added,
|
612
|
+
schedule: created_schedule,
|
613
|
+
after: schedule_spec[:after]
|
614
|
+
}
|
615
|
+
else
|
616
|
+
created_schedule = remote_process.create_schedule(schedule_spec[:cron] || schedule_cache[schedule_spec[:after]], executable, params)
|
617
|
+
end
|
618
|
+
|
522
619
|
schedule_cache[created_schedule.name] = created_schedule
|
523
620
|
|
524
621
|
results << {
|
@@ -529,8 +626,13 @@ module GoodData
|
|
529
626
|
else
|
530
627
|
schedule_spec = changed_schedule[:new_obj]
|
531
628
|
if schedule_spec[:after] && !schedule_cache[schedule_spec[:after]]
|
532
|
-
|
533
|
-
|
629
|
+
if has_cycle_trigger
|
630
|
+
# The schedule is triggered by another schedule
|
631
|
+
lazy_update_trigger_info = true
|
632
|
+
else
|
633
|
+
stack << [state, schedule_spec]
|
634
|
+
next
|
635
|
+
end
|
534
636
|
end
|
535
637
|
|
536
638
|
remote_process, process_spec = cache.find do |i|
|
@@ -543,8 +645,12 @@ module GoodData
|
|
543
645
|
|
544
646
|
schedule.params = (schedule_spec[:params] || {})
|
545
647
|
schedule.cron = schedule_spec[:cron] if schedule_spec[:cron]
|
546
|
-
|
547
|
-
|
648
|
+
|
649
|
+
unless lazy_update_trigger_info
|
650
|
+
schedule.after = schedule_cache[schedule_spec[:after]] if schedule_spec[:after]
|
651
|
+
schedule.trigger_execution_status = schedule_cache[schedule_spec[:trigger_execution_status]] if schedule_spec[:after]
|
652
|
+
end
|
653
|
+
|
548
654
|
schedule.hidden_params = schedule_spec[:hidden_params] || {}
|
549
655
|
if process_spec.type != :dataload
|
550
656
|
schedule.executable = schedule_spec[:executable] || (process_spec.type == :ruby ? 'main.rb' : 'main.grf')
|
@@ -556,6 +662,15 @@ module GoodData
|
|
556
662
|
schedule.save
|
557
663
|
schedule_cache[schedule.name] = schedule
|
558
664
|
|
665
|
+
if lazy_update_trigger_info
|
666
|
+
update_trigger_schedules << {
|
667
|
+
state: :changed,
|
668
|
+
schedule: schedule,
|
669
|
+
after: schedule_spec[:after],
|
670
|
+
trigger_execution_status: schedule_spec[:trigger_execution_status]
|
671
|
+
}
|
672
|
+
end
|
673
|
+
|
559
674
|
results << {
|
560
675
|
state: :changed,
|
561
676
|
process: remote_process,
|
@@ -564,6 +679,22 @@ module GoodData
|
|
564
679
|
end
|
565
680
|
end
|
566
681
|
|
682
|
+
if has_cycle_trigger
|
683
|
+
update_trigger_schedules.each do |update_trigger_schedule|
|
684
|
+
working_schedule = update_trigger_schedule[:schedule]
|
685
|
+
working_schedule.after = schedule_cache[update_trigger_schedule[:after]]
|
686
|
+
working_schedule.trigger_execution_status = schedule_cache[update_trigger_schedule[:trigger_execution_status]] if update_trigger_schedule[:state] == :changed
|
687
|
+
|
688
|
+
# Update trigger info
|
689
|
+
working_schedule.save
|
690
|
+
|
691
|
+
# Update transfer result
|
692
|
+
results.each do |transfer_result|
|
693
|
+
transfer_result[:schedule] = working_schedule if transfer_result[:schedule].obj_id == working_schedule.obj_id
|
694
|
+
end
|
695
|
+
end
|
696
|
+
end
|
697
|
+
|
567
698
|
diff[:removed].each do |removed_schedule|
|
568
699
|
GoodData.logger.info("Removing schedule #{removed_schedule[:name]}")
|
569
700
|
|
@@ -871,6 +1002,14 @@ module GoodData
|
|
871
1002
|
GoodData::Dashboard[id, project: self, client: client]
|
872
1003
|
end
|
873
1004
|
|
1005
|
+
# Helper for getting analytical dashboards (KD dashboards) of a project
|
1006
|
+
#
|
1007
|
+
# @param id [String | Number | Object] Anything that you can pass to GoodData::Dashboard[id]
|
1008
|
+
# @return [GoodData::AnalyticalDashboard | Array<GoodData::AnalyticalDashboard>] dashboard instance or list
|
1009
|
+
def analytical_dashboards(id = :all)
|
1010
|
+
GoodData::AnalyticalDashboard[id, project: self, client: client]
|
1011
|
+
end
|
1012
|
+
|
874
1013
|
def data_permissions(id = :all)
|
875
1014
|
GoodData::MandatoryUserFilter[id, client: client, project: self]
|
876
1015
|
end
|
@@ -1797,15 +1936,15 @@ module GoodData
|
|
1797
1936
|
end
|
1798
1937
|
|
1799
1938
|
# reassign to groups
|
1939
|
+
removal_user_group_members = []
|
1800
1940
|
mappings = new_users.map(&:to_hash).flat_map do |user|
|
1941
|
+
removal_user_group_members << user[:login] if user[:user_group]&.empty?
|
1801
1942
|
groups = user[:user_group] || []
|
1802
1943
|
groups.map { |g| [user[:login], g] }
|
1803
1944
|
end
|
1945
|
+
|
1804
1946
|
unless mappings.empty?
|
1805
|
-
users_lookup =
|
1806
|
-
a[e.login] = e
|
1807
|
-
a
|
1808
|
-
end
|
1947
|
+
users_lookup = login_users
|
1809
1948
|
mappings.group_by { |_, g| g }.each do |g, mapping|
|
1810
1949
|
remote_users = mapping.map { |user, _| user }.map { |login| users_lookup[login] && users_lookup[login].uri }.reject(&:nil?)
|
1811
1950
|
GoodData.logger.info("Assigning users #{remote_users} to group #{g}")
|
@@ -1819,14 +1958,42 @@ module GoodData
|
|
1819
1958
|
end
|
1820
1959
|
mentioned_groups = mappings.map(&:last).uniq
|
1821
1960
|
groups_to_cleanup = user_groups_cache.reject { |g| mentioned_groups.include?(g.name) }
|
1961
|
+
|
1822
1962
|
# clean all groups not mentioned with exception of whitelisted users
|
1823
1963
|
groups_to_cleanup.each do |g|
|
1824
1964
|
g.set_members(whitelist_users(g.members.map(&:to_hash), [], options[:whitelists], :include).first.map { |x| x[:uri] })
|
1825
1965
|
end
|
1826
1966
|
end
|
1967
|
+
|
1968
|
+
remove_member_from_group(users_lookup, removal_user_group_members, user_groups_cache)
|
1827
1969
|
GoodData::Helpers.join(results, diff_results, [:user], [:login_uri])
|
1828
1970
|
end
|
1829
1971
|
|
1972
|
+
def remove_member_from_group(users_lookup, removal_user_group_members, user_groups_cache)
|
1973
|
+
unless removal_user_group_members.empty?
|
1974
|
+
users_lookup ||= login_users
|
1975
|
+
current_user_groups = user_groups_cache || user_groups
|
1976
|
+
removal_user_group_members.uniq.each do |login|
|
1977
|
+
user_uri = users_lookup[login]&.uri
|
1978
|
+
|
1979
|
+
# remove user from group if exists as group member
|
1980
|
+
current_user_groups.each do |user_group|
|
1981
|
+
if user_group.member?(user_uri)
|
1982
|
+
GoodData.logger.info("Removing #{user_uri} user from group #{user_group.name}")
|
1983
|
+
user_group.remove_members(user_uri)
|
1984
|
+
end
|
1985
|
+
end
|
1986
|
+
end
|
1987
|
+
end
|
1988
|
+
end
|
1989
|
+
|
1990
|
+
def login_users
|
1991
|
+
users.reduce({}) do |a, e|
|
1992
|
+
a[e.login] = e
|
1993
|
+
a
|
1994
|
+
end
|
1995
|
+
end
|
1996
|
+
|
1830
1997
|
def disable_users(list, options = {})
|
1831
1998
|
list = list.map(&:to_hash)
|
1832
1999
|
url = "#{uri}/users"
|
@@ -2047,6 +2214,14 @@ module GoodData
|
|
2047
2214
|
GoodData::Project.update_dataset_mapping(model_mapping_json, self)
|
2048
2215
|
end
|
2049
2216
|
|
2217
|
+
def ldm_layout
|
2218
|
+
GoodData::Project.get_ldm_layout(self)
|
2219
|
+
end
|
2220
|
+
|
2221
|
+
def save_ldm_layout(ldm_layout_json)
|
2222
|
+
GoodData::Project.save_ldm_layout(ldm_layout_json, self)
|
2223
|
+
end
|
2224
|
+
|
2050
2225
|
def transfer_processes(target)
|
2051
2226
|
GoodData::Project.transfer_processes(self, target)
|
2052
2227
|
end
|