gooddata 0.6.50 → 0.6.51

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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +12 -0
  3. data/.gitignore +1 -0
  4. data/CHANGELOG.md +3 -0
  5. data/gooddata.gemspec +2 -1
  6. data/lib/gooddata/bricks/middleware/aws_middleware.rb +4 -0
  7. data/lib/gooddata/bricks/middleware/decode_params_middleware.rb +1 -1
  8. data/lib/gooddata/bricks/middleware/dwh_middleware.rb +1 -0
  9. data/lib/gooddata/bricks/middleware/gooddata_middleware.rb +1 -0
  10. data/lib/gooddata/bricks/middleware/logger_middleware.rb +2 -1
  11. data/lib/gooddata/core/nil_logger.rb +9 -0
  12. data/lib/gooddata/goodzilla/goodzilla.rb +1 -1
  13. data/lib/gooddata/helpers/data_helper.rb +1 -0
  14. data/lib/gooddata/helpers/global_helpers_params.rb +54 -27
  15. data/lib/gooddata/lcm/actions/apply_custom_maql.rb +70 -0
  16. data/lib/gooddata/lcm/actions/associate_clients.rb +17 -4
  17. data/lib/gooddata/lcm/actions/collect_clients.rb +4 -1
  18. data/lib/gooddata/lcm/actions/collect_segment_clients.rb +1 -0
  19. data/lib/gooddata/lcm/actions/collect_segments.rb +15 -2
  20. data/lib/gooddata/lcm/actions/create_segment_masters.rb +2 -2
  21. data/lib/gooddata/lcm/actions/provision_clients.rb +2 -4
  22. data/lib/gooddata/lcm/actions/purge_clients.rb +2 -2
  23. data/lib/gooddata/lcm/actions/synchronize_etls_in_segment.rb +81 -0
  24. data/lib/gooddata/lcm/actions/synchronize_label_types.rb +2 -2
  25. data/lib/gooddata/lcm/actions/synchronize_processes.rb +3 -1
  26. data/lib/gooddata/lcm/data/create_lcm_release.sql.erb +2 -1
  27. data/lib/gooddata/lcm/helpers/check_helper.rb +1 -1
  28. data/lib/gooddata/lcm/lcm.rb +29 -11
  29. data/lib/gooddata/lcm/lcm2.rb +82 -20
  30. data/lib/gooddata/models/domain.rb +22 -1
  31. data/lib/gooddata/models/metadata.rb +13 -8
  32. data/lib/gooddata/models/metadata/attribute.rb +1 -1
  33. data/lib/gooddata/models/metadata/report_definition.rb +1 -0
  34. data/lib/gooddata/models/profile.rb +1 -1
  35. data/lib/gooddata/models/project.rb +162 -38
  36. data/lib/gooddata/models/project_creator.rb +26 -6
  37. data/lib/gooddata/models/project_log_formatter.rb +204 -0
  38. data/lib/gooddata/models/schedule.rb +2 -21
  39. data/lib/gooddata/models/segment.rb +26 -0
  40. data/lib/gooddata/models/style_setting.rb +5 -1
  41. data/lib/gooddata/models/user_filters/user_filter_builder.rb +9 -0
  42. data/lib/gooddata/rest/connection.rb +4 -1
  43. data/lib/gooddata/version.rb +1 -1
  44. data/spec/environment/development.rb +29 -0
  45. data/spec/environment/environment.rb +14 -2
  46. data/spec/environment/{hotfix.rb → testing.rb} +0 -0
  47. data/spec/integration/date_dim_switch_spec.rb +3 -5
  48. data/spec/integration/lcm_spec.rb +24 -21
  49. data/spec/integration/project_spec.rb +16 -0
  50. data/spec/integration/segments_spec.rb +1 -1
  51. data/spec/unit/helpers/global_helpers_spec.rb +26 -2
  52. data/spec/unit/helpers_spec.rb +20 -0
  53. data/spec/unit/models/project_creator_spec.rb +3 -2
  54. metadata +29 -12
  55. data/lib/gooddata/lcm/actions/ensure_titles.rb +0 -54
  56. data/spec/environment/develop.rb +0 -46
@@ -0,0 +1,204 @@
1
+ require_relative 'project'
2
+
3
+ module GoodData
4
+ class ProjectLogFormatter
5
+ def initialize(project)
6
+ @project = project
7
+ @users_cache = nil
8
+ end
9
+
10
+ # Log created users
11
+ #
12
+ # @param created_users [Array<Hash>] collection of created user result, e.g:
13
+ # [
14
+ # {
15
+ # type => :successful || :failed,
16
+ # user => '/gdc/account/profile/abc@gooddata.com',
17
+ # message => error_message,
18
+ # reason: error_message
19
+ # },
20
+ # ...
21
+ # ]
22
+ # @param new_users [Array<Hash>] collection of new users to be created
23
+ # [
24
+ # {
25
+ # login => 'xxx@gooddata.com',
26
+ # role_title => 'Editor' || 'Admin' || ...
27
+ # },
28
+ # ...
29
+ # ]
30
+ # @return nil
31
+ def log_created_users(created_users, new_users)
32
+ created_users.each do |created_user|
33
+ user_login = to_user_login(created_user[:user])
34
+ if created_user[:type] == :successful
35
+ user_data = new_users.find { |new_user| new_user[:login] == user_login }
36
+ GoodData.logger.info("Added new user=#{user_login}, roles=#{user_data[:role_title]} to project=#{@project.pid}.")
37
+ elsif created_user[:type] == :failed
38
+ error_message = created_user[:message] || created_user[:reason]
39
+ GoodData.logger.error("Failed to add user=#{user_login} to project=#{@project.pid}. Error: #{error_message}")
40
+ end
41
+ end
42
+ end
43
+
44
+ # Log updated users
45
+ #
46
+ # @param updated_users [Array<Hash>] collection of updated user result, e.g:
47
+ # [
48
+ # {
49
+ # type => :successful || :failed,
50
+ # user => '/gdc/account/profile/abc@gooddata.com',
51
+ # message => error_message,
52
+ # reason: error_message
53
+ # },
54
+ # ...
55
+ # ]
56
+ # @param changed_users [Array<Hash>] collection of changed users to be updated
57
+ # [
58
+ # {
59
+ # old_obj: {
60
+ # :login => '/gdc/account/profile/abc@gooddata.com',
61
+ # :role => '/gdc/projects/clp4z1qw60o0t048tov909b1xi4qztay/roles/5'
62
+ # },
63
+ # new_obj: {
64
+ # :role_title => 'Editor' || 'Admin' || ...
65
+ # }
66
+ # },
67
+ # ...
68
+ # ]
69
+ # @param role_list [Array<ProjectRole>] project roles
70
+ # @return nil
71
+ def log_updated_users(updated_users, changed_users, role_list)
72
+ updated_users.each do |updated_user|
73
+ user_login = to_user_login(updated_user[:user])
74
+ if updated_user[:type] == :successful
75
+ changed_user = changed_users.find { |user| user[:old_obj][:login] == user_login }
76
+ old_user_data = changed_user[:old_obj]
77
+ old_role_uris = old_user_data[:role] || old_user_data[:roles]
78
+ old_role_titles = old_role_uris.map do |old_role_uri|
79
+ old_role = @project.get_role(old_role_uri, role_list)
80
+ old_role && old_role.title
81
+ end
82
+ new_role_titles = changed_user[:new_obj][:role_title]
83
+ GoodData.logger.info("Update user=#{user_login} from old_roles=#{old_role_titles} to new_roles=#{new_role_titles} in project=#{@project.pid}.")
84
+ elsif updated_user[:type] == :failed
85
+ error_message = updated_user[:message] || updated_user[:reason]
86
+ GoodData.logger.error("Failed to update user=#{user_login} to project=#{@project.pid}. Error: #{error_message}")
87
+ end
88
+ end
89
+ end
90
+
91
+ # Log disabled users
92
+ #
93
+ # @param disabled_users [Array<Hash>] collection of disabled user result, e.g:
94
+ # [
95
+ # {
96
+ # type => :successful || :failed,
97
+ # user => '/gdc/account/profile/abc@gooddata.com',
98
+ # message => error_message,
99
+ # reason: error_message
100
+ # },
101
+ # ...
102
+ # ]
103
+ # @return nil
104
+ def log_disabled_users(disabled_users)
105
+ disabled_users.each do |disabled_user|
106
+ user_login = to_user_login(disabled_user[:user])
107
+ if disabled_user[:type] == :successful
108
+ GoodData.logger.warn("Disable user=#{user_login} in project=#{@project.pid}")
109
+ elsif disabled_user[:type] == :failed
110
+ error_message = disabled_user[:message] || disabled_user[:reason]
111
+ GoodData.logger.error("Failed to disable user=#{user_login} in project=#{@project.pid}. Error: #{error_message}")
112
+ end
113
+ end
114
+ end
115
+
116
+ # Log removed users
117
+ #
118
+ # @param removed_users [Array<Hash>] collection of removed user result, e.g:
119
+ # [
120
+ # {
121
+ # type => :successful || :failed,
122
+ # user => {
123
+ # login => 'abc@gooddata.com'
124
+ # },
125
+ # message => error_message
126
+ # },
127
+ # ...
128
+ # ]
129
+ # @return nil
130
+ def log_removed_users(removed_users)
131
+ removed_users.each do |removed_user|
132
+ user_login = to_user_login(removed_user[:user])
133
+ if removed_user[:type] == :successful
134
+ GoodData.logger.warn("Remove user=#{user_login} out of project=#{@project.pid}")
135
+ elsif removed_user[:type] == :failed
136
+ error_message = removed_user[:message]
137
+ GoodData.logger.error("Failed to remove user=#{user_login} out of project=#{@project.pid}. Error: #{error_message}")
138
+ end
139
+ end
140
+ end
141
+
142
+ # Log user filters results
143
+ #
144
+ # @param results [Array<Hash>] user-filter results
145
+ # [
146
+ # {
147
+ # status: :successful || :failed,
148
+ # type: :create || delete,
149
+ # user: user_profile_url
150
+ # },
151
+ # ...
152
+ # ]
153
+ # @param user_filters [Hash] user-filters data
154
+ # {
155
+ # user_profile_url => MandatoryUserFilter,
156
+ # ...
157
+ # }
158
+ # @return nil
159
+ def log_user_filter_results(results, user_filters)
160
+ results ||= []
161
+ results.each do |result|
162
+ user_profile_url = result[:user]
163
+ status = result[:status]
164
+ operator = result[:type]
165
+ if status == :successful
166
+ filter_uris = user_filters[user_profile_url].map(&:uri)
167
+ if operator == :create && GoodData.logger.info?
168
+ readable_user_login = users_cache[user_profile_url] || user_profile_url
169
+ GoodData.logger.info "Created user-filter=#{filter_uris} for user=#{readable_user_login} in project=#{@project.pid}"
170
+ elsif operator == :delete && GoodData.logger.warn?
171
+ readable_user_login = users_cache[user_profile_url] || user_profile_url
172
+ GoodData.logger.warn "Deleted user-filter=#{filter_uris} of user=#{readable_user_login} in project=#{@project.pid}"
173
+ end
174
+ else
175
+ error_message = result[:message]
176
+ if operator == :create && GoodData.logger.error?
177
+ GoodData.logger.error "Failed to create user-filters for user=#{user_profile_url} in project=#{@project.pid}. Error: #{error_message}"
178
+ elsif operator == :delete && GoodData.logger.error?
179
+ GoodData.logger.error "Failed to delete user-filters from user=#{user_profile_url} in project=#{@project.pid}. Error: #{error_message}"
180
+ end
181
+ end
182
+ end
183
+ end
184
+
185
+ private
186
+
187
+ def users_cache
188
+ if @users_cache.nil?
189
+ @users_cache = Hash[@project.users.map { |user| [user.profile_url, user.login] }]
190
+ end
191
+ @users_cache
192
+ end
193
+
194
+ def to_user_login(user)
195
+ if user.is_a?(String) && user.start_with?('/gdc/account/profile/')
196
+ GoodData::Helpers.last_uri_part(user)
197
+ elsif user.is_a?(Hash) && user[:login]
198
+ user[:login]
199
+ else
200
+ user
201
+ end
202
+ end
203
+ end
204
+ end
@@ -371,7 +371,7 @@ module GoodData
371
371
  #
372
372
  # @param new_hidden_param [String] Hidden parameters to be set
373
373
  def hidden_params=(new_hidden_params = {})
374
- @json['schedule']['hiddenParams'] = stringify_values(new_hidden_params)
374
+ @json['schedule']['hiddenParams'] = GoodData::Helpers.stringify_values(new_hidden_params)
375
375
  @dirty = true
376
376
  self
377
377
  end
@@ -391,7 +391,7 @@ module GoodData
391
391
  'PROCESS_ID' => process_id,
392
392
  'EXECUTABLE' => executable
393
393
  }
394
- @json['schedule']['params'] = default_params.merge(stringify_values(new_params))
394
+ @json['schedule']['params'] = default_params.merge(GoodData::Helpers.stringify_values(new_params))
395
395
  @dirty = true
396
396
  self
397
397
  end
@@ -531,24 +531,5 @@ module GoodData
531
531
 
532
532
  res
533
533
  end
534
-
535
- private
536
-
537
- def stringify_values(hash)
538
- Hash[
539
- hash.map do |k, v|
540
- val = case v
541
- when nil
542
- v
543
- when Hash
544
- stringify_values(v)
545
- else
546
- v.to_s
547
- end
548
-
549
- [k, val]
550
- end
551
- ]
552
- end
553
534
  end
554
535
  end
@@ -180,6 +180,32 @@ module GoodData
180
180
  client.create(ClientSynchronizationResult, res)
181
181
  end
182
182
 
183
+ def synchronize_processes(projects = [], options = { dataproduct: 'default' })
184
+ projects = [projects] unless projects.is_a?(Array)
185
+ projects = projects.map do |p|
186
+ p = p.pid if p.respond_to?('pid')
187
+ fail('wrong type of argument. Should be either project ID or path') unless GoodData::Project.project_id_or_path?(p)
188
+ p.split('/').last
189
+ end
190
+
191
+ unless projects.empty?
192
+ body = {
193
+ syncConfig: {
194
+ filters: {
195
+ projectIdFilter: projects
196
+ }
197
+ }
198
+ }
199
+ end
200
+
201
+ uri = '/gdc/internal/lcm/domains/%s/dataproducts/%s/segments/%s/syncProcesses' % [domain.obj_id, options[:dataproduct], id]
202
+ res = client.post(uri, body)
203
+
204
+ client.poll_on_response(GoodData::Helpers.get_path(res, %w(asyncTask link poll)), sleep_interval: 1) do |r|
205
+ r['syncedResult'].nil?
206
+ end
207
+ end
208
+
183
209
  # Deletes a segment instance on the API.
184
210
  #
185
211
  # @return [GoodData::Segment] Segment instance
@@ -28,7 +28,11 @@ module GoodData
28
28
 
29
29
  def create(colors, opts = { client: GoodData.connection, project: GoodData.project })
30
30
  client, project = GoodData.get_client_and_project(opts)
31
- colors &= colors # remove duplicate colors
31
+ if colors.is_a?(StyleSetting)
32
+ colors = colors.colors
33
+ else
34
+ colors = colors.uniq
35
+ end
32
36
  uri = STYLE_SETTING_PATH % project.pid
33
37
  data_to_send = GoodData::Helpers.deep_dup(EMPTY_OBJECT).tap do |d|
34
38
  d['styleSettings']['chartPalette'] = colors
@@ -4,6 +4,8 @@
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
 
7
+ require_relative '../project_log_formatter'
8
+
7
9
  module GoodData
8
10
  module UserFilterBuilder
9
11
  # Main Entry function. Gets values and processes them to get filters
@@ -365,6 +367,7 @@ module GoodData
365
367
  ignore_missing_values = options[:ignore_missing_values]
366
368
  users_must_exist = options[:users_must_exist] == false ? false : true
367
369
  dry_run = options[:dry_run]
370
+ project_log_formatter = GoodData::ProjectLogFormatter.new(project)
368
371
 
369
372
  filters = normalize_filters(user_filters)
370
373
  user_filters, errors = maqlify_filters(filters, options.merge(users_must_exist: users_must_exist, type: :muf))
@@ -396,6 +399,9 @@ module GoodData
396
399
  res['userFiltersUpdateResult'].flat_map { |k, v| v.map { |r| { status: k.to_sym, user: r, type: :create } } }.map { |result| result[:status] == :failed ? result.merge(GoodData::Helpers.symbolize_keys(result[:user])) : result }
397
400
  end
398
401
  end
402
+
403
+ project_log_formatter.log_user_filter_results(create_results, to_create)
404
+
399
405
  delete_results = unless options[:do_not_touch_filters_that_are_not_mentioned]
400
406
  to_delete.each_slice(100).flat_map do |batch|
401
407
  batch.flat_map do |related_uri, group|
@@ -423,6 +429,9 @@ module GoodData
423
429
  end
424
430
  end
425
431
  end
432
+
433
+ project_log_formatter.log_user_filter_results(delete_results, to_delete)
434
+
426
435
  { created: to_create, deleted: to_delete, results: create_results + (delete_results || []) }
427
436
  end
428
437
 
@@ -505,7 +505,7 @@ module GoodData
505
505
  end
506
506
 
507
507
  def format_error(e, params = {})
508
- return unless e.respond_to?(:response)
508
+ return e unless e.respond_to?(:response)
509
509
  error = MultiJson.load(e.response)
510
510
  message = GoodData::Helpers.interpolate_error_message(error)
511
511
  <<-ERR
@@ -514,7 +514,10 @@ module GoodData
514
514
  Request ID: #{params[:x_gdc_request]}
515
515
  Full response:
516
516
  #{JSON.pretty_generate(error)}
517
+ Backtrace:\n#{e.backtrace.join("\n")}
517
518
  ERR
519
+ rescue MultiJson::ParseError
520
+ "Failed to parse #{e}. Raw response: #{e.response}"
518
521
  end
519
522
 
520
523
  # generate session id to be passed as the first part to
@@ -6,7 +6,7 @@
6
6
 
7
7
  # GoodData Module
8
8
  module GoodData
9
- VERSION = '0.6.50'
9
+ VERSION = '0.6.51'
10
10
 
11
11
  class << self
12
12
  # Version
@@ -0,0 +1,29 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright (c) 2010-2017 GoodData Corporation. All rights reserved.
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ module GoodData
8
+ module Environment
9
+ module ConnectionHelper
10
+ set_const :GD_PROJECT_TOKEN, GoodData::Helpers.decrypt("cfO9ifFYQVJw3q6Kf8/pVf/uLPLGnUJ/9nfvBxeGf/ILoj8N4ymWGgvryWEK\nHDMu\n", ENV['GD_SPEC_PASSWORD'] || ENV['BIA_ENCRYPTION_KEY'])
11
+ set_const :DEFAULT_DOMAIN, 'staging3-lcm-prod'
12
+ set_const :DEFAULT_SERVER, 'https://staging3-lcm-prod.intgdc.com'
13
+ set_const :DEFAULT_USER_URL, '/gdc/account/profile/a4c644d7b42b65c34e5a0f46809f7164'
14
+ set_const :STAGING_URI, 'https://staging3-lcm-prod.intgdc.com/gdc/uploads/'
15
+ end
16
+
17
+ module ProcessHelper
18
+ set_const :PROCESS_ID, 'b671fcfe-f6fd-4379-92c1-3db9eceb1c54'
19
+ end
20
+
21
+ module ProjectHelper
22
+ set_const :PROJECT_ID, 'rd87oh5rnbf1qhh9vjq5lkgq5v6okei5'
23
+ end
24
+
25
+ module ScheduleHelper
26
+ set_const :SCHEDULE_ID, '58ad6260e4b0ee87af79b0b8'
27
+ end
28
+ end
29
+ end
@@ -7,20 +7,32 @@
7
7
  module GoodData
8
8
  module Environment
9
9
  class << self
10
- def load(env = (ENV['GD_ENV'] && ENV['GD_ENV'].split('/')[1]) || 'develop')
10
+ BRANCH_TO_ENVIRONMENT = {
11
+ develop: 'testing',
12
+ hotfix: 'production'
13
+ }
14
+
15
+ def load(env = ENV['GD_ENV'] || 'development')
11
16
  require_relative 'default'
17
+ env = branch_to_environment(env)
12
18
 
13
19
  puts "USING ENVIRONMENT: #{env}"
14
20
  begin
15
21
  require_relative env
16
22
  rescue
17
23
  puts "Unable to find environment '#{env}'"
18
- require_relative 'develop'
24
+ require_relative 'development'
19
25
  end
20
26
 
21
27
  GoodData::Environment::ProjectHelper.set_const :PROJECT_URL, "/gdc/projects/#{GoodData::Environment::ProjectHelper::PROJECT_ID}"
22
28
  ENV['GD_SERVER'] = GoodData::Environment::ConnectionHelper::DEFAULT_SERVER
23
29
  end
30
+
31
+ # Translates ci-infra branch to environment config name
32
+ # @param branch Name of the branch we are testing against
33
+ def branch_to_environment(branch)
34
+ BRANCH_TO_ENVIRONMENT[branch.to_sym] || branch
35
+ end
24
36
  end
25
37
  end
26
38
  end
File without changes
@@ -40,7 +40,7 @@ describe "Swapping a date dimension and exchanging all attributes/elements", :co
40
40
  @client.disconnect
41
41
  end
42
42
 
43
- it "should swap the dimension, exhcange all stuff and not break anything" do
43
+ it "should swap the dimension, exchange all stuffs and not break anything" do
44
44
  # WE have 2 date dims
45
45
  expect(@blueprint.date_dimensions.map(&:id)).to eq %w(created_on created_on_2)
46
46
  # One is connected
@@ -120,14 +120,12 @@ describe "Swapping a date dimension and exchanging all attributes/elements", :co
120
120
  # Labels
121
121
  GoodData::SmallGoodZilla.get_uris(@metric_2.expression)
122
122
  expect(@report.definition.attributes.map(&:identifier)).to eq ["created_on_2.quarter"]
123
- ids = GoodData::SmallGoodZilla.get_uris(@report.definition.filters.first).map { |x| x.split('/')[-2..-1].join('/') }
124
- expect(ids).to eq ["obj/2286", "2286/elements?id=2015", "2286/elements?id=2016"]
125
123
 
126
124
  ids = GoodData::SmallGoodZilla.get_uris(@report.definition.filters.first).map { |x| x.split('/')[-2..-1].join('/') }
127
- expect(ids).to eq ["obj/2286", "2286/elements?id=2015", "2286/elements?id=2016"]
125
+ expect(ids).to eq ["obj/281", "281/elements?id=2015", "281/elements?id=2016"]
128
126
 
129
127
  ids = GoodData::SmallGoodZilla.get_uris(@variable.values.first.expression).map { |v| v.split('/')[-2..-1].join('/') }
130
- expect(ids).to eq ["obj/2286", "2286/elements?id=2015", "2286/elements?id=2016"]
128
+ expect(ids).to eq ["obj/281", "281/elements?id=2015", "281/elements?id=2016"]
131
129
 
132
130
  # Swap the dims
133
131
  bp = @project.blueprint