gooddata 0.6.50 → 0.6.51
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.editorconfig +12 -0
- data/.gitignore +1 -0
- data/CHANGELOG.md +3 -0
- data/gooddata.gemspec +2 -1
- data/lib/gooddata/bricks/middleware/aws_middleware.rb +4 -0
- data/lib/gooddata/bricks/middleware/decode_params_middleware.rb +1 -1
- data/lib/gooddata/bricks/middleware/dwh_middleware.rb +1 -0
- data/lib/gooddata/bricks/middleware/gooddata_middleware.rb +1 -0
- data/lib/gooddata/bricks/middleware/logger_middleware.rb +2 -1
- data/lib/gooddata/core/nil_logger.rb +9 -0
- data/lib/gooddata/goodzilla/goodzilla.rb +1 -1
- data/lib/gooddata/helpers/data_helper.rb +1 -0
- data/lib/gooddata/helpers/global_helpers_params.rb +54 -27
- data/lib/gooddata/lcm/actions/apply_custom_maql.rb +70 -0
- data/lib/gooddata/lcm/actions/associate_clients.rb +17 -4
- data/lib/gooddata/lcm/actions/collect_clients.rb +4 -1
- data/lib/gooddata/lcm/actions/collect_segment_clients.rb +1 -0
- data/lib/gooddata/lcm/actions/collect_segments.rb +15 -2
- data/lib/gooddata/lcm/actions/create_segment_masters.rb +2 -2
- data/lib/gooddata/lcm/actions/provision_clients.rb +2 -4
- data/lib/gooddata/lcm/actions/purge_clients.rb +2 -2
- data/lib/gooddata/lcm/actions/synchronize_etls_in_segment.rb +81 -0
- data/lib/gooddata/lcm/actions/synchronize_label_types.rb +2 -2
- data/lib/gooddata/lcm/actions/synchronize_processes.rb +3 -1
- data/lib/gooddata/lcm/data/create_lcm_release.sql.erb +2 -1
- data/lib/gooddata/lcm/helpers/check_helper.rb +1 -1
- data/lib/gooddata/lcm/lcm.rb +29 -11
- data/lib/gooddata/lcm/lcm2.rb +82 -20
- data/lib/gooddata/models/domain.rb +22 -1
- data/lib/gooddata/models/metadata.rb +13 -8
- data/lib/gooddata/models/metadata/attribute.rb +1 -1
- data/lib/gooddata/models/metadata/report_definition.rb +1 -0
- data/lib/gooddata/models/profile.rb +1 -1
- data/lib/gooddata/models/project.rb +162 -38
- data/lib/gooddata/models/project_creator.rb +26 -6
- data/lib/gooddata/models/project_log_formatter.rb +204 -0
- data/lib/gooddata/models/schedule.rb +2 -21
- data/lib/gooddata/models/segment.rb +26 -0
- data/lib/gooddata/models/style_setting.rb +5 -1
- data/lib/gooddata/models/user_filters/user_filter_builder.rb +9 -0
- data/lib/gooddata/rest/connection.rb +4 -1
- data/lib/gooddata/version.rb +1 -1
- data/spec/environment/development.rb +29 -0
- data/spec/environment/environment.rb +14 -2
- data/spec/environment/{hotfix.rb → testing.rb} +0 -0
- data/spec/integration/date_dim_switch_spec.rb +3 -5
- data/spec/integration/lcm_spec.rb +24 -21
- data/spec/integration/project_spec.rb +16 -0
- data/spec/integration/segments_spec.rb +1 -1
- data/spec/unit/helpers/global_helpers_spec.rb +26 -2
- data/spec/unit/helpers_spec.rb +20 -0
- data/spec/unit/models/project_creator_spec.rb +3 -2
- metadata +29 -12
- data/lib/gooddata/lcm/actions/ensure_titles.rb +0 -54
- data/spec/environment/develop.rb +0 -46
@@ -87,17 +87,22 @@ module GoodData
|
|
87
87
|
from_attribute, to_attribute = mapping.find { |k, _| k.uri == a_uri }
|
88
88
|
vals = from_attribute.values_for(id)
|
89
89
|
labels = to_attribute.labels
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
90
|
+
|
91
|
+
result = nil
|
92
|
+
catch :found_value do
|
93
|
+
labels.each do |l|
|
94
|
+
vals.each do |v|
|
95
|
+
throw :found_value if result
|
96
|
+
result = begin
|
97
|
+
l.find_value_uri(v)
|
98
|
+
rescue
|
99
|
+
nil
|
100
|
+
end
|
96
101
|
end
|
97
102
|
end
|
98
103
|
end
|
99
|
-
fail "Unable to find replacement for #{a_uri}"
|
100
|
-
[a_uri, id,
|
104
|
+
fail "Unable to find replacement for #{a_uri}" unless result
|
105
|
+
[a_uri, id, result]
|
101
106
|
end
|
102
107
|
replaceable_vals.map { |a, id, r| ["#{a}/elements?id=#{id}", r] }
|
103
108
|
end
|
@@ -116,7 +116,7 @@ module GoodData
|
|
116
116
|
# @return [Array] list of values for certain element. Returned in the same order as is the order of labels
|
117
117
|
def values_for(element_id)
|
118
118
|
# element_id = element_id.is_a?(String) ? element_id.match(/\?id=(\d)/)[1] : element_id
|
119
|
-
labels.
|
119
|
+
labels.pmap do |label|
|
120
120
|
label.find_element_value(element_id)
|
121
121
|
end
|
122
122
|
end
|
@@ -251,6 +251,7 @@ module GoodData
|
|
251
251
|
x = GoodData::MdObject.replace_quoted(self, mapping)
|
252
252
|
x = GoodData::MdObject.replace_bracketed(x, mapping)
|
253
253
|
vals = GoodData::MdObject.find_replaceable_values(self, mapping)
|
254
|
+
GoodData::MdObject.replace_quoted(x, vals)
|
254
255
|
GoodData::MdObject.replace_bracketed(x, vals)
|
255
256
|
end
|
256
257
|
|
@@ -60,7 +60,7 @@ module GoodData
|
|
60
60
|
return id if id.instance_of?(GoodData::Profile) || id.respond_to?(:profile?) && id.profile?
|
61
61
|
|
62
62
|
if id.to_s !~ %r{^(\/gdc\/account\/profile\/)?[a-zA-Z\d]+$}
|
63
|
-
fail(ArgumentError, 'wrong type of argument. Should be either
|
63
|
+
fail(ArgumentError, 'wrong type of argument. Should be either profile ID or path')
|
64
64
|
end
|
65
65
|
|
66
66
|
id = id.match(/[a-zA-Z\d]+$/)[0] if id =~ %r{/}
|
@@ -10,6 +10,7 @@ require 'fileutils'
|
|
10
10
|
require 'multi_json'
|
11
11
|
require 'pmap'
|
12
12
|
require 'zip'
|
13
|
+
require 'net/smtp'
|
13
14
|
|
14
15
|
require_relative '../exceptions/no_project_error'
|
15
16
|
|
@@ -23,6 +24,7 @@ require_relative '../mixins/uri_getter'
|
|
23
24
|
|
24
25
|
require_relative 'membership'
|
25
26
|
require_relative 'process'
|
27
|
+
require_relative 'project_log_formatter'
|
26
28
|
require_relative 'project_role'
|
27
29
|
require_relative 'blueprint/blueprint'
|
28
30
|
|
@@ -78,9 +80,7 @@ module GoodData
|
|
78
80
|
if id == :all
|
79
81
|
Project.all({ client: GoodData.connection }.merge(opts))
|
80
82
|
else
|
81
|
-
|
82
|
-
fail(ArgumentError, 'wrong type of argument. Should be either project ID or path')
|
83
|
-
end
|
83
|
+
fail(ArgumentError, 'wrong type of argument. Should be either project ID or path') unless project_id_or_path?(id)
|
84
84
|
|
85
85
|
id = id.match(/[a-zA-Z\d]+$/)[0] if id =~ %r{/}
|
86
86
|
|
@@ -90,6 +90,10 @@ module GoodData
|
|
90
90
|
end
|
91
91
|
end
|
92
92
|
|
93
|
+
def project_id_or_path?(id)
|
94
|
+
id.to_s =~ %r{^(\/gdc\/(projects|md)\/)?[a-zA-Z\d]+$}
|
95
|
+
end
|
96
|
+
|
93
97
|
# Clones project along with etl and schedules
|
94
98
|
#
|
95
99
|
# @param project [Project] Project to be cloned from
|
@@ -184,10 +188,40 @@ module GoodData
|
|
184
188
|
}
|
185
189
|
end
|
186
190
|
|
191
|
+
def transfer_output_stage(from_project, to_project, options)
|
192
|
+
if from_project.processes.any? { |p| p.type == :dataload }
|
193
|
+
if to_project.processes.any? { |p| p.type == :dataload }
|
194
|
+
to_project.add.output_stage.schema = from_project.add.output_stage.schema
|
195
|
+
to_project.add.output_stage.output_stage_prefix = from_project.add.output_stage.output_stage_prefix
|
196
|
+
to_project.add.output_stage.save
|
197
|
+
else
|
198
|
+
from_prj_output_stage = from_project.add.output_stage
|
199
|
+
from_server = from_project.client.connection.server.url
|
200
|
+
to_server = to_project.client.connection.server.url
|
201
|
+
if from_server != to_server && options[:ads_output_stage_uri].nil?
|
202
|
+
raise "Cannot transfer output stage from #{from_server} to #{to_server}. " \
|
203
|
+
'It is not possible to transfer output stages between ' \
|
204
|
+
'different domains. Please specify an address of an output ' \
|
205
|
+
'stage that is in the same domain as the target project ' \
|
206
|
+
'using the "ads_output_stage_uri" parameter.'
|
207
|
+
end
|
208
|
+
|
209
|
+
to_project.add.output_stage = GoodData::AdsOutputStage.create(
|
210
|
+
client: to_project.client,
|
211
|
+
ads: options[:ads_output_stage_uri] || from_prj_output_stage.schema,
|
212
|
+
client_id: from_prj_output_stage.client_id,
|
213
|
+
output_stage_prefix: from_prj_output_stage.output_stage_prefix,
|
214
|
+
project: to_project
|
215
|
+
)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
187
220
|
# Clones project along with etl and schedules.
|
188
221
|
#
|
189
222
|
# @param client [GoodData::Rest::Client] GoodData client to be used for connection
|
190
223
|
# @param from_project [GoodData::Project | GoodData::Segment | GoodData:Client | String] Object to be cloned from. Can be either segment in which case we take the master, client in which case we take its project, string in which case we treat is as an project object or directly project
|
224
|
+
# @param to_project [GoodData::Project | GoodData::Segment | GoodData:Client | String]
|
191
225
|
def transfer_etl(client, from_project, to_project)
|
192
226
|
from_project = case from_project
|
193
227
|
when GoodData::Client
|
@@ -210,7 +244,12 @@ module GoodData
|
|
210
244
|
transfer_schedules(from_project, to_project)
|
211
245
|
end
|
212
246
|
|
213
|
-
|
247
|
+
# @param from_project The source project
|
248
|
+
# @param to_project The target project
|
249
|
+
# @param options Optional parameters
|
250
|
+
# @option ads_output_stage_uri Uri of the source output stage. It must be in the same domain as the target project.
|
251
|
+
def transfer_processes(from_project, to_project, options = {})
|
252
|
+
options = GoodData::Helpers.symbolize_keys(options)
|
214
253
|
to_project_processes = to_project.processes
|
215
254
|
from_project.processes.uniq(&:name).each do |process|
|
216
255
|
fail "The process name #{process.name} must be unique in transfered project #{to_project}" if to_project_processes.count { |p| p.name == process.name } > 1
|
@@ -232,16 +271,8 @@ module GoodData
|
|
232
271
|
end
|
233
272
|
end
|
234
273
|
|
235
|
-
|
236
|
-
|
237
|
-
to_project.add.output_stage.schema = from_project.add.output_stage.schema
|
238
|
-
to_project.add.output_stage.output_stage_prefix = from_project.add.output_stage.output_stage_prefix
|
239
|
-
to_project.add.output_stage.save
|
240
|
-
else
|
241
|
-
from_prj_output_stage = from_project.add.output_stage
|
242
|
-
to_project.add.output_stage = GoodData::AdsOutputStage.create(client: to_project.client, ads: from_prj_output_stage.schema, client_id: from_prj_output_stage.client_id, output_stage_prefix: from_prj_output_stage.output_stage_prefix, project: to_project)
|
243
|
-
end
|
244
|
-
end
|
274
|
+
transfer_output_stage(from_project, to_project, options)
|
275
|
+
|
245
276
|
res = (from_project.processes + to_project.processes).map { |p| [p, p.name, p.type] }
|
246
277
|
res.group_by { |x| [x[1], x[2]] }
|
247
278
|
.select { |_, procs| procs.length == 1 && procs[2] != :dataload }
|
@@ -249,6 +280,27 @@ module GoodData
|
|
249
280
|
.peach(&:delete)
|
250
281
|
end
|
251
282
|
|
283
|
+
def transfer_user_groups(from_project, to_project)
|
284
|
+
from_project.user_groups.each do |ug|
|
285
|
+
# migrate groups
|
286
|
+
new_group = to_project.user_groups.select { |group| group.name == ug.name }.first
|
287
|
+
new_group ||= UserGroup.create(:name => ug.name, :description => ug.description, :project => to_project)
|
288
|
+
new_group.project = to_project
|
289
|
+
new_group.description = ug.description
|
290
|
+
new_group.save
|
291
|
+
# migrate dashboard "grantees"
|
292
|
+
dashboards = from_project.dashboards
|
293
|
+
dashboards.each do |dashboard|
|
294
|
+
new_dashboard = to_project.dashboards.select { |dash| dash.title == dashboard.title }.first
|
295
|
+
next unless new_dashboard
|
296
|
+
grantee = dashboard.grantees['granteeURIs']['items'].select { |item| item['aclEntryURI']['grantee'].split('/').last == ug.links['self'].split('/').last }.first
|
297
|
+
next unless grantee
|
298
|
+
permission = grantee['aclEntryURI']['permission']
|
299
|
+
new_dashboard.grant(:member => new_group, :permission => permission)
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
252
304
|
# Clones project along with etl and schedules.
|
253
305
|
#
|
254
306
|
# @param client [GoodData::Rest::Client] GoodData client to be used for connection
|
@@ -680,7 +732,7 @@ module GoodData
|
|
680
732
|
def delete_all_data(options = {})
|
681
733
|
return false unless options[:force]
|
682
734
|
begin
|
683
|
-
datasets.pmap(&:delete_data)
|
735
|
+
datasets.reject(&:date_dimension?).pmap(&:delete_data)
|
684
736
|
rescue MaqlExecutionError => e
|
685
737
|
# This is here so that we do not throw out exceptions on synchornizing date dimensions
|
686
738
|
# Currently there is no reliable way how to tell it is a date dimension
|
@@ -916,6 +968,7 @@ module GoodData
|
|
916
968
|
def initialize(json)
|
917
969
|
super
|
918
970
|
@json = json
|
971
|
+
@log_formatter = GoodData::ProjectLogFormatter.new(self)
|
919
972
|
end
|
920
973
|
|
921
974
|
# Invites new user to project
|
@@ -1252,14 +1305,16 @@ module GoodData
|
|
1252
1305
|
GoodData.logger.info "Saving #{new_item.uri}"
|
1253
1306
|
new_item.save
|
1254
1307
|
end
|
1308
|
+
else
|
1309
|
+
GoodData.logger.info "Ignore #{item.uri}"
|
1255
1310
|
end
|
1256
1311
|
end
|
1257
1312
|
end
|
1258
1313
|
|
1259
1314
|
GoodData.logger.info 'Replacing hidden metrics'
|
1260
|
-
local_metrics =
|
1315
|
+
local_metrics = mapping.map { |a, _| a }.pmapcat { |a| a.usedby('metric') }.select { |m| m['deprecated'] == '1' }.map { |m| m['link'] }.uniq
|
1261
1316
|
puts "Found #{local_metrics.count} metrics"
|
1262
|
-
local_metrics.pmap { |m| metrics(m
|
1317
|
+
local_metrics.pmap { |m| metrics(m) }.peach do |item|
|
1263
1318
|
new_item = item.replace(mapping)
|
1264
1319
|
if new_item.json != item.json
|
1265
1320
|
if dry_run
|
@@ -1268,6 +1323,8 @@ module GoodData
|
|
1268
1323
|
GoodData.logger.info "Saving #{new_item.uri}"
|
1269
1324
|
new_item.save
|
1270
1325
|
end
|
1326
|
+
else
|
1327
|
+
GoodData.logger.info "Ignore #{item.uri}"
|
1271
1328
|
end
|
1272
1329
|
end
|
1273
1330
|
|
@@ -1466,7 +1523,7 @@ module GoodData
|
|
1466
1523
|
whitelisted_new_users, whitelisted_users = whitelist_users(new_users.map(&:to_hash), users_list, options[:whitelists])
|
1467
1524
|
|
1468
1525
|
# First check that if groups are provided we have them set up
|
1469
|
-
check_groups(new_users.map(&:to_hash).flat_map { |u| u[:user_group] || [] }.uniq)
|
1526
|
+
check_groups(new_users.map(&:to_hash).flat_map { |u| u[:user_group] || [] }.uniq, options)
|
1470
1527
|
|
1471
1528
|
# conform the role on list of new users so we can diff them with the users coming from the project
|
1472
1529
|
diffable_new_with_default_role = whitelisted_new_users.map do |u|
|
@@ -1477,12 +1534,12 @@ module GoodData
|
|
1477
1534
|
intermediate_new = diffable_new_with_default_role.map do |u|
|
1478
1535
|
u[:role] = u[:role].map do |r|
|
1479
1536
|
role = get_role(r, role_list)
|
1480
|
-
role
|
1537
|
+
role ? role.uri : r
|
1481
1538
|
end
|
1482
1539
|
|
1483
1540
|
u[:role_title] = u[:role].map do |r|
|
1484
1541
|
role = get_role(r, role_list)
|
1485
|
-
role
|
1542
|
+
role ? role.title : r
|
1486
1543
|
end
|
1487
1544
|
|
1488
1545
|
if u[:role].all?(&:nil?)
|
@@ -1515,29 +1572,37 @@ module GoodData
|
|
1515
1572
|
return diff_results if options[:dry_run]
|
1516
1573
|
|
1517
1574
|
# Create new users
|
1518
|
-
u = diff[:added].map { |x| { user: x, role: x[:role] } }
|
1519
|
-
|
1520
1575
|
results = []
|
1521
1576
|
GoodData.logger.warn("Creating #{diff[:added].count} users in project (#{pid})")
|
1522
|
-
|
1577
|
+
to_create = diff[:added].map { |x| { user: x, role: x[:role] } }
|
1578
|
+
created_users_result = create_users(to_create, roles: role_list, project_users: whitelisted_users)
|
1579
|
+
@log_formatter.log_created_users(created_users_result, diff[:added])
|
1580
|
+
results.concat(created_users_result)
|
1581
|
+
send_mail_to_new_users(diff[:added], options[:email_options]) if options[:email_options] && !options[:email_options].empty? && !diff[:added].empty?
|
1523
1582
|
|
1524
1583
|
# # Update existing users
|
1525
1584
|
GoodData.logger.warn("Updating #{diff[:changed].count} users in project (#{pid})")
|
1526
|
-
|
1527
|
-
|
1585
|
+
to_update = diff[:changed].map { |x| { user: x[:new_obj], role: x[:new_obj][:role] || x[:new_obj][:roles] } }
|
1586
|
+
updated_users_result = set_users_roles(to_update, roles: role_list, project_users: whitelisted_users)
|
1587
|
+
@log_formatter.log_updated_users(updated_users_result, diff[:changed], role_list)
|
1588
|
+
results.concat(updated_users_result)
|
1528
1589
|
|
1529
1590
|
# Remove old users
|
1530
|
-
|
1531
|
-
GoodData.logger.warn("Disabling #{
|
1532
|
-
|
1591
|
+
to_disable = diff[:removed].reject { |user| user[:status] == 'DISABLED' || user[:status] == :disabled }
|
1592
|
+
GoodData.logger.warn("Disabling #{to_disable.count} users from project (#{pid})")
|
1593
|
+
disabled_users_result = disable_users(to_disable, roles: role_list, project_users: whitelisted_users)
|
1594
|
+
@log_formatter.log_disabled_users(disabled_users_result)
|
1595
|
+
results.concat(disabled_users_result)
|
1533
1596
|
|
1534
1597
|
# Remove old users completely
|
1535
1598
|
if options[:remove_users_from_project]
|
1536
|
-
to_remove = (
|
1599
|
+
to_remove = (to_disable + users(disabled: true).to_a).map(&:to_hash).uniq do |user|
|
1537
1600
|
user[:uri]
|
1538
1601
|
end
|
1539
1602
|
GoodData.logger.warn("Removing #{to_remove.count} users from project (#{pid})")
|
1540
|
-
|
1603
|
+
removed_users_result = remove_users(to_remove)
|
1604
|
+
@log_formatter.log_removed_users(removed_users_result)
|
1605
|
+
results.concat(removed_users_result)
|
1541
1606
|
end
|
1542
1607
|
|
1543
1608
|
# reassign to groups
|
@@ -1551,9 +1616,9 @@ module GoodData
|
|
1551
1616
|
a
|
1552
1617
|
end
|
1553
1618
|
mappings.group_by { |_, g| g }.each do |g, mapping|
|
1554
|
-
|
1555
|
-
|
1556
|
-
user_groups(g).set_members(
|
1619
|
+
remote_users = mapping.map { |user, _| user }.map { |login| users_lookup[login] && users_lookup[login].uri }.reject(&:nil?)
|
1620
|
+
next if remote_users.empty?
|
1621
|
+
user_groups(g).set_members(remote_users)
|
1557
1622
|
end
|
1558
1623
|
mentioned_groups = mappings.map(&:last).uniq
|
1559
1624
|
groups_to_cleanup = user_groups.reject { |g| mentioned_groups.include?(g.name) }
|
@@ -1589,15 +1654,21 @@ module GoodData
|
|
1589
1654
|
client.delete(url)
|
1590
1655
|
[{ type: :successful, operation: :user_deleted_from_project, user: u }]
|
1591
1656
|
rescue => e
|
1592
|
-
[{ type: :failed, message: e.message }]
|
1657
|
+
[{ type: :failed, message: e.message, user: u }]
|
1593
1658
|
end
|
1594
1659
|
end
|
1595
1660
|
end
|
1596
1661
|
|
1597
|
-
def check_groups(specified_groups)
|
1662
|
+
def check_groups(specified_groups, options = {})
|
1598
1663
|
groups = user_groups.map(&:name)
|
1599
1664
|
missing_groups = specified_groups - groups
|
1600
|
-
|
1665
|
+
if options[:create_non_existing_user_groups]
|
1666
|
+
missing_groups.each do |g|
|
1667
|
+
create_group(name: g, description: g)
|
1668
|
+
end
|
1669
|
+
else
|
1670
|
+
fail "All groups have to be specified before you try to import users. Groups that are currently in project are #{groups.join(',')} and you asked for #{missing_groups.join(',')}" unless missing_groups.empty?
|
1671
|
+
end
|
1601
1672
|
end
|
1602
1673
|
|
1603
1674
|
# Update user
|
@@ -1632,7 +1703,7 @@ module GoodData
|
|
1632
1703
|
login, roles = resolve_roles(user, desired_roles, options.merge(project_users: project_users, roles: role_list))
|
1633
1704
|
[{ :type => :successful, user: login, roles: roles }]
|
1634
1705
|
rescue => e
|
1635
|
-
[{ :type => :failed, :reason => e.message, user:
|
1706
|
+
[{ :type => :failed, :reason => e.message, user: user, roles: desired_roles }]
|
1636
1707
|
end
|
1637
1708
|
end
|
1638
1709
|
|
@@ -1705,7 +1776,7 @@ module GoodData
|
|
1705
1776
|
desired_roles = Array(desired_roles)
|
1706
1777
|
roles = desired_roles.map do |role_name|
|
1707
1778
|
role = get_role(role_name, role_list)
|
1708
|
-
fail ArgumentError, "Invalid role '#{role_name}' specified for user '#{user
|
1779
|
+
fail ArgumentError, "Invalid role '#{role_name}' specified for user '#{GoodData::Helpers.last_uri_part(user)}'" if role.nil?
|
1709
1780
|
role.uri
|
1710
1781
|
end
|
1711
1782
|
[user, roles]
|
@@ -1754,6 +1825,59 @@ module GoodData
|
|
1754
1825
|
|
1755
1826
|
private
|
1756
1827
|
|
1828
|
+
def send_mail_to_new_users(users, email_options)
|
1829
|
+
password = email_options[:email_password]
|
1830
|
+
from = email_options[:email_from]
|
1831
|
+
raise 'Missing sender email, please specify parameter "email_from"' unless from
|
1832
|
+
raise 'Missing authentication password, please specify parameter "email_password"' unless password
|
1833
|
+
template = get_email_template(email_options)
|
1834
|
+
smtp = Net::SMTP.new('relay1.na.intgdc.com', 25)
|
1835
|
+
smtp.enable_starttls OpenSSL::SSL::SSLContext.new("TLSv1_2_client")
|
1836
|
+
smtp.start('notifications.gooddata.com', 'gdc', password, :plain)
|
1837
|
+
users.each do |user|
|
1838
|
+
smtp.send_mail(get_email_body(template, user), from, user[:login])
|
1839
|
+
end
|
1840
|
+
end
|
1841
|
+
|
1842
|
+
def get_email_template(options)
|
1843
|
+
bucket = options[:email_template_bucket]
|
1844
|
+
path = options[:email_template_path]
|
1845
|
+
access_key = options[:email_template_access_key]
|
1846
|
+
secret_key = options[:email_template_secret_key]
|
1847
|
+
raise "Unable to connect to AWS. Parameter \"email_template_bucket\" seems to be empty" unless bucket
|
1848
|
+
raise "Unable to connect to AWS. Parameter \"email_template_path\" is missing" unless path
|
1849
|
+
raise "Unable to connect to AWS. Parameter \"email_template_access_key\" is missing" unless access_key
|
1850
|
+
raise "Unable to connect to AWS. Parameter \"email_template_secret_key\" is missing" unless secret_key
|
1851
|
+
args = {
|
1852
|
+
access_key_id: access_key,
|
1853
|
+
secret_access_key: secret_key,
|
1854
|
+
max_retries: 15,
|
1855
|
+
http_read_timeout: 120,
|
1856
|
+
http_open_timeout: 120
|
1857
|
+
}
|
1858
|
+
|
1859
|
+
server_side_encryption = options['email_server_side_encryption'] || false
|
1860
|
+
args['s3_server_side_encryption'] = :aes256 if server_side_encryption
|
1861
|
+
|
1862
|
+
s3 = AWS::S3.new(args)
|
1863
|
+
bucket = s3.buckets[bucket]
|
1864
|
+
process_email_template(bucket, path)
|
1865
|
+
end
|
1866
|
+
|
1867
|
+
def process_email_template(bucket, path)
|
1868
|
+
type = path.split('/').last.include?('.html') ? 'html' : 'txt'
|
1869
|
+
body = bucket.objects[path].read
|
1870
|
+
body.prepend("MIME-Version: 1.0\nContent-type: text/html\n") if type == 'html'
|
1871
|
+
body
|
1872
|
+
end
|
1873
|
+
|
1874
|
+
def get_email_body(template, user)
|
1875
|
+
template.gsub('${name}', "#{user[:first_name]} #{user[:last_name]}")
|
1876
|
+
.gsub('${role}', user[:role_title].count == 1 ? user[:role_title].first : user[:role_title].to_s)
|
1877
|
+
.gsub('${user_group}', user[:user_group].count == 1 ? user[:user_group].first : user[:user_group].to_s)
|
1878
|
+
.gsub('${project}', Project[user[:pid]].title)
|
1879
|
+
end
|
1880
|
+
|
1757
1881
|
def generate_user_payload(user_uri, status = 'ENABLED', roles_uri = nil)
|
1758
1882
|
payload = {
|
1759
1883
|
'user' => {
|
@@ -49,7 +49,10 @@ module GoodData
|
|
49
49
|
result = client.post(uri, bp.to_wire)
|
50
50
|
response = client.poll_on_code(result['asyncTask']['link']['poll'])
|
51
51
|
|
52
|
-
|
52
|
+
chunks = response['projectModelDiff']['updateScripts']
|
53
|
+
return [] if chunks.empty?
|
54
|
+
|
55
|
+
maqls = pick_correct_chunks(chunks, opts)
|
53
56
|
replaced_maqls = apply_replacements_on_maql(maqls, replacements)
|
54
57
|
|
55
58
|
unless dry_run
|
@@ -126,14 +129,31 @@ module GoodData
|
|
126
129
|
{ cascade_drops: chunk['updateScript']['cascadeDrops'], preserve_data: chunk['updateScript']['preserveData'], maql: chunk['updateScript']['maqlDdlChunks'], orig: chunk }
|
127
130
|
end
|
128
131
|
|
129
|
-
|
132
|
+
results_from_api = GoodData::Helpers.join(rules, stuff, [:cascade_drops, :preserve_data], [:cascade_drops, :preserve_data], inner: true).sort_by { |l| l[:priority] } || []
|
130
133
|
|
131
|
-
preference.
|
132
|
-
|
133
|
-
|
134
|
+
if preference.empty?
|
135
|
+
[results_from_api.first[:orig]]
|
136
|
+
else
|
137
|
+
results = results_from_api.dup
|
138
|
+
preference.each do |k, v|
|
139
|
+
results = results.select do |result|
|
140
|
+
result[k] == v
|
141
|
+
end
|
142
|
+
end
|
143
|
+
if results.empty?
|
144
|
+
available_chunks = results_from_api
|
145
|
+
.map do |result|
|
146
|
+
{
|
147
|
+
cascade_drops: result[:cascade_drops],
|
148
|
+
preserve_data: result[:preserve_data]
|
149
|
+
}
|
150
|
+
end
|
151
|
+
.map(&:to_s)
|
152
|
+
.join(', ')
|
153
|
+
fail "Synchronize LDM cannot proceed. Adjust your update_preferences and try again. Available chunks with preference: #{available_chunks}"
|
134
154
|
end
|
155
|
+
results.map { |result| result[:orig] }
|
135
156
|
end
|
136
|
-
(preference.empty? ? [results.first].compact : results).map { |result| result[:orig] }
|
137
157
|
end
|
138
158
|
|
139
159
|
private
|