gooddata 0.6.50 → 0.6.51

Sign up to get free protection for your applications and to get access to all the features.
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