gooddata 2.1.8-java → 2.1.13-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +7 -0
- data/.travis.yml +2 -4
- data/CHANGELOG.md +43 -0
- data/Dockerfile +19 -4
- data/Dockerfile.jruby +4 -4
- data/Dockerfile.ruby +5 -4
- data/README.md +2 -0
- data/SDK_VERSION +1 -1
- data/VERSION +1 -1
- data/bin/provision.sh +2 -0
- data/bin/release.sh +2 -0
- data/bin/rollout.sh +2 -0
- data/bin/run_brick.rb +31 -7
- data/bin/test_projects_cleanup.rb +10 -2
- data/bin/user_filters.sh +2 -0
- data/ci.rake +1 -1
- data/ci/bigquery/pom.xml +54 -0
- data/ci/redshift/pom.xml +73 -0
- data/ci/snowflake/pom.xml +57 -0
- data/dev-gooddata-sso.pub.encrypted +40 -40
- data/gdc_fossa_lcm.yaml +2 -0
- data/gdc_fossa_ruby_sdk.yaml +4 -0
- data/gooddata.gemspec +6 -2
- data/k8s/charts/lcm-bricks/Chart.yaml +1 -1
- data/k8s/charts/lcm-bricks/templates/prometheus/alertingRules.yaml +22 -12
- data/lcm.rake +14 -0
- data/lib/gooddata/bricks/middleware/execution_result_middleware.rb +68 -0
- data/lib/gooddata/bricks/middleware/logger_middleware.rb +2 -1
- data/lib/gooddata/bricks/middleware/mask_logger_decorator.rb +5 -1
- data/lib/gooddata/bricks/pipeline.rb +7 -0
- data/lib/gooddata/cloud_resources/bigquery/bigquery_client.rb +86 -0
- data/lib/gooddata/cloud_resources/bigquery/drivers/.gitkeepme +0 -0
- data/lib/gooddata/cloud_resources/cloud_resouce_factory.rb +28 -0
- data/lib/gooddata/cloud_resources/cloud_resource_client.rb +24 -0
- data/lib/gooddata/cloud_resources/cloud_resources.rb +12 -0
- data/lib/gooddata/cloud_resources/redshift/drivers/log4j.properties +15 -0
- data/lib/gooddata/cloud_resources/redshift/redshift_client.rb +101 -0
- data/lib/gooddata/cloud_resources/snowflake/drivers/.gitkeepme +0 -0
- data/lib/gooddata/cloud_resources/snowflake/snowflake_client.rb +84 -0
- data/lib/gooddata/exceptions/invalid_env_error.rb +15 -0
- data/lib/gooddata/helpers/data_helper.rb +10 -0
- data/lib/gooddata/helpers/data_source_helpers.rb +47 -0
- data/lib/gooddata/helpers/global_helpers.rb +4 -0
- data/lib/gooddata/helpers/global_helpers_params.rb +6 -9
- data/lib/gooddata/lcm/actions/collect_clients.rb +6 -6
- data/lib/gooddata/lcm/actions/collect_dynamic_schedule_params.rb +6 -6
- data/lib/gooddata/lcm/actions/collect_segment_clients.rb +4 -1
- data/lib/gooddata/lcm/actions/collect_segments.rb +1 -2
- data/lib/gooddata/lcm/actions/collect_users_brick_users.rb +7 -6
- data/lib/gooddata/lcm/actions/create_segment_masters.rb +5 -3
- data/lib/gooddata/lcm/actions/migrate_gdc_date_dimension.rb +116 -0
- data/lib/gooddata/lcm/actions/set_master_project.rb +76 -0
- data/lib/gooddata/lcm/actions/synchronize_clients.rb +1 -1
- data/lib/gooddata/lcm/actions/synchronize_etls_in_segment.rb +1 -2
- data/lib/gooddata/lcm/actions/synchronize_ldm.rb +20 -3
- data/lib/gooddata/lcm/actions/synchronize_user_filters.rb +23 -3
- data/lib/gooddata/lcm/actions/synchronize_users.rb +50 -30
- data/lib/gooddata/lcm/actions/update_release_table.rb +7 -1
- data/lib/gooddata/lcm/exceptions/lcm_execution_error.rb +16 -0
- data/lib/gooddata/lcm/helpers/release_table_helper.rb +16 -8
- data/lib/gooddata/lcm/lcm2.rb +28 -5
- data/lib/gooddata/models/domain.rb +17 -15
- data/lib/gooddata/models/execution.rb +0 -1
- data/lib/gooddata/models/execution_detail.rb +0 -1
- data/lib/gooddata/models/from_wire.rb +1 -0
- data/lib/gooddata/models/process.rb +11 -3
- data/lib/gooddata/models/profile.rb +33 -11
- data/lib/gooddata/models/project.rb +120 -31
- data/lib/gooddata/models/project_creator.rb +2 -0
- data/lib/gooddata/models/schedule.rb +0 -1
- data/lib/gooddata/rest/client.rb +2 -2
- data/lib/gooddata/rest/connection.rb +5 -3
- data/rubydev_public.gpg.encrypted +51 -51
- data/rubydev_secret_keys.gpg.encrypted +109 -109
- metadata +32 -13
- data/lib/gooddata/extensions/hash.rb +0 -18
@@ -324,20 +324,42 @@ module GoodData
|
|
324
324
|
end
|
325
325
|
|
326
326
|
# Gets the array of projects
|
327
|
-
#
|
327
|
+
# @param limit [Integer] maximum number of projects to get.
|
328
|
+
# @param offset [Integer] offset of the first project, start from 0.
|
328
329
|
# @return [Array<GoodData::Project>] Array of project where account settings belongs to
|
329
|
-
def projects(limit = nil)
|
330
|
+
def projects(limit = nil, offset = nil)
|
330
331
|
url = @json['accountSetting']['links']['projects']
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
332
|
+
|
333
|
+
all_projects = []
|
334
|
+
|
335
|
+
raise ArgumentError, 'Params limit and offset are expected' if !offset.nil? && limit.nil?
|
336
|
+
|
337
|
+
if limit.nil?
|
338
|
+
url += "?limit=500"
|
339
|
+
loop do
|
340
|
+
projects = client.get url
|
341
|
+
projects['projects']['items'].each do |project|
|
342
|
+
all_projects << client.create(GoodData::Project, project)
|
343
|
+
end
|
344
|
+
if !projects['projects']['paging'].nil? && !projects['projects']['paging']['next'].nil?
|
345
|
+
url = projects['projects']['paging']['next']
|
346
|
+
else
|
347
|
+
break
|
348
|
+
end
|
349
|
+
end
|
350
|
+
else
|
351
|
+
limit = [limit, 500].min if limit.is_a?(Integer) && limit > 0
|
352
|
+
|
353
|
+
url += "?limit=#{limit}"
|
354
|
+
url += "&offset=#{offset}" if !offset.nil? && offset.is_a?(Integer) && offset > 0
|
355
|
+
|
356
|
+
projects = client.get url
|
357
|
+
projects['projects']['items'].each do |project|
|
358
|
+
all_projects << client.create(GoodData::Project, project)
|
359
|
+
end
|
340
360
|
end
|
361
|
+
|
362
|
+
all_projects
|
341
363
|
end
|
342
364
|
|
343
365
|
# Saves object if dirty, clears dirty flag
|
@@ -67,9 +67,9 @@ module GoodData
|
|
67
67
|
class << self
|
68
68
|
# Returns an array of all projects accessible by
|
69
69
|
# current user
|
70
|
-
def all(opts = { client: GoodData.connection }, limit = nil)
|
70
|
+
def all(opts = { client: GoodData.connection }, limit = nil, offset = nil)
|
71
71
|
c = GoodData.get_client(opts)
|
72
|
-
c.user.projects(limit)
|
72
|
+
c.user.projects(limit, offset)
|
73
73
|
end
|
74
74
|
|
75
75
|
# Returns a Project object identified by given string
|
@@ -261,21 +261,26 @@ module GoodData
|
|
261
261
|
# @option ads_output_stage_uri Uri of the source output stage. It must be in the same domain as the target project.
|
262
262
|
def transfer_processes(from_project, to_project, options = {})
|
263
263
|
options = GoodData::Helpers.symbolize_keys(options)
|
264
|
+
aliases = {}
|
264
265
|
to_project_processes = to_project.processes
|
265
266
|
additional_hidden_params = options[:additional_hidden_params] || {}
|
266
267
|
result = from_project.processes.uniq(&:name).map do |process|
|
267
|
-
fail "The process name #{process.name} must be unique in
|
268
|
+
fail "The process name #{process.name} must be unique in transferred project #{to_project}" if to_project_processes.count { |p| p.name == process.name } > 1
|
268
269
|
next if process.type == :dataload || process.add_v2_component?
|
270
|
+
collect_process_aliases(process.data, from_project.client, aliases)
|
269
271
|
|
270
272
|
to_process = to_project_processes.find { |p| p.name == process.name }
|
271
273
|
|
274
|
+
data_sources = GoodData::Helpers.symbolize_keys_recursively!(process.data_sources)
|
275
|
+
data_sources = replace_data_source_ids(data_sources, to_project.client, aliases)
|
272
276
|
to_process = if process.path
|
273
277
|
to_process.delete if to_process
|
274
|
-
|
278
|
+
Process.deploy_from_appstore(process.path, name: process.name, client: to_project.client, project: to_project, data_sources: data_sources)
|
275
279
|
elsif process.component
|
276
280
|
to_process.delete if to_process
|
277
281
|
process_hash = GoodData::Helpers::DeepMergeableHash[GoodData::Helpers.symbolize_keys(process.to_hash)].deep_merge(additional_hidden_params)
|
278
|
-
|
282
|
+
process_hash = replace_process_data_source_ids(process_hash, to_project.client, aliases)
|
283
|
+
Process.deploy_component(process_hash, project: to_project, client: to_project.client)
|
279
284
|
else
|
280
285
|
Dir.mktmpdir('etl_transfer') do |dir|
|
281
286
|
dir = Pathname(dir)
|
@@ -283,11 +288,10 @@ module GoodData
|
|
283
288
|
File.open(filename, 'w') do |f|
|
284
289
|
f << process.download
|
285
290
|
end
|
286
|
-
|
287
291
|
if to_process
|
288
|
-
to_process.deploy(filename, type: process.type, name: process.name)
|
292
|
+
to_process.deploy(filename, type: process.type, name: process.name, data_sources: data_sources)
|
289
293
|
else
|
290
|
-
to_project.deploy_process(filename, type: process.type, name: process.name)
|
294
|
+
to_project.deploy_process(filename, type: process.type, name: process.name, data_sources: data_sources)
|
291
295
|
end
|
292
296
|
end
|
293
297
|
end
|
@@ -318,6 +322,56 @@ module GoodData
|
|
318
322
|
result.compact
|
319
323
|
end
|
320
324
|
|
325
|
+
def collect_process_aliases(process_data, client, aliases)
|
326
|
+
data_sources = process_data.dig('process', 'dataSources')
|
327
|
+
unless data_sources.blank?
|
328
|
+
data_sources.map do |data_source|
|
329
|
+
get_data_source_alias(data_source['id'], client, aliases)
|
330
|
+
end
|
331
|
+
end
|
332
|
+
component = process_data.dig('process', 'component')
|
333
|
+
get_data_source_alias(component['configLocation']['dataSourceConfig']['id'], client, aliases) if component&.dig('configLocation', 'dataSourceConfig')
|
334
|
+
aliases
|
335
|
+
end
|
336
|
+
|
337
|
+
def get_data_source_alias(data_source_id, client, aliases)
|
338
|
+
unless aliases[data_source_id]
|
339
|
+
data_source = GoodData::Helpers.get_data_source_by_id(data_source_id, client)
|
340
|
+
if data_source&.dig('dataSource', 'alias')
|
341
|
+
aliases[data_source_id] = {
|
342
|
+
:type => get_data_source_type(data_source),
|
343
|
+
:alias => data_source['dataSource']['alias']
|
344
|
+
}
|
345
|
+
end
|
346
|
+
end
|
347
|
+
aliases[data_source_id]
|
348
|
+
end
|
349
|
+
|
350
|
+
def get_data_source_type(data_source_data)
|
351
|
+
data_source_data&.dig('dataSource', 'connectionInfo') ? data_source_data['dataSource']['connectionInfo'].first[0].upcase : ""
|
352
|
+
end
|
353
|
+
|
354
|
+
def replace_process_data_source_ids(process_data, client, aliases)
|
355
|
+
component = process_data.dig(:process, :component)
|
356
|
+
if component&.dig(:configLocation, :dataSourceConfig)
|
357
|
+
the_alias = aliases[component[:configLocation][:dataSourceConfig][:id]]
|
358
|
+
process_data[:process][:component][:configLocation][:dataSourceConfig][:id] = GoodData::Helpers.verify_data_source_alias(the_alias, client)
|
359
|
+
end
|
360
|
+
process_data[:process][:dataSources] = replace_data_source_ids(process_data[:process][:dataSources], client, aliases)
|
361
|
+
process_data
|
362
|
+
end
|
363
|
+
|
364
|
+
def replace_data_source_ids(data_sources, client, aliases)
|
365
|
+
array_data_sources = []
|
366
|
+
if data_sources && !data_sources.empty?
|
367
|
+
data_sources.map do |data_source|
|
368
|
+
new_id = GoodData::Helpers.verify_data_source_alias(aliases[data_source[:id]], client)
|
369
|
+
array_data_sources.push(:id => new_id)
|
370
|
+
end
|
371
|
+
end
|
372
|
+
array_data_sources
|
373
|
+
end
|
374
|
+
|
321
375
|
def transfer_user_groups(from_project, to_project)
|
322
376
|
from_project.user_groups.map do |ug|
|
323
377
|
# migrate groups
|
@@ -625,6 +679,7 @@ module GoodData
|
|
625
679
|
def blueprint(options = {})
|
626
680
|
options = { include_ca: true }.merge(options)
|
627
681
|
result = client.get("/gdc/projects/#{pid}/model/view", params: { includeDeprecated: true, includeGrain: true, includeCA: options[:include_ca] })
|
682
|
+
|
628
683
|
polling_url = result['asyncTask']['link']['poll']
|
629
684
|
model = client.poll_on_code(polling_url, options)
|
630
685
|
bp = GoodData::Model::FromWire.from_wire(model, options)
|
@@ -1546,26 +1601,28 @@ module GoodData
|
|
1546
1601
|
# @return [Array<GoodData::User>] List of users
|
1547
1602
|
def users(opts = {})
|
1548
1603
|
client = client(opts)
|
1549
|
-
|
1550
|
-
|
1551
|
-
|
1552
|
-
|
1553
|
-
|
1554
|
-
|
1555
|
-
|
1556
|
-
|
1557
|
-
|
1558
|
-
|
1559
|
-
|
1560
|
-
|
1561
|
-
|
1562
|
-
|
1563
|
-
end
|
1604
|
+
all_users = []
|
1605
|
+
offset = opts[:offset] || 0
|
1606
|
+
limit = opts[:limit] || 1_000
|
1607
|
+
loop do
|
1608
|
+
tmp = client.get("/gdc/projects/#{pid}/users", params: { offset: offset, limit: limit })
|
1609
|
+
tmp['users'].each do |user_data|
|
1610
|
+
user = client.create(GoodData::Membership, user_data, project: self)
|
1611
|
+
|
1612
|
+
if opts[:all]
|
1613
|
+
all_users << user
|
1614
|
+
elsif opts[:disabled]
|
1615
|
+
all_users << user if user&.disabled?
|
1616
|
+
else
|
1617
|
+
all_users << user if user&.enabled?
|
1564
1618
|
end
|
1565
|
-
break if tmp['users'].count < limit
|
1566
|
-
offset += limit
|
1567
1619
|
end
|
1620
|
+
break if tmp['users'].count < limit
|
1621
|
+
|
1622
|
+
offset += limit
|
1568
1623
|
end
|
1624
|
+
|
1625
|
+
all_users
|
1569
1626
|
end
|
1570
1627
|
|
1571
1628
|
alias_method :members, :users
|
@@ -1604,14 +1661,19 @@ module GoodData
|
|
1604
1661
|
def import_users(new_users, options = {})
|
1605
1662
|
role_list = roles
|
1606
1663
|
users_list = users
|
1607
|
-
new_users = new_users.map { |x| ((x.is_a?(Hash) && x[:user] && x[:user].to_hash.merge(role: x[:role])) || x.to_hash).tap { |u| u[:login].downcase! } }
|
1608
1664
|
|
1609
1665
|
GoodData.logger.warn("Importing users to project (#{pid})")
|
1666
|
+
new_users = new_users.map { |x| ((x.is_a?(Hash) && x[:user] && x[:user].to_hash.merge(role: x[:role])) || x.to_hash).tap { |u| u[:login].downcase! } }
|
1667
|
+
# First check that if groups are provided we have them set up
|
1668
|
+
user_groups_cache, change_groups = check_groups(new_users.map(&:to_hash).flat_map { |u| u[:user_group] || [] }.uniq, options[:user_groups_cache], options)
|
1610
1669
|
|
1611
|
-
|
1670
|
+
unless change_groups.empty?
|
1671
|
+
new_users.each do |user|
|
1672
|
+
user[:user_group].map! { |e| change_groups[e].nil? ? e : change_groups[e] }
|
1673
|
+
end
|
1674
|
+
end
|
1612
1675
|
|
1613
|
-
|
1614
|
-
user_groups_cache = check_groups(new_users.map(&:to_hash).flat_map { |u| u[:user_group] || [] }.uniq, options[:user_groups_cache], options)
|
1676
|
+
whitelisted_new_users, whitelisted_users = whitelist_users(new_users.map(&:to_hash), users_list, options[:whitelists])
|
1615
1677
|
|
1616
1678
|
# conform the role on list of new users so we can diff them with the users coming from the project
|
1617
1679
|
diffable_new_with_default_role = whitelisted_new_users.map do |u|
|
@@ -1758,7 +1820,20 @@ module GoodData
|
|
1758
1820
|
def check_groups(specified_groups, user_groups_cache = nil, options = {})
|
1759
1821
|
current_user_groups = user_groups if user_groups_cache.nil? || user_groups_cache.empty?
|
1760
1822
|
groups = current_user_groups.map(&:name)
|
1761
|
-
missing_groups =
|
1823
|
+
missing_groups = []
|
1824
|
+
change_groups = {}
|
1825
|
+
specified_groups.each do |group|
|
1826
|
+
found_group = groups.find { |name| name.casecmp(group).zero? }
|
1827
|
+
if found_group.nil?
|
1828
|
+
missing_groups << group
|
1829
|
+
else
|
1830
|
+
# Change groups when they have similar group name with difference of case sensitivity
|
1831
|
+
if found_group != group
|
1832
|
+
change_groups[group] = found_group
|
1833
|
+
GoodData.logger.warn("Group with name #{group} is existed in project with name #{found_group}.")
|
1834
|
+
end
|
1835
|
+
end
|
1836
|
+
end
|
1762
1837
|
if options[:create_non_existing_user_groups]
|
1763
1838
|
missing_groups.each do |g|
|
1764
1839
|
GoodData.logger.info("Creating group #{g}")
|
@@ -1771,7 +1846,7 @@ module GoodData
|
|
1771
1846
|
"#{groups.join(',')} and you asked for #{missing_groups.join(',')}"
|
1772
1847
|
end
|
1773
1848
|
end
|
1774
|
-
current_user_groups
|
1849
|
+
[current_user_groups, change_groups]
|
1775
1850
|
end
|
1776
1851
|
|
1777
1852
|
# Update user
|
@@ -1902,6 +1977,20 @@ module GoodData
|
|
1902
1977
|
[user, roles]
|
1903
1978
|
end
|
1904
1979
|
|
1980
|
+
def upgrade_custom_v2(message, options = {})
|
1981
|
+
uri = "/gdc/md/#{pid}/datedimension/upgrade"
|
1982
|
+
poll_result = client&.post(uri, message)
|
1983
|
+
|
1984
|
+
return poll_result['wTaskStatus']['status'] if poll_result['wTaskStatus'] && poll_result['wTaskStatus']['status']
|
1985
|
+
|
1986
|
+
polling_uri = poll_result['asyncTask']['link']['poll']
|
1987
|
+
result = client&.poll_on_response(polling_uri, options) do |body|
|
1988
|
+
body && body['wTaskStatus'] && body['wTaskStatus']['status'] == 'RUNNING'
|
1989
|
+
end
|
1990
|
+
|
1991
|
+
result['wTaskStatus']['status'] == 'OK' ? 'OK' : 'FAIL'
|
1992
|
+
end
|
1993
|
+
|
1905
1994
|
def add
|
1906
1995
|
@add ||= GoodData::AutomatedDataDistribution.new(self)
|
1907
1996
|
@add
|
@@ -47,6 +47,8 @@ module GoodData
|
|
47
47
|
unless response
|
48
48
|
maql_diff_params = [:includeGrain]
|
49
49
|
maql_diff_params << :excludeFactRule if opts[:exclude_fact_rule]
|
50
|
+
maql_diff_params << :includeDeprecated if opts[:include_deprecated]
|
51
|
+
|
50
52
|
maql_diff_time = Benchmark.realtime do
|
51
53
|
response = project.maql_diff(blueprint: bp, params: maql_diff_params)
|
52
54
|
end
|
data/lib/gooddata/rest/client.rb
CHANGED
@@ -188,11 +188,11 @@ module GoodData
|
|
188
188
|
end
|
189
189
|
end
|
190
190
|
|
191
|
-
def projects(id = :all, limit = nil)
|
191
|
+
def projects(id = :all, limit = nil, offset = nil)
|
192
192
|
if limit.nil?
|
193
193
|
GoodData::Project[id, client: self]
|
194
194
|
else
|
195
|
-
GoodData::Project.all({ client: self }, limit)
|
195
|
+
GoodData::Project.all({ client: self }, limit, offset)
|
196
196
|
end
|
197
197
|
end
|
198
198
|
|
@@ -30,6 +30,7 @@ module GoodData
|
|
30
30
|
LOGIN_PATH = '/gdc/account/login'
|
31
31
|
TOKEN_PATH = '/gdc/account/token'
|
32
32
|
KEYS_TO_SCRUB = [:password, :verifyPassword, :authorizationToken]
|
33
|
+
API_LEVEL = 2
|
33
34
|
|
34
35
|
ID_LENGTH = 16
|
35
36
|
|
@@ -307,7 +308,6 @@ module GoodData
|
|
307
308
|
# Remove when TT sent in headers. Currently we need to parse from body
|
308
309
|
merge_headers!(:x_gdc_authtt => GoodData::Helpers.get_path(response, %w(userToken token)))
|
309
310
|
rescue Exception => e # rubocop:disable RescueException
|
310
|
-
GoodData.logger.error(e.message)
|
311
311
|
raise e
|
312
312
|
end
|
313
313
|
end
|
@@ -350,6 +350,9 @@ module GoodData
|
|
350
350
|
profile method.to_s.upcase, uri, request_id, stats_on do
|
351
351
|
b = proc do
|
352
352
|
params = fresh_request_params(request_id).merge(options)
|
353
|
+
|
354
|
+
params['X-GDC-VERSION'] = API_LEVEL
|
355
|
+
|
353
356
|
case method
|
354
357
|
when :get
|
355
358
|
@server[uri].get(params, &user_block)
|
@@ -514,8 +517,7 @@ module GoodData
|
|
514
517
|
begin
|
515
518
|
request.execute
|
516
519
|
rescue => e
|
517
|
-
|
518
|
-
raise e
|
520
|
+
raise "Error when uploading file #{filename}. Error: #{e}"
|
519
521
|
end
|
520
522
|
end
|
521
523
|
|
@@ -1,51 +1,51 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
1
|
+
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
|