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