gooddata 2.1.14 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gdc-ii-config.yaml +1 -1
- data/.github/workflows/build.yml +66 -0
- data/.github/workflows/pre-merge.yml +72 -0
- data/.sonar.settings +4 -0
- data/.travis.yml +78 -12
- data/CHANGELOG.md +62 -0
- data/Dockerfile +25 -14
- data/LICENSE +4418 -17
- data/LICENSE.rb +1 -1
- data/README.md +3 -3
- data/Rakefile +8 -1
- data/SDK_VERSION +1 -1
- data/VERSION +1 -1
- data/bin/test_projects_cleanup.rb +45 -3
- data/ci/mssql/pom.xml +62 -0
- data/ci/mysql/pom.xml +57 -0
- data/ci/postgresql/pom.xml +57 -0
- data/ci/redshift/pom.xml +1 -1
- data/dev-gooddata-sso.pub.encrypted +40 -40
- data/gdc_fossa_ruby_sdk.yaml +1 -0
- data/gooddata.gemspec +10 -6
- data/k8s/charts/lcm-bricks/Chart.yaml +1 -1
- data/k8s/charts/lcm-bricks/templates/prometheus/alertingRules.yaml +11 -1
- data/lcm.rake +2 -2
- data/lib/gooddata/bricks/middleware/aws_middleware.rb +35 -9
- data/lib/gooddata/cloud_resources/blobstorage/blobstorage_client.rb +98 -0
- data/lib/gooddata/cloud_resources/{cloud_resouce_factory.rb → cloud_resource_factory.rb} +8 -0
- data/lib/gooddata/cloud_resources/cloud_resources.rb +1 -1
- data/lib/gooddata/cloud_resources/mssql/drivers/.gitkeepme +0 -0
- data/lib/gooddata/cloud_resources/mssql/mssql_client.rb +122 -0
- data/lib/gooddata/cloud_resources/mysql/drivers/.gitkeepme +0 -0
- data/lib/gooddata/cloud_resources/mysql/mysql_client.rb +111 -0
- data/lib/gooddata/cloud_resources/postgresql/drivers/.gitkeepme +0 -0
- data/lib/gooddata/cloud_resources/postgresql/postgresql_client.rb +106 -0
- data/lib/gooddata/cloud_resources/snowflake/snowflake_client.rb +18 -1
- data/lib/gooddata/commands/scaffold.rb +9 -10
- data/lib/gooddata/core/nil_logger.rb +3 -1
- data/lib/gooddata/helpers/data_helper.rb +9 -5
- data/lib/gooddata/helpers/global_helpers.rb +6 -5
- data/lib/gooddata/lcm/actions/associate_clients.rb +8 -2
- data/lib/gooddata/lcm/actions/base_action.rb +0 -2
- data/lib/gooddata/lcm/actions/collect_meta.rb +3 -1
- data/lib/gooddata/lcm/actions/migrate_gdc_date_dimension.rb +3 -2
- data/lib/gooddata/lcm/actions/provision_clients.rb +31 -10
- data/lib/gooddata/lcm/actions/synchronize_clients.rb +56 -7
- data/lib/gooddata/lcm/actions/synchronize_dataset_mappings.rb +64 -0
- data/lib/gooddata/lcm/actions/synchronize_ldm.rb +19 -8
- data/lib/gooddata/lcm/actions/synchronize_user_filters.rb +16 -9
- data/lib/gooddata/lcm/actions/synchronize_users.rb +7 -6
- data/lib/gooddata/lcm/actions/update_metric_formats.rb +185 -0
- data/lib/gooddata/lcm/data/delete_from_lcm_release.sql.erb +5 -0
- data/lib/gooddata/lcm/helpers/release_table_helper.rb +42 -8
- data/lib/gooddata/lcm/lcm2.rb +5 -2
- data/lib/gooddata/lcm/types/base_type.rb +0 -2
- data/lib/gooddata/mixins/md_object_query.rb +9 -6
- data/lib/gooddata/models/blueprint/project_blueprint.rb +0 -2
- data/lib/gooddata/models/client.rb +14 -12
- data/lib/gooddata/models/data_source.rb +668 -0
- data/lib/gooddata/models/dataset_mapping.rb +36 -0
- data/lib/gooddata/models/domain.rb +3 -2
- data/lib/gooddata/models/metadata/analytical_dashboard.rb +49 -0
- data/lib/gooddata/models/metadata/analytical_visualization_object.rb +30 -0
- data/lib/gooddata/models/metadata/label.rb +26 -27
- data/lib/gooddata/models/metadata/visualization_object.rb +50 -0
- data/lib/gooddata/models/project.rb +66 -19
- data/lib/gooddata/models/schedule.rb +13 -1
- data/lib/gooddata/models/user_filters/user_filter_builder.rb +58 -54
- data/lib/gooddata/models/user_group.rb +0 -1
- data/lib/gooddata/rest/connection.rb +6 -4
- data/lib/gooddata/rest/phmap.rb +2 -1
- data/lib/gooddata.rb +2 -0
- data/rubydev_public.gpg.encrypted +51 -51
- data/rubydev_secret_keys.gpg.encrypted +109 -109
- metadata +52 -27
- data/DEPENDENCIES.md +0 -880
- data/lib/gooddata/bricks/middleware/bulk_salesforce_middleware.rb +0 -37
- data/lib/gooddata/helpers/data_source_helpers.rb +0 -47
@@ -5,7 +5,6 @@
|
|
5
5
|
# LICENSE file in the root directory of this source tree.
|
6
6
|
|
7
7
|
require 'cgi'
|
8
|
-
require 'active_support/core_ext/hash/compact'
|
9
8
|
|
10
9
|
require_relative 'profile'
|
11
10
|
require_relative '../extensions/enumerable'
|
@@ -457,10 +456,12 @@ Available values for setting language are: #{available_languages}."
|
|
457
456
|
alias_method :add_clients_settings, :update_clients_settings
|
458
457
|
|
459
458
|
def update_clients(data, options = {})
|
459
|
+
results = []
|
460
460
|
data.group_by(&:data_product_id).each do |data_product_id, client_update_data|
|
461
461
|
data_product = data_products(data_product_id)
|
462
|
-
data_product.update_clients(client_update_data, options)
|
462
|
+
results.concat data_product.update_clients(client_update_data, options)
|
463
463
|
end
|
464
|
+
results
|
464
465
|
end
|
465
466
|
|
466
467
|
# Update user in domain
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
#
|
4
|
+
# Copyright (c) 2010-2021 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
|
+
require_relative 'analytical_visualization_object'
|
9
|
+
|
10
|
+
module GoodData
|
11
|
+
class AnalyticalDashboard < GoodData::AnalyticalVisualizationObject
|
12
|
+
EMPTY_OBJECT = {
|
13
|
+
'analyticalDashboard' => {
|
14
|
+
'content' => {
|
15
|
+
'filterContext' => '',
|
16
|
+
'layout' => {},
|
17
|
+
'widgets' => []
|
18
|
+
},
|
19
|
+
'meta' => {
|
20
|
+
'deprecated' => '0',
|
21
|
+
'summary' => '',
|
22
|
+
'title' => ''
|
23
|
+
}
|
24
|
+
}
|
25
|
+
}
|
26
|
+
|
27
|
+
ASSIGNABLE_MEMBERS = %i[filterContext layout widgets deprecated summary title]
|
28
|
+
|
29
|
+
class << self
|
30
|
+
# Method intended to get all AnalyticalDashboard objects in a specified project
|
31
|
+
#
|
32
|
+
# @param options [Hash] the options hash
|
33
|
+
# @option options [Boolean] :full with true value will pull in full objects. Default is false value
|
34
|
+
# @return [Array<GoodData::AnalyticalDashboard>] Return AnalyticalDashboard list
|
35
|
+
def all(options = { :client => GoodData.connection, :project => GoodData.project })
|
36
|
+
query('analyticalDashboard', AnalyticalDashboard, options)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Create Analytical Dashboard in the specify project
|
40
|
+
#
|
41
|
+
# @param analytical_dashboard [Hash] the data of object will be created
|
42
|
+
# @param options [Hash] The project that the object will be created in
|
43
|
+
# @return GoodData::AnalyticalDashboard object
|
44
|
+
def create(analytical_dashboard = {}, options = { :client => GoodData.client, :project => GoodData.project })
|
45
|
+
GoodData::AnalyticalVisualizationObject.create(analytical_dashboard, AnalyticalDashboard, EMPTY_OBJECT, ASSIGNABLE_MEMBERS, options)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
#
|
4
|
+
# Copyright (c) 2010-2021 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 AnalyticalVisualizationObject < GoodData::MdObject
|
10
|
+
class << self
|
11
|
+
# Create a specify object in the specify project
|
12
|
+
#
|
13
|
+
# @param object_data [Hash] the data of object will be created
|
14
|
+
# @param klass [Class] A class used for instantiating the returned data
|
15
|
+
# @param empty_data_object [Hash] the empty data of object will be created
|
16
|
+
# @param assignable_properties [Hash] the properties allow updating
|
17
|
+
# @param options [Hash] The project that the object will be created in
|
18
|
+
# @return klass object
|
19
|
+
def create(object_data, klass, empty_data_object = {}, assignable_properties = [], options = { :client => GoodData.client, :project => GoodData.project })
|
20
|
+
client, project = GoodData.get_client_and_project(GoodData::Helpers.symbolize_keys(options))
|
21
|
+
|
22
|
+
res = client.create(klass, GoodData::Helpers.deep_dup(GoodData::Helpers.stringify_keys(empty_data_object)), :project => project)
|
23
|
+
object_data.each do |k, v|
|
24
|
+
res.send("#{k}=", v) if assignable_properties.include? k
|
25
|
+
end
|
26
|
+
res
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -40,22 +40,20 @@ module GoodData
|
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
43
|
-
# Gets valid elements
|
43
|
+
# Gets valid elements of a label for a specific paging (:offset and :limit) or get validElements of a specific value (:filter).
|
44
|
+
# In the case filter a specific value, because the API /validElements only filter by partial match, we need to filter again at client side for exact match.
|
44
45
|
# @return [Array] Results
|
45
46
|
def get_valid_elements(*args)
|
46
|
-
|
47
|
-
|
48
|
-
# so we do a preliminary first request to check and then increase the limit if needed
|
49
|
-
if results['validElements']['paging']['total'].to_i != params[:limit]
|
47
|
+
if args && !args.empty? && args.first[:filter]
|
48
|
+
params = args.first
|
50
49
|
params[:limit] = 100_000
|
51
50
|
results, = valid_elements params
|
52
|
-
|
53
|
-
|
54
|
-
i['element']['title'] != params[:filter]
|
55
|
-
end
|
51
|
+
results['validElements']['items'] = results['validElements']['items'].select do |i|
|
52
|
+
i['element']['title'] == params[:filter]
|
56
53
|
end
|
54
|
+
else
|
55
|
+
results, = valid_elements(*args)
|
57
56
|
end
|
58
|
-
|
59
57
|
results
|
60
58
|
end
|
61
59
|
|
@@ -74,24 +72,25 @@ module GoodData
|
|
74
72
|
# @option options [Number] :limit limits the number of values to certain number. Default is 100
|
75
73
|
# @return [Array]
|
76
74
|
def values(options = {})
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
end
|
91
|
-
break if elements['items'].count < page_limit
|
92
|
-
offset += page_limit
|
75
|
+
all_values = []
|
76
|
+
offset = options[:offset] || 0
|
77
|
+
page_limit = options[:limit] || 100
|
78
|
+
loop do
|
79
|
+
results = get_valid_elements(limit: page_limit, offset: offset)
|
80
|
+
|
81
|
+
elements = results['validElements']
|
82
|
+
elements['items'].map do |el|
|
83
|
+
v = el['element']
|
84
|
+
all_values << {
|
85
|
+
:value => v['title'],
|
86
|
+
:uri => v['uri']
|
87
|
+
}
|
93
88
|
end
|
89
|
+
break if elements['items'].count < page_limit
|
90
|
+
|
91
|
+
offset += page_limit
|
94
92
|
end
|
93
|
+
all_values
|
95
94
|
end
|
96
95
|
|
97
96
|
def values_count
|
@@ -136,7 +135,7 @@ module GoodData
|
|
136
135
|
if status_url
|
137
136
|
results = client.poll_on_response(status_url) do |body|
|
138
137
|
status = body['taskState'] && body['taskState']['status']
|
139
|
-
status == 'RUNNING' || status == 'PREPARED'
|
138
|
+
status == 'RUNNING' || status == 'PREPARED' || body['uri']
|
140
139
|
end
|
141
140
|
end
|
142
141
|
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
#
|
4
|
+
# Copyright (c) 2010-2021 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
|
+
require_relative 'analytical_visualization_object'
|
9
|
+
|
10
|
+
module GoodData
|
11
|
+
class VisualizationObject < GoodData::AnalyticalVisualizationObject
|
12
|
+
EMPTY_OBJECT = {
|
13
|
+
'visualizationObject' => {
|
14
|
+
'content' => {
|
15
|
+
'buckets' => [],
|
16
|
+
'properties' => '',
|
17
|
+
'visualizationClass' => {}
|
18
|
+
},
|
19
|
+
'links' => {},
|
20
|
+
'meta' => {
|
21
|
+
'deprecated' => '0',
|
22
|
+
'summary' => '',
|
23
|
+
'title' => ''
|
24
|
+
}
|
25
|
+
}
|
26
|
+
}
|
27
|
+
|
28
|
+
ASSIGNABLE_MEMBERS = %i[buckets properties visualizationClass deprecated summary title]
|
29
|
+
|
30
|
+
class << self
|
31
|
+
# Method intended to get all VisualizationObject objects in a specified project
|
32
|
+
#
|
33
|
+
# @param options [Hash] the options hash
|
34
|
+
# @option options [Boolean] :full with true value to pull full objects
|
35
|
+
# @return [Array<GoodData::VisualizationObject>] Return VisualizationObject list
|
36
|
+
def all(options = { :client => GoodData.connection, :project => GoodData.project })
|
37
|
+
query('visualizationObject', VisualizationObject, options)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Create Visualization Object in the specify project
|
41
|
+
#
|
42
|
+
# @param visualization_object [Hash] the data of object will be created
|
43
|
+
# @param options [Hash] The project that the object will be created in
|
44
|
+
# @return GoodData::VisualizationObject object
|
45
|
+
def create(visualization_object = {}, options = { :client => GoodData.client, :project => GoodData.project })
|
46
|
+
GoodData::AnalyticalVisualizationObject.create(visualization_object, VisualizationObject, EMPTY_OBJECT, ASSIGNABLE_MEMBERS, options)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
#
|
3
|
-
# Copyright (c) 2010-
|
3
|
+
# Copyright (c) 2010-2021 GoodData Corporation. All rights reserved.
|
4
4
|
# This source code is licensed under the BSD-style license found in the
|
5
5
|
# LICENSE file in the root directory of this source tree.
|
6
6
|
|
@@ -13,7 +13,6 @@ require 'zip'
|
|
13
13
|
require 'net/smtp'
|
14
14
|
|
15
15
|
require 'active_support/core_ext/hash/except'
|
16
|
-
require 'active_support/core_ext/hash/compact'
|
17
16
|
require 'active_support/core_ext/hash/slice'
|
18
17
|
|
19
18
|
require_relative '../exceptions/no_project_error'
|
@@ -31,6 +30,7 @@ require_relative 'process'
|
|
31
30
|
require_relative 'project_log_formatter'
|
32
31
|
require_relative 'project_role'
|
33
32
|
require_relative 'blueprint/blueprint'
|
33
|
+
require_relative 'dataset_mapping'
|
34
34
|
|
35
35
|
require_relative 'metadata/scheduled_mail'
|
36
36
|
require_relative 'metadata/scheduled_mail/dashboard_attachment'
|
@@ -38,7 +38,8 @@ require_relative 'metadata/scheduled_mail/report_attachment'
|
|
38
38
|
|
39
39
|
module GoodData
|
40
40
|
class Project < Rest::Resource
|
41
|
-
|
41
|
+
USER_ACCOUNT_PATH = '/gdc/account/profile/'
|
42
|
+
USERSPROJECTS_PATH = USER_ACCOUNT_PATH + '%s/projects'
|
42
43
|
PROJECTS_PATH = '/gdc/projects'
|
43
44
|
PROJECT_PATH = '/gdc/projects/%s'
|
44
45
|
SLIS_PATH = '/ldm/singleloadinterface'
|
@@ -255,6 +256,22 @@ module GoodData
|
|
255
256
|
transfer_schedules(from_project, to_project)
|
256
257
|
end
|
257
258
|
|
259
|
+
def get_dataset_mapping(from_project)
|
260
|
+
GoodData::DatasetMapping.get(:client => from_project.client, :project => from_project)
|
261
|
+
end
|
262
|
+
|
263
|
+
def update_dataset_mapping(model_mapping_json, to_project)
|
264
|
+
dataset_mapping = GoodData::DatasetMapping.new(model_mapping_json)
|
265
|
+
res = dataset_mapping.save(:client => to_project.client, :project => to_project)
|
266
|
+
status = res&.dig('datasetMappings', 'items').nil? ? "Failed" : "OK"
|
267
|
+
count = "OK".eql?(status) ? res['datasetMappings']['items'].length : 0
|
268
|
+
{
|
269
|
+
to: to_project.pid,
|
270
|
+
count: count,
|
271
|
+
status: status
|
272
|
+
}
|
273
|
+
end
|
274
|
+
|
258
275
|
# @param from_project The source project
|
259
276
|
# @param to_project The target project
|
260
277
|
# @param options Optional parameters
|
@@ -336,26 +353,22 @@ module GoodData
|
|
336
353
|
|
337
354
|
def get_data_source_alias(data_source_id, client, aliases)
|
338
355
|
unless aliases[data_source_id]
|
339
|
-
data_source = GoodData::
|
340
|
-
if data_source&.
|
356
|
+
data_source = GoodData::DataSource.from_id(data_source_id, client: client)
|
357
|
+
if data_source&.alias
|
341
358
|
aliases[data_source_id] = {
|
342
|
-
:type =>
|
343
|
-
:alias => data_source
|
359
|
+
:type => data_source.type,
|
360
|
+
:alias => data_source.alias
|
344
361
|
}
|
345
362
|
end
|
346
363
|
end
|
347
364
|
aliases[data_source_id]
|
348
365
|
end
|
349
366
|
|
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
367
|
def replace_process_data_source_ids(process_data, client, aliases)
|
355
368
|
component = process_data.dig(:process, :component)
|
356
369
|
if component&.dig(:configLocation, :dataSourceConfig)
|
357
370
|
the_alias = aliases[component[:configLocation][:dataSourceConfig][:id]]
|
358
|
-
process_data[:process][:component][:configLocation][:dataSourceConfig][:id] =
|
371
|
+
process_data[:process][:component][:configLocation][:dataSourceConfig][:id] = verify_data_source_alias(the_alias, client)
|
359
372
|
end
|
360
373
|
process_data[:process][:dataSources] = replace_data_source_ids(process_data[:process][:dataSources], client, aliases)
|
361
374
|
process_data
|
@@ -365,13 +378,35 @@ module GoodData
|
|
365
378
|
array_data_sources = []
|
366
379
|
if data_sources && !data_sources.empty?
|
367
380
|
data_sources.map do |data_source|
|
368
|
-
new_id =
|
381
|
+
new_id = verify_data_source_alias(aliases[data_source[:id]], client)
|
369
382
|
array_data_sources.push(:id => new_id)
|
370
383
|
end
|
371
384
|
end
|
372
385
|
array_data_sources
|
373
386
|
end
|
374
387
|
|
388
|
+
# Verify whether the data source exists in the domain using its alias
|
389
|
+
#
|
390
|
+
# @param [String] ds_alias The data source's alias
|
391
|
+
# @param [Object] client The Rest Client object
|
392
|
+
# @return [String] Id of the data source or failed with the reason
|
393
|
+
def verify_data_source_alias(ds_alias, client)
|
394
|
+
domain = client.connection.server.url
|
395
|
+
fail "The data source alias is empty, check your data source configuration." unless ds_alias
|
396
|
+
|
397
|
+
uri = "/gdc/dataload/dataSources/internal/availableAlias?alias=#{ds_alias[:alias]}"
|
398
|
+
res = client.get(uri)
|
399
|
+
fail "Unable to get information about the Data Source '#{ds_alias[:alias]}' in the domain '#{domain}'" unless res
|
400
|
+
fail "Unable to find the #{ds_alias[:type]} Data Source '#{ds_alias[:alias]}' in the domain '#{domain}'" if res['availableAlias']['available']
|
401
|
+
|
402
|
+
ds_type = res['availableAlias']['existingDataSource']['type']
|
403
|
+
if ds_type && ds_type != ds_alias[:type]
|
404
|
+
fail "Wrong Data Source type - the '#{ds_type}' type is expected but the Data Source '#{ds_alias[:alias]}' in the domain '#{domain}' has the '#{ds_alias[:type]}' type"
|
405
|
+
else
|
406
|
+
res['availableAlias']['existingDataSource']['id']
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
375
410
|
def transfer_user_groups(from_project, to_project)
|
376
411
|
from_project.user_groups.map do |ug|
|
377
412
|
# migrate groups
|
@@ -438,7 +473,9 @@ module GoodData
|
|
438
473
|
local_stuff = local_schedules.map do |s|
|
439
474
|
v = s.to_hash
|
440
475
|
after_schedule = local_schedules.find { |s2| s.trigger_id == s2.obj_id }
|
441
|
-
|
476
|
+
after_process_schedule = from_project_processes.find { |p| after_schedule && p.obj_id == after_schedule.process_id }
|
477
|
+
v[:after] = s.trigger_id && after_process_schedule && after_schedule && after_schedule.name
|
478
|
+
v[:trigger_execution_status] = s.trigger_execution_status
|
442
479
|
v[:remote_schedule] = s
|
443
480
|
v[:params] = v[:params].except("EXECUTABLE", "PROCESS_ID")
|
444
481
|
v.compact
|
@@ -507,6 +544,7 @@ module GoodData
|
|
507
544
|
schedule.params = (schedule_spec[:params] || {})
|
508
545
|
schedule.cron = schedule_spec[:cron] if schedule_spec[:cron]
|
509
546
|
schedule.after = schedule_cache[schedule_spec[:after]] if schedule_spec[:after]
|
547
|
+
schedule.trigger_execution_status = schedule_cache[schedule_spec[:trigger_execution_status]] if schedule_spec[:after]
|
510
548
|
schedule.hidden_params = schedule_spec[:hidden_params] || {}
|
511
549
|
if process_spec.type != :dataload
|
512
550
|
schedule.executable = schedule_spec[:executable] || (process_spec.type == :ruby ? 'main.rb' : 'main.grf')
|
@@ -567,7 +605,8 @@ module GoodData
|
|
567
605
|
hidden_params: schedule_spec[:hidden_params],
|
568
606
|
name: schedule_spec[:name],
|
569
607
|
reschedule: schedule_spec[:reschedule],
|
570
|
-
state: schedule_spec[:state]
|
608
|
+
state: schedule_spec[:state],
|
609
|
+
trigger_execution_status: schedule_spec[:trigger_execution_status]
|
571
610
|
}
|
572
611
|
end
|
573
612
|
end
|
@@ -1716,7 +1755,7 @@ module GoodData
|
|
1716
1755
|
end
|
1717
1756
|
end
|
1718
1757
|
diff_results = diff_results.map do |u|
|
1719
|
-
u[:login_uri] =
|
1758
|
+
u[:login_uri] = USER_ACCOUNT_PATH + u[:login]
|
1720
1759
|
u
|
1721
1760
|
end
|
1722
1761
|
return diff_results if options[:dry_run]
|
@@ -1952,17 +1991,17 @@ module GoodData
|
|
1952
1991
|
|
1953
1992
|
def resolve_roles(login, desired_roles, options = {})
|
1954
1993
|
user = if login.is_a?(String) && login.include?('@')
|
1955
|
-
|
1994
|
+
USER_ACCOUNT_PATH + login
|
1956
1995
|
elsif login.is_a?(String)
|
1957
1996
|
login
|
1958
1997
|
elsif login.is_a?(Hash) && login[:login]
|
1959
|
-
|
1998
|
+
USER_ACCOUNT_PATH + login[:login]
|
1960
1999
|
elsif login.is_a?(Hash) && login[:uri]
|
1961
2000
|
login[:uri]
|
1962
2001
|
elsif login.respond_to?(:uri) && login.uri
|
1963
2002
|
login.uri
|
1964
2003
|
elsif login.respond_to?(:login) && login.login
|
1965
|
-
|
2004
|
+
USER_ACCOUNT_PATH + login.login
|
1966
2005
|
else
|
1967
2006
|
fail "Unsupported user specification #{login}"
|
1968
2007
|
end
|
@@ -2000,6 +2039,14 @@ module GoodData
|
|
2000
2039
|
GoodData::Project.transfer_etl(client, self, target)
|
2001
2040
|
end
|
2002
2041
|
|
2042
|
+
def dataset_mapping
|
2043
|
+
GoodData::Project.get_dataset_mapping(self)
|
2044
|
+
end
|
2045
|
+
|
2046
|
+
def update_dataset_mapping(model_mapping_json)
|
2047
|
+
GoodData::Project.update_dataset_mapping(model_mapping_json, self)
|
2048
|
+
end
|
2049
|
+
|
2003
2050
|
def transfer_processes(target)
|
2004
2051
|
GoodData::Project.transfer_processes(self, target)
|
2005
2052
|
end
|
@@ -101,6 +101,7 @@ module GoodData
|
|
101
101
|
|
102
102
|
schedule.name = options[:name]
|
103
103
|
schedule.set_trigger(trigger)
|
104
|
+
schedule.trigger_execution_status = options[:trigger_execution_status]
|
104
105
|
schedule.params = default_opts[:params].merge(options[:params] || {})
|
105
106
|
schedule.hidden_params = options[:hidden_params] || {}
|
106
107
|
schedule.timezone = options[:timezone] || default_opts[:timezone]
|
@@ -468,6 +469,7 @@ module GoodData
|
|
468
469
|
hidden_params: hidden_params,
|
469
470
|
cron: cron,
|
470
471
|
trigger_id: trigger_id,
|
472
|
+
trigger_execution_status: trigger_execution_status,
|
471
473
|
timezone: timezone,
|
472
474
|
uri: uri,
|
473
475
|
reschedule: reschedule,
|
@@ -486,6 +488,16 @@ module GoodData
|
|
486
488
|
self
|
487
489
|
end
|
488
490
|
|
491
|
+
def trigger_execution_status
|
492
|
+
json['schedule']['triggerExecutionStatus']
|
493
|
+
end
|
494
|
+
|
495
|
+
def trigger_execution_status=(trigger_execution_status)
|
496
|
+
json['schedule']['triggerExecutionStatus'] = trigger_execution_status
|
497
|
+
@dirty = true
|
498
|
+
self # rubocop:disable Lint/Void
|
499
|
+
end
|
500
|
+
|
489
501
|
def name
|
490
502
|
json['schedule']['name']
|
491
503
|
end
|
@@ -530,7 +542,7 @@ module GoodData
|
|
530
542
|
'hiddenParams' => GoodData::Helpers.encode_hidden_params(hidden_params)
|
531
543
|
}
|
532
544
|
}
|
533
|
-
|
545
|
+
res['schedule']['triggerExecutionStatus'] = trigger_execution_status if trigger_execution_status
|
534
546
|
res['schedule']['reschedule'] = reschedule if reschedule
|
535
547
|
|
536
548
|
res
|
@@ -7,7 +7,6 @@
|
|
7
7
|
require_relative '../project_log_formatter'
|
8
8
|
|
9
9
|
require 'active_support/core_ext/hash/indifferent_access'
|
10
|
-
require 'active_support/core_ext/hash/compact'
|
11
10
|
|
12
11
|
require 'gooddata/extensions/true'
|
13
12
|
require 'gooddata/extensions/false'
|
@@ -204,7 +203,7 @@ module GoodData
|
|
204
203
|
# so it precaches the values and still be able to function for larger ones even
|
205
204
|
# though that would mean tons of requests
|
206
205
|
def self.get_small_labels(labels_cache)
|
207
|
-
labels_cache.values.select { |label| label
|
206
|
+
labels_cache.values.select { |label| label &.values_count &. < 100_000 }
|
208
207
|
end
|
209
208
|
|
210
209
|
# Creates a MAQL expression(s) based on the filter defintion.
|
@@ -422,68 +421,73 @@ module GoodData
|
|
422
421
|
results: create_results + delete_results }
|
423
422
|
end
|
424
423
|
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
'userFilters'
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
424
|
+
if to_create.empty?
|
425
|
+
create_results = []
|
426
|
+
else
|
427
|
+
create_results = to_create.each_slice(100).flat_map do |batch|
|
428
|
+
batch.pmapcat do |related_uri, group|
|
429
|
+
group.each(&:save)
|
430
|
+
res = client.get("/gdc/md/#{project.pid}/userfilters?users=#{related_uri}")
|
431
|
+
items = res['userFilters']['items'].empty? ? [] : res['userFilters']['items'].first['userFilters']
|
432
|
+
|
433
|
+
payload = {
|
434
|
+
'userFilters' => {
|
435
|
+
'items' => [{
|
436
|
+
'user' => related_uri,
|
437
|
+
'userFilters' => items.concat(group.map(&:uri))
|
438
|
+
}]
|
439
|
+
}
|
437
440
|
}
|
438
|
-
|
439
|
-
res = client.post("/gdc/md/#{project.pid}/userfilters", payload)
|
441
|
+
res = client.post("/gdc/md/#{project.pid}/userfilters", payload)
|
440
442
|
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
443
|
+
# turn the errors from hashes into array of hashes
|
444
|
+
update_result = res['userFiltersUpdateResult'].flat_map do |k, v|
|
445
|
+
v.map { |r| { status: k.to_sym, user: r, type: :create } }
|
446
|
+
end
|
445
447
|
|
446
|
-
|
447
|
-
|
448
|
+
update_result.map do |result|
|
449
|
+
result[:status] == :failed ? result.merge(GoodData::Helpers.symbolize_keys(result[:user])) : result
|
450
|
+
end
|
448
451
|
end
|
449
452
|
end
|
453
|
+
project_log_formatter.log_user_filter_results(create_results, to_create)
|
454
|
+
create_errors = create_results.select { |r| r[:status] == :failed }
|
455
|
+
fail "Creating MUFs resulted in errors: #{create_errors}" if create_errors.any?
|
450
456
|
end
|
451
457
|
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
res = client.post("/gdc/md/#{project.pid}/userfilters", payload)
|
474
|
-
results.concat(res['userFiltersUpdateResult']
|
458
|
+
if to_delete.empty?
|
459
|
+
delete_results = []
|
460
|
+
elsif !options[:do_not_touch_filters_that_are_not_mentioned]
|
461
|
+
delete_results = to_delete.each_slice(100).flat_map do |batch|
|
462
|
+
batch.flat_map do |related_uri, group|
|
463
|
+
results = []
|
464
|
+
if related_uri
|
465
|
+
res = client.get("/gdc/md/#{project.pid}/userfilters?users=#{related_uri}")
|
466
|
+
items = res['userFilters']['items'].empty? ? [] : res['userFilters']['items'].first['userFilters']
|
467
|
+
payload = {
|
468
|
+
'userFilters' => {
|
469
|
+
'items' => [
|
470
|
+
{
|
471
|
+
'user' => related_uri,
|
472
|
+
'userFilters' => items - group.map(&:uri)
|
473
|
+
}
|
474
|
+
]
|
475
|
+
}
|
476
|
+
}
|
477
|
+
res = client.post("/gdc/md/#{project.pid}/userfilters", payload)
|
478
|
+
results.concat(res['userFiltersUpdateResult']
|
475
479
|
.flat_map { |k, v| v.map { |r| { status: k.to_sym, user: r, type: :delete } } }
|
476
480
|
.map { |result| result[:status] == :failed ? result.merge(GoodData::Helpers.symbolize_keys(result[:user])) : result })
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
end
|
482
|
-
end
|
481
|
+
end
|
482
|
+
group.peach(&:delete)
|
483
|
+
results
|
484
|
+
end
|
483
485
|
|
484
|
-
|
485
|
-
|
486
|
-
|
486
|
+
project_log_formatter.log_user_filter_results(delete_results, to_delete)
|
487
|
+
delete_errors = delete_results.select { |r| r[:status] == :failed } if delete_results
|
488
|
+
fail "Deleting MUFs resulted in errors: #{delete_errors}" if delete_errors&.any?
|
489
|
+
end
|
490
|
+
end
|
487
491
|
|
488
492
|
{ created: to_create, deleted: to_delete, results: create_results + (delete_results || []) }
|
489
493
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
#
|
3
|
-
# Copyright (c) 2010-
|
3
|
+
# Copyright (c) 2010-2021 GoodData Corporation. All rights reserved.
|
4
4
|
# This source code is licensed under the BSD-style license found in the
|
5
5
|
# LICENSE file in the root directory of this source tree.
|
6
6
|
|
@@ -26,6 +26,8 @@ module GoodData
|
|
26
26
|
class Connection
|
27
27
|
include MonitorMixin
|
28
28
|
|
29
|
+
HTTPS_PROTOCOL = 'https://'
|
30
|
+
HTTP_PROTOCOL = 'http://'
|
29
31
|
DEFAULT_URL = 'https://secure.gooddata.com'
|
30
32
|
LOGIN_PATH = '/gdc/account/login'
|
31
33
|
TOKEN_PATH = '/gdc/account/token'
|
@@ -712,12 +714,12 @@ ERR
|
|
712
714
|
|
713
715
|
def fix_server_url(server)
|
714
716
|
server = server.chomp('/')
|
715
|
-
if server.start_with?
|
716
|
-
server = server.sub
|
717
|
+
if server.start_with? HTTP_PROTOCOL
|
718
|
+
server = server.sub HTTP_PROTOCOL, HTTPS_PROTOCOL
|
717
719
|
GoodData.logger.warn 'You specified the HTTP protocol in your server string. It has been autofixed to HTTPS.'
|
718
720
|
end
|
719
721
|
|
720
|
-
server =
|
722
|
+
server = HTTPS_PROTOCOL + server unless server.start_with? HTTPS_PROTOCOL
|
721
723
|
server
|
722
724
|
end
|
723
725
|
end
|