gooddata 0.6.53 → 0.6.54
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/.flayignore +6 -0
- data/.gitignore +1 -0
- data/.pronto.yml +3 -0
- data/.rspec +2 -0
- data/.rubocop.yml +4 -1
- data/CHANGELOG.md +18 -0
- data/CONTRIBUTING.md +14 -1
- data/DEPENDENCIES.md +324 -253
- data/Dockerfile.jruby +5 -7
- data/Dockerfile.ruby +8 -8
- data/Rakefile +24 -0
- data/ci.rake +47 -0
- data/docker-compose.yml +34 -0
- data/gooddata.gemspec +8 -2
- data/lib/gooddata/bricks/middleware/restforce_middleware.rb +0 -3
- data/lib/gooddata/helpers/data_helper.rb +10 -7
- data/lib/gooddata/helpers/global_helpers_params.rb +8 -3
- data/lib/gooddata/lcm/actions/apply_custom_maql.rb +2 -1
- data/lib/gooddata/lcm/actions/associate_clients.rb +10 -1
- data/lib/gooddata/lcm/actions/collect_client_projects.rb +78 -0
- data/lib/gooddata/lcm/actions/collect_clients.rb +20 -6
- data/lib/gooddata/lcm/actions/collect_data_product.rb +62 -0
- data/lib/gooddata/lcm/actions/collect_dynamic_schedule_params.rb +62 -0
- data/lib/gooddata/lcm/actions/{collect_attrs.rb → collect_ldm_objects.rb} +3 -3
- data/lib/gooddata/lcm/actions/collect_meta.rb +6 -3
- data/lib/gooddata/lcm/actions/collect_segment_clients.rb +2 -1
- data/lib/gooddata/lcm/actions/collect_segments.rb +6 -7
- data/lib/gooddata/lcm/actions/collect_tagged_objects.rb +7 -4
- data/lib/gooddata/lcm/actions/create_segment_masters.rb +7 -3
- data/lib/gooddata/lcm/actions/ensure_data_product.rb +53 -0
- data/lib/gooddata/lcm/actions/ensure_technical_users_domain.rb +6 -2
- data/lib/gooddata/lcm/actions/ensure_technical_users_project.rb +30 -18
- data/lib/gooddata/lcm/actions/execute_schedules.rb +128 -0
- data/lib/gooddata/lcm/actions/provision_clients.rb +32 -21
- data/lib/gooddata/lcm/actions/purge_clients.rb +25 -39
- data/lib/gooddata/lcm/actions/rename_existing_client_projects.rb +70 -0
- data/lib/gooddata/lcm/actions/segments_filter.rb +6 -0
- data/lib/gooddata/lcm/actions/synchronize_cas.rb +11 -0
- data/lib/gooddata/lcm/actions/synchronize_clients.rb +2 -1
- data/lib/gooddata/lcm/actions/synchronize_etls_in_segment.rb +34 -15
- data/lib/gooddata/lcm/actions/synchronize_ldm.rb +10 -1
- data/lib/gooddata/lcm/actions/synchronize_new_segments.rb +2 -1
- data/lib/gooddata/lcm/actions/synchronize_processes.rb +4 -7
- data/lib/gooddata/lcm/actions/synchronize_tag_objects.rb +8 -5
- data/lib/gooddata/lcm/actions/synchronize_user_filters.rb +224 -0
- data/lib/gooddata/lcm/actions/synchronize_user_groups.rb +53 -0
- data/lib/gooddata/lcm/actions/synchronize_users.rb +324 -0
- data/lib/gooddata/lcm/dsl/type_dsl.rb +1 -0
- data/lib/gooddata/lcm/helpers/check_helper.rb +4 -0
- data/lib/gooddata/lcm/helpers/tags_helper.rb +4 -3
- data/lib/gooddata/lcm/lcm2.rb +33 -1
- data/lib/gooddata/lcm/types/complex/segment.rb +3 -0
- data/lib/gooddata/lcm/types/complex/update_preference.rb +8 -2
- data/lib/gooddata/lcm/types/special/array.rb +1 -3
- data/lib/gooddata/lcm/types/special/enum.rb +1 -3
- data/lib/gooddata/mixins/md_id_to_uri.rb +0 -1
- data/lib/gooddata/mixins/md_json.rb +2 -2
- data/lib/gooddata/models/blueprint/project_blueprint.rb +15 -0
- data/lib/gooddata/models/blueprint/to_wire.rb +1 -0
- data/lib/gooddata/models/client.rb +21 -9
- data/lib/gooddata/models/data_product.rb +149 -0
- data/lib/gooddata/models/domain.rb +26 -72
- data/lib/gooddata/models/from_wire.rb +2 -0
- data/lib/gooddata/models/metadata/report.rb +9 -3
- data/lib/gooddata/models/metadata/report_definition.rb +2 -2
- data/lib/gooddata/models/model.rb +1 -1
- data/lib/gooddata/models/process.rb +4 -0
- data/lib/gooddata/models/project.rb +58 -35
- data/lib/gooddata/models/project_creator.rb +13 -0
- data/lib/gooddata/models/segment.rb +63 -16
- data/lib/gooddata/models/style_setting.rb +2 -15
- data/lib/gooddata/models/user_group.rb +2 -0
- data/lib/gooddata/rest/connection.rb +32 -9
- data/lib/gooddata/rest/object_factory.rb +0 -25
- data/lib/gooddata/version.rb +1 -1
- data/spec/data/blueprints/invalid_blueprint.json +2 -2
- data/spec/data/blueprints/test_project_model_spec.json +1 -1
- data/spec/data/dynamic_schedule_params_table.csv +7 -0
- data/spec/data/workspace_table.csv +3 -3
- data/spec/environment/staging.rb +3 -3
- data/spec/integration/ads_output_stage_spec.rb +0 -10
- data/spec/integration/clients_spec.rb +1 -1
- data/spec/{unit → integration}/commands/command_projects_spec.rb +0 -0
- data/spec/{unit → integration}/core/connection_spec.rb +0 -0
- data/spec/{unit → integration}/core/logging_spec.rb +0 -0
- data/spec/{unit → integration}/core/project_spec.rb +0 -0
- data/spec/integration/date_dim_switch_spec.rb +13 -0
- data/spec/integration/full_process_schedule_spec.rb +2 -2
- data/spec/integration/helpers_spec.rb +16 -0
- data/spec/integration/lcm_spec.rb +12 -2
- data/spec/integration/mixins/id_to_uri_spec.rb +44 -0
- data/spec/integration/models/data_product_spec.rb +71 -0
- data/spec/{unit → integration}/models/domain_spec.rb +2 -2
- data/spec/{unit → integration}/models/invitation_spec.rb +0 -0
- data/spec/{unit → integration}/models/membership_spec.rb +0 -0
- data/spec/{unit → integration}/models/params_spec.rb +0 -0
- data/spec/{unit → integration}/models/profile_spec.rb +0 -0
- data/spec/{unit → integration}/models/project_role_spec.rb +0 -0
- data/spec/integration/models/project_spec.rb +225 -0
- data/spec/{unit → integration}/models/schedule_spec.rb +0 -0
- data/spec/{unit → integration}/models/unit_project_spec.rb +0 -0
- data/spec/integration/project_spec.rb +40 -5
- data/spec/integration/segments_spec.rb +27 -26
- data/spec/integration/user_filters_spec.rb +1 -1
- data/spec/spec_helper.rb +15 -19
- data/spec/unit/actions/associate_clients_spec.rb +47 -0
- data/spec/unit/actions/collect_client_projects_spec.rb +47 -0
- data/spec/unit/actions/collect_clients_spec.rb +27 -0
- data/spec/unit/actions/collect_data_product_spec.rb +64 -0
- data/spec/unit/actions/collect_dynamic_schedule_params_spec.rb +56 -0
- data/spec/unit/actions/collect_meta_spec.rb +4 -4
- data/spec/unit/actions/collect_segment_clients_spec.rb +44 -3
- data/spec/unit/actions/collect_tagged_objects_spec.rb +20 -4
- data/spec/unit/actions/create_segment_masters_spec.rb +64 -0
- data/spec/unit/actions/ensure_data_product_spec.rb +38 -0
- data/spec/unit/actions/ensure_technical_users_domain_spec.rb +51 -0
- data/spec/unit/actions/ensure_technical_users_project_spec.rb +72 -0
- data/spec/unit/actions/execute_schedules_spec.rb +94 -0
- data/spec/unit/actions/provision_clients_spec.rb +45 -0
- data/spec/unit/actions/purge_clients_spec.rb +47 -0
- data/spec/unit/actions/rename_existing_client_projects_spec.rb +54 -0
- data/spec/unit/actions/segments_filter_spec.rb +46 -0
- data/spec/unit/actions/shared_examples_for_user_actions.rb +10 -0
- data/spec/unit/actions/synchronize_cas_spec.rb +58 -0
- data/spec/unit/actions/synchronize_etls_in_segment_spec.rb +174 -13
- data/spec/unit/actions/synchronize_ldm_spec.rb +57 -0
- data/spec/unit/actions/synchronize_user_filters_spec.rb +142 -0
- data/spec/unit/actions/synchronize_user_groups_spec.rb +49 -0
- data/spec/unit/actions/synchronize_users_spec.rb +76 -0
- data/spec/unit/helpers/data_helper_spec.rb +17 -0
- data/spec/unit/helpers/global_helpers_spec.rb +16 -0
- data/spec/unit/helpers_spec.rb +0 -6
- data/spec/unit/models/blueprint/project_blueprint_spec.rb +21 -4
- data/spec/unit/models/project_creator_spec.rb +16 -0
- data/spec/unit/models/project_spec.rb +66 -197
- metadata +202 -100
- data/PULL_REQUEST_TEMPLATE.md +0 -5
- data/lib/gooddata/bricks/middleware/params_inspect_middleware.rb +0 -21
|
@@ -14,8 +14,6 @@ module GoodData
|
|
|
14
14
|
class Domain < Rest::Resource
|
|
15
15
|
attr_reader :name
|
|
16
16
|
|
|
17
|
-
ProvisioningResult = Struct.new('ProvisioningResult', :id, :status, :project_uri, :error)
|
|
18
|
-
|
|
19
17
|
USER_LANGUAGES = {
|
|
20
18
|
'en-US' => 'English',
|
|
21
19
|
'nl-NL' => 'Dutch',
|
|
@@ -95,6 +93,10 @@ module GoodData
|
|
|
95
93
|
tmp = user_data[:timezone]
|
|
96
94
|
data[:timezone] = tmp if tmp && !tmp.empty?
|
|
97
95
|
|
|
96
|
+
# Optional ip whitelist
|
|
97
|
+
tmp = user_data[:ip_whitelist]
|
|
98
|
+
data[:ipWhitelist] = tmp if tmp
|
|
99
|
+
|
|
98
100
|
c = client(opts)
|
|
99
101
|
|
|
100
102
|
# TODO: It will be nice if the API will return us user just newly created
|
|
@@ -323,19 +325,11 @@ Available values for setting language are: #{available_languages}."
|
|
|
323
325
|
# if it exists.
|
|
324
326
|
#
|
|
325
327
|
# @param id [String] Id of client that you are looking for
|
|
328
|
+
# @param data_product [DataProduct] data product object in which the clients are located
|
|
326
329
|
# @return [Object] Raw response
|
|
327
330
|
#
|
|
328
|
-
def clients(id = :all)
|
|
329
|
-
|
|
330
|
-
res = client.get("/gdc/domains/#{name}/clients")
|
|
331
|
-
res_clients = (res['clients'] && res['clients']['items']) || []
|
|
332
|
-
res_clients.map { |res_client| client.create(GoodData::Client, res_client) }
|
|
333
|
-
else
|
|
334
|
-
res = client.get("/gdc/domains/#{name}/clients/#{id}")
|
|
335
|
-
error = GoodData::Helpers.interpolate_error_message(res)
|
|
336
|
-
raise error if error
|
|
337
|
-
client.create(GoodData::Client, res)
|
|
338
|
-
end
|
|
331
|
+
def clients(id = :all, data_product = nil)
|
|
332
|
+
GoodData::Client[id, data_product: data_product, domain: self]
|
|
339
333
|
end
|
|
340
334
|
|
|
341
335
|
alias_method :create_user, :add_user
|
|
@@ -344,8 +338,17 @@ Available values for setting language are: #{available_languages}."
|
|
|
344
338
|
GoodData::Domain.create_users(list, name, { client: client }.merge(options))
|
|
345
339
|
end
|
|
346
340
|
|
|
347
|
-
def
|
|
348
|
-
GoodData::
|
|
341
|
+
def data_products(id = :all)
|
|
342
|
+
GoodData::DataProduct[id, domain: self]
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def create_data_product(data)
|
|
346
|
+
data_product = GoodData::DataProduct.create(data, domain: self, client: client)
|
|
347
|
+
data_product.save
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
def segments(id = :all, data_product = nil)
|
|
351
|
+
GoodData::Segment[id, data_product: data_product, domain: self]
|
|
349
352
|
end
|
|
350
353
|
|
|
351
354
|
# Creates new segment in current domain from parameters passed
|
|
@@ -429,29 +432,10 @@ Available values for setting language are: #{available_languages}."
|
|
|
429
432
|
#
|
|
430
433
|
# @return [Enumerator] Returns Enumerator of results
|
|
431
434
|
def provision_client_projects(segments = nil)
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
end
|
|
439
|
-
|
|
440
|
-
res = client.post(segments_uri + '/provisionClientProjects', body)
|
|
441
|
-
res = client.poll_on_code(res['asyncTask']['links']['poll'])
|
|
442
|
-
failed_count = GoodData::Helpers.get_path(res, %w(clientProjectProvisioningResult failed count), 0)
|
|
443
|
-
created_count = GoodData::Helpers.get_path(res, %w(clientProjectProvisioningResult created count), 0)
|
|
444
|
-
return Enumerator.new([]) if failed_count + created_count == 0 # rubocop:disable Style/NumericPredicate
|
|
445
|
-
Enumerator.new do |y|
|
|
446
|
-
uri = GoodData::Helpers.get_path(res, %w(clientProjectProvisioningResult links details))
|
|
447
|
-
loop do
|
|
448
|
-
result = client.get(uri)
|
|
449
|
-
(GoodData::Helpers.get_path(result, %w(clientProjectProvisioningResultDetails items)) || []).each do |item|
|
|
450
|
-
y << ProvisioningResult.new(item['id'], item['status'], item['project'], item['error'])
|
|
451
|
-
end
|
|
452
|
-
uri = GoodData::Helpers.get_path(res, %w(clientProjectProvisioningResultDetails paging next))
|
|
453
|
-
break if uri.nil?
|
|
454
|
-
end
|
|
435
|
+
segments = segments.is_a?(Array) ? segments : [segments]
|
|
436
|
+
segments.each do |segment_id|
|
|
437
|
+
segment = segments(segment_id)
|
|
438
|
+
segment.provision_client_projects
|
|
455
439
|
end
|
|
456
440
|
end
|
|
457
441
|
|
|
@@ -460,7 +444,7 @@ Available values for setting language are: #{available_languages}."
|
|
|
460
444
|
client_id = datum[:id]
|
|
461
445
|
settings = datum[:settings]
|
|
462
446
|
settings.each do |setting|
|
|
463
|
-
GoodData::Client.update_setting(setting[:name], setting[:value], domain: self, client_id: client_id)
|
|
447
|
+
GoodData::Client.update_setting(setting[:name], setting[:value], domain: self, client_id: client_id, data_product_id: datum[:data_product_id])
|
|
464
448
|
end
|
|
465
449
|
end
|
|
466
450
|
nil
|
|
@@ -468,39 +452,9 @@ Available values for setting language are: #{available_languages}."
|
|
|
468
452
|
alias_method :add_clients_settings, :update_clients_settings
|
|
469
453
|
|
|
470
454
|
def update_clients(data, options = {})
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
delete_projects = options[:delete_projects] == false ? false : true
|
|
476
|
-
payload = data.map do |datum|
|
|
477
|
-
{
|
|
478
|
-
:client => {
|
|
479
|
-
:id => datum[:id],
|
|
480
|
-
:segment => segments_uri + '/segments/' + datum[:segment]
|
|
481
|
-
}
|
|
482
|
-
}.tap do |h|
|
|
483
|
-
h[:client][:project] = datum[:project] if datum.key?(:project)
|
|
484
|
-
end
|
|
485
|
-
end
|
|
486
|
-
if options[:delete_extra] == true
|
|
487
|
-
res = client.post(segments_uri + '/updateClients?deleteExtra=true', updateClients: { items: payload })
|
|
488
|
-
elsif options[:delete_extra_in_segments]
|
|
489
|
-
segments_to_delete_in = options[:delete_extra_in_segments]
|
|
490
|
-
.map { |segment| CGI.escape(segment) }
|
|
491
|
-
.join(',')
|
|
492
|
-
uri = segments_uri + "/updateClients?deleteExtraInSegments=#{segments_to_delete_in}"
|
|
493
|
-
res = client.post(uri, updateClients: { items: payload })
|
|
494
|
-
else
|
|
495
|
-
res = client.post(segments_uri + '/updateClients', updateClients: { items: payload })
|
|
496
|
-
end
|
|
497
|
-
data = GoodData::Helpers.get_path(res, ['updateClientsResponse'])
|
|
498
|
-
if data
|
|
499
|
-
result = data.flat_map { |k, v| v.map { |h| GoodData::Helpers.symbolize_keys(h.merge('type' => k)) } }
|
|
500
|
-
result.select { |r| r[:status] == 'DELETED' }.peach { |r| r[:originalProject] && client.delete(r[:originalProject]) } if delete_projects
|
|
501
|
-
result
|
|
502
|
-
else
|
|
503
|
-
[]
|
|
455
|
+
data.group_by(&:data_product_id).each do |data_product_id, client_update_data|
|
|
456
|
+
data_product = data_products(data_product_id)
|
|
457
|
+
data_product.update_clients(client_update_data, options)
|
|
504
458
|
end
|
|
505
459
|
end
|
|
506
460
|
|
|
@@ -108,6 +108,7 @@ module GoodData
|
|
|
108
108
|
d[:id] = date_dim['dateDimension']['name']
|
|
109
109
|
d[:title] = date_dim['dateDimension']['title']
|
|
110
110
|
d[:urn] = date_dim['dateDimension']['urn']
|
|
111
|
+
d[:identifier_prefix] = date_dim['dateDimension']['identifierPrefix']
|
|
111
112
|
end
|
|
112
113
|
end
|
|
113
114
|
|
|
@@ -125,6 +126,7 @@ module GoodData
|
|
|
125
126
|
f[:description] = fact['fact']['description'] if fact['fact']['description']
|
|
126
127
|
f[:folder] = fact['fact']['folder']
|
|
127
128
|
f[:gd_data_type] = fact['fact']['dataType'] || GoodData::Model::DEFAULT_FACT_DATATYPE
|
|
129
|
+
f[:restricted] = fact['fact']['restricted'] if fact['fact']['restricted']
|
|
128
130
|
end
|
|
129
131
|
end
|
|
130
132
|
end
|
|
@@ -72,7 +72,7 @@ module GoodData
|
|
|
72
72
|
end
|
|
73
73
|
end
|
|
74
74
|
|
|
75
|
-
if result.empty?
|
|
75
|
+
if result.to_s.empty?
|
|
76
76
|
ReportDataResult.new(data: [], top: 0, left: 0)
|
|
77
77
|
else
|
|
78
78
|
ReportDataResult.from_xtab(result)
|
|
@@ -144,10 +144,16 @@ module GoodData
|
|
|
144
144
|
|
|
145
145
|
# Computes the report and returns the result. If it is not computable returns nil.
|
|
146
146
|
#
|
|
147
|
+
# @option options [Time] :time Force the platform to simutale the result at this time
|
|
147
148
|
# @return [GoodData::DataResult] Returns the result
|
|
148
149
|
def execute(options = {})
|
|
150
|
+
time = options[:time]
|
|
151
|
+
|
|
152
|
+
report_req = { 'report' => uri }
|
|
153
|
+
report_req['timestamp'] = time.to_i if time
|
|
154
|
+
|
|
149
155
|
fail 'You have to save the report before executing. If you do not want to do that please use GoodData::ReportDefinition' unless saved?
|
|
150
|
-
result = client.post
|
|
156
|
+
result = client.post "/gdc/projects/#{project.pid}/execute", 'report_req' => report_req
|
|
151
157
|
GoodData::Report.data_result(result, options.merge(client: client))
|
|
152
158
|
end
|
|
153
159
|
|
|
@@ -163,7 +169,7 @@ module GoodData
|
|
|
163
169
|
#
|
|
164
170
|
# @return [String] Returns data
|
|
165
171
|
def export(format, options = {})
|
|
166
|
-
result = client.post(
|
|
172
|
+
result = client.post("/gdc/projects/#{project.pid}/execute", 'report_req' => { 'report' => uri })
|
|
167
173
|
result1 = client.post('/gdc/exporter/executor', :result_req => { :format => format, :result => result })
|
|
168
174
|
client.poll_on_code(result1['uri'], options.merge(process: false))
|
|
169
175
|
end
|
|
@@ -228,7 +228,7 @@ module GoodData
|
|
|
228
228
|
pars = {
|
|
229
229
|
'report_req' => { 'reportDefinition' => uri }
|
|
230
230
|
}
|
|
231
|
-
client.post
|
|
231
|
+
client.post "/gdc/projects/#{project.pid}/execute", pars
|
|
232
232
|
else
|
|
233
233
|
data = {
|
|
234
234
|
report_req: {
|
|
@@ -238,7 +238,7 @@ module GoodData
|
|
|
238
238
|
}
|
|
239
239
|
}
|
|
240
240
|
}
|
|
241
|
-
uri = "/gdc/
|
|
241
|
+
uri = "/gdc/projects/#{project.pid}/execute"
|
|
242
242
|
client.post(uri, data)
|
|
243
243
|
end
|
|
244
244
|
GoodData::Report.data_result(result, opts.merge(client: client))
|
|
@@ -86,7 +86,7 @@ module GoodData
|
|
|
86
86
|
'GDC.time.day_us_noleading', # M/d/yy
|
|
87
87
|
]
|
|
88
88
|
|
|
89
|
-
GD_DATA_TYPES = ['BIGINT', 'DOUBLE', 'INTEGER', 'INT', /^VARCHAR\(\d{
|
|
89
|
+
GD_DATA_TYPES = ['BIGINT', 'DOUBLE', 'INTEGER', 'INT', /^VARCHAR\(([1-9]\d{0,3}|10000)\)$/i, /^DECIMAL\(\d{1,3},\s*\d{1,3}\)$/i]
|
|
90
90
|
|
|
91
91
|
DEFAULT_FACT_DATATYPE = 'DECIMAL(12,2)'
|
|
92
92
|
DEFAULT_ATTRIBUTE_DATATYPE = 'VARCHAR(128)'
|
|
@@ -125,6 +125,10 @@ module GoodData
|
|
|
125
125
|
|
|
126
126
|
File.delete(deployed_path) if File.exist?(deployed_path)
|
|
127
127
|
|
|
128
|
+
if res['asyncTask']
|
|
129
|
+
res = client.poll_on_response(res['asyncTask']['links']['poll']) { |body| body['asyncTask'] }
|
|
130
|
+
end
|
|
131
|
+
|
|
128
132
|
process = client.create(Process, res, project: project)
|
|
129
133
|
puts HighLine.color("Deploy DONE #{path}", HighLine::GREEN) if verbose
|
|
130
134
|
process
|
|
@@ -256,39 +256,60 @@ module GoodData
|
|
|
256
256
|
def transfer_processes(from_project, to_project, options = {})
|
|
257
257
|
options = GoodData::Helpers.symbolize_keys(options)
|
|
258
258
|
to_project_processes = to_project.processes
|
|
259
|
-
from_project.processes.uniq(&:name).
|
|
259
|
+
result = from_project.processes.uniq(&:name).map do |process|
|
|
260
260
|
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
|
|
261
|
+
next if process.type == :dataload
|
|
261
262
|
to_process = to_project_processes.find { |p| p.name == process.name }
|
|
262
263
|
|
|
263
|
-
if process.path
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
264
|
+
to_process = if process.path
|
|
265
|
+
to_process.delete if to_process
|
|
266
|
+
GoodData::Process.deploy_from_appstore(process.path, name: process.name, client: to_project.client, project: to_project)
|
|
267
|
+
else
|
|
268
|
+
Dir.mktmpdir('etl_transfer') do |dir|
|
|
269
|
+
dir = Pathname(dir)
|
|
270
|
+
filename = dir + 'process.zip'
|
|
271
|
+
File.open(filename, 'w') do |f|
|
|
272
|
+
f << process.download
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
if to_process
|
|
276
|
+
to_process.deploy(filename, type: process.type, name: process.name)
|
|
277
|
+
else
|
|
278
|
+
to_project.deploy_process(filename, type: process.type, name: process.name)
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
{
|
|
284
|
+
from: from_project.pid,
|
|
285
|
+
to: to_project.pid,
|
|
286
|
+
name: process.name,
|
|
287
|
+
status: to_process ? 'successful' : 'failed'
|
|
288
|
+
}
|
|
277
289
|
end
|
|
278
290
|
|
|
279
291
|
transfer_output_stage(from_project, to_project, options)
|
|
292
|
+
result << {
|
|
293
|
+
from: from_project.pid,
|
|
294
|
+
to: to_project.pid,
|
|
295
|
+
name: 'Automated Data Distribution',
|
|
296
|
+
status: 'successful'
|
|
297
|
+
}
|
|
280
298
|
|
|
281
299
|
res = (from_project.processes + to_project.processes).map { |p| [p, p.name, p.type] }
|
|
282
300
|
res.group_by { |x| [x[1], x[2]] }
|
|
283
301
|
.select { |_, procs| procs.length == 1 && procs[2] != :dataload }
|
|
284
302
|
.flat_map { |_, procs| procs.select { |p| p[0].project.pid == to_project.pid }.map { |p| p[0] } }
|
|
285
303
|
.peach(&:delete)
|
|
304
|
+
|
|
305
|
+
result.compact
|
|
286
306
|
end
|
|
287
307
|
|
|
288
308
|
def transfer_user_groups(from_project, to_project)
|
|
289
|
-
from_project.user_groups.
|
|
309
|
+
from_project.user_groups.map do |ug|
|
|
290
310
|
# migrate groups
|
|
291
311
|
new_group = to_project.user_groups.select { |group| group.name == ug.name }.first
|
|
312
|
+
new_group_status = new_group ? 'modified' : 'created'
|
|
292
313
|
new_group ||= UserGroup.create(:name => ug.name, :description => ug.description, :project => to_project)
|
|
293
314
|
new_group.project = to_project
|
|
294
315
|
new_group.description = ug.description
|
|
@@ -303,6 +324,13 @@ module GoodData
|
|
|
303
324
|
permission = grantee['aclEntryURI']['permission']
|
|
304
325
|
new_dashboard.grant(:member => new_group, :permission => permission)
|
|
305
326
|
end
|
|
327
|
+
|
|
328
|
+
{
|
|
329
|
+
from: from_project.pid,
|
|
330
|
+
to: to_project.pid,
|
|
331
|
+
user_group: new_group.name,
|
|
332
|
+
status: new_group_status
|
|
333
|
+
}
|
|
306
334
|
end
|
|
307
335
|
end
|
|
308
336
|
|
|
@@ -314,9 +342,14 @@ module GoodData
|
|
|
314
342
|
# the master, client in which case we take its project, string in which
|
|
315
343
|
# case we treat is as an project object or directly project.
|
|
316
344
|
def transfer_schedules(from_project, to_project)
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
345
|
+
to_project_processes = to_project.processes.sort_by(&:name)
|
|
346
|
+
from_project_processes = from_project.processes.sort_by(&:name)
|
|
347
|
+
|
|
348
|
+
GoodData.logger.debug("Processes in from project #{from_project.pid}: #{from_project_processes.map(&:name).join(', ')}")
|
|
349
|
+
GoodData.logger.debug("Processes in to project #{to_project.pid}: #{to_project_processes.map(&:name).join(', ')}")
|
|
350
|
+
|
|
351
|
+
cache = to_project_processes
|
|
352
|
+
.zip(from_project_processes)
|
|
320
353
|
.flat_map do |remote, local|
|
|
321
354
|
local.schedules.map do |schedule|
|
|
322
355
|
[remote, local, schedule]
|
|
@@ -574,10 +607,11 @@ module GoodData
|
|
|
574
607
|
#
|
|
575
608
|
# @return [GoodData::ProjectRole] Project role if found
|
|
576
609
|
def blueprint(options = {})
|
|
577
|
-
|
|
610
|
+
options = { include_ca: true }.merge(options)
|
|
611
|
+
result = client.get("/gdc/projects/#{pid}/model/view", params: { includeDeprecated: true, includeGrain: true, includeCA: options[:include_ca] })
|
|
578
612
|
polling_url = result['asyncTask']['link']['poll']
|
|
579
613
|
model = client.poll_on_code(polling_url, options)
|
|
580
|
-
bp = GoodData::Model::FromWire.from_wire(model,
|
|
614
|
+
bp = GoodData::Model::FromWire.from_wire(model, options)
|
|
581
615
|
bp.title = title
|
|
582
616
|
bp
|
|
583
617
|
end
|
|
@@ -730,6 +764,7 @@ module GoodData
|
|
|
730
764
|
def data_permissions(id = :all)
|
|
731
765
|
GoodData::MandatoryUserFilter[id, client: client, project: self]
|
|
732
766
|
end
|
|
767
|
+
alias_method :user_filters, :data_permissions
|
|
733
768
|
|
|
734
769
|
# Deletes project
|
|
735
770
|
def delete
|
|
@@ -1075,7 +1110,7 @@ module GoodData
|
|
|
1075
1110
|
# @param [String] key key of the value to be stored
|
|
1076
1111
|
# @return [String] val value to be stored
|
|
1077
1112
|
def set_metadata(key, val)
|
|
1078
|
-
GoodData::ProjectMetadata[key, client: client, project: self
|
|
1113
|
+
GoodData::ProjectMetadata.[]=(key, { client: client, project: self }, val)
|
|
1079
1114
|
end
|
|
1080
1115
|
|
|
1081
1116
|
# Helper for getting metrics of a project
|
|
@@ -1185,7 +1220,7 @@ module GoodData
|
|
|
1185
1220
|
:partialMDImport => {
|
|
1186
1221
|
:token => token,
|
|
1187
1222
|
:overwriteNewer => '1',
|
|
1188
|
-
:updateLDMObjects => '
|
|
1223
|
+
:updateLDMObjects => '1',
|
|
1189
1224
|
:importAttributeProperties => '1'
|
|
1190
1225
|
}
|
|
1191
1226
|
}
|
|
@@ -1512,18 +1547,6 @@ module GoodData
|
|
|
1512
1547
|
data['links']['self'] if data && data['links'] && data['links']['self']
|
|
1513
1548
|
end
|
|
1514
1549
|
|
|
1515
|
-
# List of user filters within this project
|
|
1516
|
-
#
|
|
1517
|
-
# @return [Array<GoodData::MandatoryUserFilter>] List of mandatory user
|
|
1518
|
-
def user_filters
|
|
1519
|
-
url = "/gdc/md/#{pid}/userfilters"
|
|
1520
|
-
|
|
1521
|
-
tmp = client.get(url)
|
|
1522
|
-
tmp['userFilters']['items'].pmap do |filter|
|
|
1523
|
-
client.create(GoodData::MandatoryUserFilter, filter, project: self)
|
|
1524
|
-
end
|
|
1525
|
-
end
|
|
1526
|
-
|
|
1527
1550
|
# List of users in project
|
|
1528
1551
|
#
|
|
1529
1552
|
#
|
|
@@ -127,6 +127,19 @@ module GoodData
|
|
|
127
127
|
preference = GoodData::Helpers.symbolize_keys(opts[:update_preference] || {})
|
|
128
128
|
preference = Hash[preference.map { |k, v| [k, GoodData::Helpers.to_boolean(v)] }]
|
|
129
129
|
|
|
130
|
+
# will use new parameters instead of the old ones
|
|
131
|
+
if preference.empty? || [:allow_cascade_drops, :keep_data].any? { |k| preference.key?(k) }
|
|
132
|
+
if [:cascade_drops, :preserve_data].any? { |k| preference.key?(k) }
|
|
133
|
+
fail "Please do not mix old parameters (:cascade_drops, :preserve_data) with the new ones (:allow_cascade_drops, :keep_data)."
|
|
134
|
+
end
|
|
135
|
+
preference = { allow_cascade_drops: false, keep_data: true }.merge(preference)
|
|
136
|
+
|
|
137
|
+
new_preference = {}
|
|
138
|
+
new_preference[:cascade_drops] = false unless preference[:allow_cascade_drops]
|
|
139
|
+
new_preference[:preserve_data] = true if preference[:keep_data]
|
|
140
|
+
preference = new_preference
|
|
141
|
+
end
|
|
142
|
+
|
|
130
143
|
# first is cascadeDrops, second is preserveData
|
|
131
144
|
rules = [
|
|
132
145
|
{ priority: 1, cascade_drops: false, preserve_data: true },
|
|
@@ -15,10 +15,15 @@ require_relative '../mixins/uri_getter'
|
|
|
15
15
|
|
|
16
16
|
module GoodData
|
|
17
17
|
class Segment < Rest::Resource
|
|
18
|
-
SYNCHRONIZE_URI = '/gdc/domains/%s/segments/%s/synchronizeClients'
|
|
18
|
+
SYNCHRONIZE_URI = '/gdc/domains/%s/dataproducts/%s/segments/%s/synchronizeClients'
|
|
19
|
+
|
|
20
|
+
#
|
|
21
|
+
ProvisioningResult = Struct.new('ProvisioningResult', :id, :status, :project_uri, :error)
|
|
19
22
|
|
|
20
23
|
attr_accessor :domain
|
|
21
24
|
|
|
25
|
+
attr_writer :data_product
|
|
26
|
+
|
|
22
27
|
data_property_reader 'id'
|
|
23
28
|
|
|
24
29
|
include Mixin::Links
|
|
@@ -43,10 +48,12 @@ module GoodData
|
|
|
43
48
|
client = domain.client
|
|
44
49
|
fail ArgumentError, 'No client specified' if client.nil?
|
|
45
50
|
|
|
51
|
+
data_product = opts[:data_product]
|
|
52
|
+
|
|
46
53
|
if id == :all
|
|
47
54
|
GoodData::Segment.all(opts)
|
|
48
55
|
else
|
|
49
|
-
result = client.get(domain
|
|
56
|
+
result = client.get(base_uri(domain, data_product) + "/segments/#{CGI.escape(id)}")
|
|
50
57
|
client.create(GoodData::Segment, result.merge('domain' => domain))
|
|
51
58
|
end
|
|
52
59
|
end
|
|
@@ -60,8 +67,17 @@ module GoodData
|
|
|
60
67
|
fail 'Domain has to be passed in options' unless domain
|
|
61
68
|
client = domain.client
|
|
62
69
|
|
|
63
|
-
|
|
64
|
-
|
|
70
|
+
data_product = opts[:data_product]
|
|
71
|
+
results = client.get(base_uri(domain, data_product) + '/segments')
|
|
72
|
+
GoodData::Helpers.get_path(results, %w(segments items)).map { |i| client.create(GoodData::Segment, i, domain: domain) }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def base_uri(domain, data_product)
|
|
76
|
+
if data_product
|
|
77
|
+
GoodData::DataProduct::ONE_DATA_PRODUCT_PATH % { domain_name: domain.name, id: data_product.data_product_id }
|
|
78
|
+
else
|
|
79
|
+
domain.segments_uri
|
|
80
|
+
end
|
|
65
81
|
end
|
|
66
82
|
|
|
67
83
|
# Creates new segment from parameters passed
|
|
@@ -71,9 +87,9 @@ module GoodData
|
|
|
71
87
|
# @return [GoodData::Segment] New Segment instance
|
|
72
88
|
def create(data = {}, options = {})
|
|
73
89
|
segment_id = data[:segment_id]
|
|
74
|
-
fail '
|
|
90
|
+
fail 'segment_id has to be provided' if segment_id.blank?
|
|
75
91
|
client = options[:client]
|
|
76
|
-
segment = client.create(GoodData::Segment, GoodData::Helpers.stringify_keys(SEGMENT_TEMPLATE)
|
|
92
|
+
segment = client.create(GoodData::Segment, GoodData::Helpers.stringify_keys(SEGMENT_TEMPLATE), options)
|
|
77
93
|
segment.tap do |s|
|
|
78
94
|
s.segment_id = segment_id
|
|
79
95
|
s.master_project = data[:master_project]
|
|
@@ -84,9 +100,19 @@ module GoodData
|
|
|
84
100
|
def initialize(data)
|
|
85
101
|
super
|
|
86
102
|
@domain = data.delete('domain')
|
|
103
|
+
@data_product = nil
|
|
87
104
|
@json = data
|
|
88
105
|
end
|
|
89
106
|
|
|
107
|
+
def data_product
|
|
108
|
+
if @data_product
|
|
109
|
+
@data_product
|
|
110
|
+
else
|
|
111
|
+
json = client.get(data['links']['dataProduct'])
|
|
112
|
+
@data_product = client.create(GoodData::DataProduct, json)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
90
116
|
# Segment id getter for the Segment. Called segment_id since id is a reserved word in ruby world
|
|
91
117
|
#
|
|
92
118
|
# @return [String] Segment id
|
|
@@ -159,17 +185,17 @@ module GoodData
|
|
|
159
185
|
if uri
|
|
160
186
|
client.put(uri, json)
|
|
161
187
|
else
|
|
162
|
-
res = client.post(domain
|
|
188
|
+
res = client.post(self.class.base_uri(domain, @data_product ? data_product : nil) + '/segments', json)
|
|
163
189
|
@json = res
|
|
164
190
|
end
|
|
165
191
|
self
|
|
166
192
|
end
|
|
167
193
|
|
|
168
|
-
# Runs async process that walks
|
|
194
|
+
# Runs async process that walks through segments and provisions projects if necessary.
|
|
169
195
|
#
|
|
170
196
|
# @return [Array] Returns array of results
|
|
171
197
|
def synchronize_clients
|
|
172
|
-
sync_uri = SYNCHRONIZE_URI % [domain.obj_id, id]
|
|
198
|
+
sync_uri = SYNCHRONIZE_URI % [domain.obj_id, data_product.data_product_id, id]
|
|
173
199
|
res = client.post sync_uri, nil
|
|
174
200
|
|
|
175
201
|
# wait until the instance is created
|
|
@@ -180,7 +206,7 @@ module GoodData
|
|
|
180
206
|
client.create(ClientSynchronizationResult, res)
|
|
181
207
|
end
|
|
182
208
|
|
|
183
|
-
def synchronize_processes(projects = []
|
|
209
|
+
def synchronize_processes(projects = [])
|
|
184
210
|
projects = [projects] unless projects.is_a?(Array)
|
|
185
211
|
projects = projects.map do |p|
|
|
186
212
|
p = p.pid if p.respond_to?('pid')
|
|
@@ -198,7 +224,8 @@ module GoodData
|
|
|
198
224
|
}
|
|
199
225
|
end
|
|
200
226
|
|
|
201
|
-
uri = '/gdc/internal/lcm/domains/%
|
|
227
|
+
uri = '/gdc/internal/lcm/domains/%{domain}/dataproducts/%{dataproduct}/segments/%{segment}/syncProcesses'
|
|
228
|
+
uri = uri % { domain: domain.name, dataproduct: data_product.data_product_id, segment: segment_id }
|
|
202
229
|
res = client.post(uri, body)
|
|
203
230
|
|
|
204
231
|
client.poll_on_response(GoodData::Helpers.get_path(res, %w(asyncTask link poll)), sleep_interval: 1) do |r|
|
|
@@ -216,11 +243,31 @@ module GoodData
|
|
|
216
243
|
self
|
|
217
244
|
rescue RestClient::BadRequest => e
|
|
218
245
|
payload = GoodData::Helpers.parse_http_exception(e)
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
246
|
+
e = SegmentNotEmpty if GoodData::Helpers.get_path(payload) == 'gdc.c4.conflict.domain.segment.contains_clients'
|
|
247
|
+
raise e
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def provision_client_projects(segments = [])
|
|
251
|
+
body = {
|
|
252
|
+
provisionClientProjects: {
|
|
253
|
+
segments: segments.empty? ? [segment_id] : segments
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
res = client.post(GoodData::DataProduct::ONE_DATA_PRODUCT_PATH % { domain_name: domain.name, id: data_product.data_product_id } + '/provisionClientProjects', body)
|
|
257
|
+
res = client.poll_on_code(res['asyncTask']['links']['poll'])
|
|
258
|
+
failed_count = GoodData::Helpers.get_path(res, %w(clientProjectProvisioningResult failed count), 0)
|
|
259
|
+
created_count = GoodData::Helpers.get_path(res, %w(clientProjectProvisioningResult created count), 0)
|
|
260
|
+
return Enumerator.new([]) if (failed_count + created_count).zero?
|
|
261
|
+
Enumerator.new do |y|
|
|
262
|
+
uri = GoodData::Helpers.get_path(res, %w(clientProjectProvisioningResult links details))
|
|
263
|
+
loop do
|
|
264
|
+
result = client.get(uri)
|
|
265
|
+
(GoodData::Helpers.get_path(result, %w(clientProjectProvisioningResultDetails items)) || []).each do |item|
|
|
266
|
+
y << ProvisioningResult.new(item['id'], item['status'], item['project'], item['error'])
|
|
267
|
+
end
|
|
268
|
+
uri = GoodData::Helpers.get_path(res, %w(clientProjectProvisioningResultDetails paging next))
|
|
269
|
+
break if uri.nil?
|
|
270
|
+
end
|
|
224
271
|
end
|
|
225
272
|
end
|
|
226
273
|
end
|