gooddata 0.6.24 → 0.6.25

Sign up to get free protection for your applications and to get access to all the features.
Files changed (114) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +54 -0
  3. data/CHANGELOG.md +3 -0
  4. data/DEPENDENCIES.md +155 -154
  5. data/README.md +15 -6
  6. data/Rakefile +5 -3
  7. data/dependency_decisions.yml +2 -0
  8. data/gooddata.gemspec +2 -3
  9. data/lib/gooddata/cli/cli.rb +1 -3
  10. data/lib/gooddata/cli/commands/auth_cmd.rb +16 -7
  11. data/lib/gooddata/cli/commands/project_cmd.rb +16 -178
  12. data/lib/gooddata/cli/shared.rb +46 -44
  13. data/lib/gooddata/commands/auth.rb +4 -0
  14. data/lib/gooddata/commands/project.rb +7 -24
  15. data/lib/gooddata/exceptions/object_migration.rb +4 -0
  16. data/lib/gooddata/exceptions/segment_not_empty.rb +18 -0
  17. data/lib/gooddata/extensions/object.rb +12 -0
  18. data/lib/gooddata/goodzilla/goodzilla.rb +56 -9
  19. data/lib/gooddata/helpers/global_helpers.rb +92 -0
  20. data/lib/gooddata/mixins/md_finders.rb +2 -8
  21. data/lib/gooddata/mixins/md_grantees.rb +42 -0
  22. data/lib/gooddata/mixins/md_id_to_uri.rb +1 -8
  23. data/lib/gooddata/mixins/md_object_id.rb +1 -1
  24. data/lib/gooddata/mixins/md_object_indexer.rb +5 -8
  25. data/lib/gooddata/mixins/md_object_query.rb +2 -2
  26. data/lib/gooddata/mixins/not_group.rb +17 -0
  27. data/lib/gooddata/mixins/rest_getters.rb +2 -2
  28. data/lib/gooddata/mixins/rest_resource.rb +1 -0
  29. data/lib/gooddata/mixins/to_json.rb +11 -0
  30. data/lib/gooddata/mixins/uri_getter.rb +9 -0
  31. data/lib/gooddata/models/blueprint/anchor_field.rb +14 -0
  32. data/lib/gooddata/models/blueprint/project_blueprint.rb +15 -1
  33. data/lib/gooddata/models/blueprint/to_wire.rb +10 -0
  34. data/lib/gooddata/models/client.rb +178 -0
  35. data/lib/gooddata/models/client_synchronization_result.rb +31 -0
  36. data/lib/gooddata/models/client_synchronization_result_details.rb +41 -0
  37. data/lib/gooddata/models/datawarehouse.rb +1 -5
  38. data/lib/gooddata/models/domain.rb +85 -1
  39. data/lib/gooddata/models/execution.rb +0 -2
  40. data/lib/gooddata/models/execution_detail.rb +0 -2
  41. data/lib/gooddata/models/from_wire.rb +10 -0
  42. data/lib/gooddata/models/invitation.rb +1 -1
  43. data/lib/gooddata/models/links.rb +1 -1
  44. data/lib/gooddata/models/membership.rb +10 -6
  45. data/lib/gooddata/models/metadata.rb +98 -11
  46. data/lib/gooddata/models/metadata/attribute.rb +6 -7
  47. data/lib/gooddata/models/metadata/dashboard.rb +41 -75
  48. data/lib/gooddata/models/metadata/dashboard/dashboard_item.rb +20 -4
  49. data/lib/gooddata/models/metadata/dashboard/filter_apply_item.rb +37 -0
  50. data/lib/gooddata/models/metadata/dashboard/filter_item.rb +49 -0
  51. data/lib/gooddata/models/metadata/dashboard/geo_chart_item.rb +56 -0
  52. data/lib/gooddata/models/metadata/dashboard/headline_item.rb +56 -0
  53. data/lib/gooddata/models/metadata/dashboard/iframe_item.rb +46 -0
  54. data/lib/gooddata/models/metadata/dashboard/report_item.rb +49 -8
  55. data/lib/gooddata/models/metadata/dashboard/text_item.rb +55 -0
  56. data/lib/gooddata/models/metadata/dashboard_tab.rb +83 -30
  57. data/lib/gooddata/models/metadata/dataset.rb +0 -2
  58. data/lib/gooddata/models/metadata/dimension.rb +1 -3
  59. data/lib/gooddata/models/metadata/fact.rb +1 -3
  60. data/lib/gooddata/models/metadata/label.rb +1 -3
  61. data/lib/gooddata/models/metadata/metric.rb +11 -42
  62. data/lib/gooddata/models/metadata/report.rb +7 -18
  63. data/lib/gooddata/models/metadata/report_definition.rb +21 -113
  64. data/lib/gooddata/models/metadata/scheduled_mail.rb +274 -0
  65. data/lib/gooddata/models/metadata/scheduled_mail/dashboard_attachment.rb +62 -0
  66. data/lib/gooddata/models/metadata/scheduled_mail/report_attachment.rb +64 -0
  67. data/lib/gooddata/models/metadata/variable.rb +8 -2
  68. data/lib/gooddata/models/model.rb +2 -9
  69. data/lib/gooddata/models/process.rb +7 -29
  70. data/lib/gooddata/models/profile.rb +1 -1
  71. data/lib/gooddata/models/project.rb +131 -167
  72. data/lib/gooddata/models/project_creator.rb +2 -7
  73. data/lib/gooddata/models/project_metadata.rb +2 -10
  74. data/lib/gooddata/models/project_role.rb +4 -10
  75. data/lib/gooddata/models/report_data_result.rb +3 -5
  76. data/lib/gooddata/models/schedule.rb +4 -31
  77. data/lib/gooddata/models/segment.rb +192 -0
  78. data/lib/gooddata/models/user_filters/mandatory_user_filter.rb +2 -2
  79. data/lib/gooddata/models/user_filters/user_filter_builder.rb +1 -1
  80. data/lib/gooddata/models/user_filters/variable_user_filter.rb +11 -0
  81. data/lib/gooddata/models/user_group.rb +241 -0
  82. data/lib/gooddata/rest/connection.rb +81 -16
  83. data/lib/gooddata/rest/object.rb +29 -0
  84. data/lib/gooddata/rest/object_factory.rb +6 -1
  85. data/lib/gooddata/rest/resource.rb +7 -1
  86. data/lib/gooddata/version.rb +1 -1
  87. data/spec/environment/default.rb +19 -16
  88. data/spec/environment/develop.rb +10 -10
  89. data/spec/environment/hotfix.rb +6 -6
  90. data/spec/environment/production.rb +14 -14
  91. data/spec/environment/release.rb +6 -6
  92. data/spec/environment/staging.rb +9 -9
  93. data/spec/environment/staging_3.rb +14 -15
  94. data/spec/integration/blueprint_with_grain_spec.rb +72 -0
  95. data/spec/integration/clients_spec.rb +135 -0
  96. data/spec/integration/date_dim_switch_spec.rb +142 -0
  97. data/spec/integration/full_project_spec.rb +3 -3
  98. data/spec/integration/project_spec.rb +20 -0
  99. data/spec/integration/segments_spec.rb +141 -0
  100. data/spec/integration/user_group_spec.rb +127 -0
  101. data/spec/spec_helper.rb +4 -0
  102. data/spec/unit/models/domain_spec.rb +7 -1
  103. data/spec/unit/models/metric_spec.rb +0 -8
  104. data/spec/unit/models/profile_spec.rb +1 -1
  105. data/spec/unit/models/report_result_data_spec.rb +6 -0
  106. metadata +38 -38
  107. data/lib/gooddata/cli/commands/api_cmd.rb +0 -34
  108. data/lib/gooddata/cli/commands/console_cmd.rb +0 -40
  109. data/lib/gooddata/cli/commands/domain_cmd.rb +0 -46
  110. data/lib/gooddata/cli/commands/process_cmd.rb +0 -145
  111. data/lib/gooddata/cli/commands/projects_cmd.rb +0 -23
  112. data/lib/gooddata/cli/commands/run_ruby_cmd.rb +0 -77
  113. data/lib/gooddata/cli/commands/scaffold_cmd.rb +0 -35
  114. data/lib/gooddata/cli/commands/user_cmd.rb +0 -24
@@ -0,0 +1,62 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright (c) 2010-2015 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
+ class DashboardAttachment
9
+ include GoodData::Mixin::RootKeyGetter
10
+ include GoodData::Mixin::DataGetter
11
+
12
+ attr_reader :scheduled_email
13
+ attr_accessor :json
14
+
15
+ DEFAULT_OPTS = {
16
+ :allTabs => 1,
17
+ :tabs => []
18
+ }
19
+
20
+ def initialize(scheduled_email, json)
21
+ @scheduled_email = scheduled_email
22
+ @json = json
23
+ end
24
+
25
+ # Get all tabs flag
26
+ #
27
+ # @return [Fixnum] All dashboard tabs?
28
+ def all_tabs
29
+ data['allTabs']
30
+ end
31
+
32
+ # Set all tabs flag
33
+ #
34
+ # @param [String | Fixnum] new_all_tabs New value of all_tabs flag to be set
35
+ # @return [Fixnum] New value of all_tabs flag
36
+ def all_tabs=(new_all_tabs)
37
+ data['allTabs'] = new_all_tabs.to_i
38
+ end
39
+
40
+ # Get selected tabs
41
+ #
42
+ # @return [Array<String>] List of selected tabs
43
+ def tabs
44
+ data['tabs']
45
+ end
46
+
47
+ # Set selected tabs
48
+ #
49
+ # @param [Array<String>] new_tabs New list of selected tabs to be set
50
+ # @return [Array<String>] New list of selected tabs
51
+ def tabs=(new_tabs)
52
+ data['tabs'] = new_tabs
53
+ end
54
+
55
+ # Get attachment URI
56
+ #
57
+ # @return [String] Attachment URI
58
+ def uri
59
+ data['uri']
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,64 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright (c) 2010-2015 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
+ class ReportAttachment
9
+ include GoodData::Mixin::RootKeyGetter
10
+ include GoodData::Mixin::DataGetter
11
+
12
+ attr_reader :scheduled_email
13
+ attr_accessor :json
14
+
15
+ DEFAULT_OPTS = {
16
+ :formats => %w(pdf xls),
17
+ :exportOptions => {
18
+ :pageOrientation => 'landscape'
19
+ }
20
+ }
21
+
22
+ def initialize(scheduled_email, json)
23
+ @scheduled_email = scheduled_email
24
+ @json = json
25
+ end
26
+
27
+ # Get export options settings
28
+ #
29
+ # @return [Hash] Export options settings
30
+ def export_options
31
+ data['exportOptions']
32
+ end
33
+
34
+ # Set export options settings
35
+ #
36
+ # @param [Hash] new_export_options New export options settings to be set
37
+ # @return [Hash] New export options settings
38
+ def export_options=(new_export_options)
39
+ data['exportOptions'] = new_export_options
40
+ end
41
+
42
+ # Get formats
43
+ #
44
+ # @return [Array<String>] List of selected formats
45
+ def formats
46
+ data['formats']
47
+ end
48
+
49
+ # Set formats
50
+ #
51
+ # @param [String | Array<String>] new_formats New list of selected formats to be set
52
+ # @return [Array<String>] New list of selected formats
53
+ def formats=(new_formats)
54
+ data['formats'] = new_formats.is_a?(Array) ? new_formats : [new_formats]
55
+ end
56
+
57
+ # Get attachment URI
58
+ #
59
+ # @return [String] Attachment URI
60
+ def uri
61
+ data['uri']
62
+ end
63
+ end
64
+ end
@@ -10,8 +10,6 @@ require_relative 'metadata'
10
10
 
11
11
  module GoodData
12
12
  class Variable < MdObject
13
- root_key :prompt
14
-
15
13
  class << self
16
14
  # Method intended to get all objects of that type in a specified project
17
15
  #
@@ -69,6 +67,14 @@ module GoodData
69
67
  values.select { |x| x.level == :user }
70
68
  end
71
69
 
70
+ # Method used for replacing values in their state according to mapping. Can be used to replace any values but it is typically used to replace the URIs. Returns a new object of the same type.
71
+ #
72
+ # @param [Array<Array>]Mapping specifying what should be exchanged for what. As mapping should be used output of GoodData::Helpers.prepare_mapping.
73
+ # @return [GoodData::Variable]
74
+ def replace(mapping)
75
+ GoodData::MdObject.replace_quoted(self, mapping)
76
+ end
77
+
72
78
  # Retrieves variable values and returns only those related to project
73
79
  #
74
80
  # @return [Array<GoodData::VariableUserFilter>] Values of variable related to project
@@ -151,16 +151,9 @@ module GoodData
151
151
  # @param options [Hash] Additional options
152
152
  # @return [Hash] Batch upload result
153
153
  def upload_multiple_data(data, project_blueprint, options = { :client => GoodData.connection, :project => GoodData.project })
154
- client = options[:client]
155
- fail ArgumentError, 'No :client specified' if client.nil?
154
+ client, project = GoodData.get_client_and_project(options)
156
155
 
157
- p = options[:project]
158
- fail ArgumentError, 'No :project specified' if p.nil?
159
-
160
- project = GoodData::Project[p, options]
161
- fail ArgumentError, 'Wrong :project specified' if project.nil?
162
-
163
- project = options[:project] || GoodData.project
156
+ project ||= GoodData.project
164
157
 
165
158
  manifest = {
166
159
 
@@ -14,7 +14,7 @@ require_relative 'execution_detail'
14
14
  require_relative 'schedule'
15
15
 
16
16
  module GoodData
17
- class Process < GoodData::Rest::Object
17
+ class Process < Rest::Resource
18
18
  attr_reader :data
19
19
 
20
20
  alias_method :raw_data, :data
@@ -56,14 +56,7 @@ module GoodData
56
56
  end
57
57
 
58
58
  def with_deploy(dir, options = {}, &block)
59
- client = options[:client]
60
- fail ArgumentError, 'No :client specified' if client.nil?
61
-
62
- p = options[:project]
63
- fail ArgumentError, 'No :project specified' if p.nil?
64
-
65
- project = GoodData::Project[p, options]
66
- fail ArgumentError, 'Wrong :project specified' if project.nil?
59
+ _client, project = GoodData.get_client_and_project(options)
67
60
 
68
61
  GoodData.with_project(project) do
69
62
  params = options[:params].nil? ? [] : [options[:params]]
@@ -83,15 +76,7 @@ module GoodData
83
76
  end
84
77
 
85
78
  def upload_package(path, files_to_exclude, opts = { :client => GoodData.connection })
86
- client = opts[:client]
87
- fail ArgumentError, 'No :client specified' if client.nil?
88
-
89
- p = opts[:project]
90
- fail ArgumentError, 'No :project specified' if p.nil?
91
-
92
- project = GoodData::Project[p, opts]
93
- fail ArgumentError, 'Wrong :project specified' if project.nil?
94
-
79
+ GoodData.get_client_and_project(opts)
95
80
  zip_and_upload(path, files_to_exclude, opts)
96
81
  end
97
82
 
@@ -104,14 +89,7 @@ module GoodData
104
89
  # @option options [String] :process_id ID of a process to be redeployed (do not set if you want to create a new process)
105
90
  # @option options [Boolean] :verbose (false) Switch on verbose mode for detailed logging
106
91
  def deploy(path, options = { :client => GoodData.client, :project => GoodData.project })
107
- client = options[:client]
108
- fail ArgumentError, 'No :client specified' if client.nil?
109
-
110
- p = options[:project]
111
- fail ArgumentError, 'No :project specified' if p.nil?
112
-
113
- project = GoodData::Project[p, options]
114
- fail ArgumentError, 'No :project specified' if project.nil?
92
+ client, project = GoodData.get_client_and_project(options)
115
93
 
116
94
  path = Pathname(path) || fail('Path is not specified')
117
95
  files_to_exclude = options[:files_to_exclude].nil? ? [] : options[:files_to_exclude].map { |pname| Pathname(pname) }
@@ -139,7 +117,7 @@ module GoodData
139
117
  client.put("/gdc/projects/#{project.pid}/dataload/processes/#{process_id}", data)
140
118
  end
141
119
 
142
- process = client.create(Process, res, project: p)
120
+ process = client.create(Process, res, project: project)
143
121
  puts HighLine.color("Deploy DONE #{path}", HighLine::GREEN) if verbose
144
122
  process
145
123
  end
@@ -284,8 +262,8 @@ module GoodData
284
262
  client.post(executions_link,
285
263
  :execution => {
286
264
  :graph => executable.to_s,
287
- :params => GoodData::Helpers.encode_params(params, false),
288
- :hiddenParams => GoodData::Helpers.encode_params(hidden_params, true)
265
+ :params => GoodData::Helpers.encode_public_params(params),
266
+ :hiddenParams => GoodData::Helpers.encode_hidden_params(hidden_params)
289
267
  })
290
268
  end
291
269
  end
@@ -11,7 +11,7 @@ require_relative '../rest/object'
11
11
  require_relative 'project'
12
12
 
13
13
  module GoodData
14
- class Profile < GoodData::Rest::Object
14
+ class Profile < Rest::Resource
15
15
  attr_reader :user, :json
16
16
 
17
17
  EMPTY_OBJECT = {
@@ -19,13 +19,18 @@ require_relative '../rest/resource'
19
19
  require_relative '../mixins/author'
20
20
  require_relative '../mixins/contributor'
21
21
  require_relative '../mixins/rest_resource'
22
+ require_relative '../mixins/uri_getter'
22
23
 
23
24
  require_relative 'process'
24
25
  require_relative 'project_role'
25
26
  require_relative 'blueprint/blueprint'
26
27
 
28
+ require_relative 'metadata/scheduled_mail'
29
+ require_relative 'metadata/scheduled_mail/dashboard_attachment'
30
+ require_relative 'metadata/scheduled_mail/report_attachment'
31
+
27
32
  module GoodData
28
- class Project < GoodData::Rest::Resource
33
+ class Project < Rest::Resource
29
34
  USERSPROJECTS_PATH = '/gdc/account/profile/%s/projects'
30
35
  PROJECTS_PATH = '/gdc/projects'
31
36
  PROJECT_PATH = '/gdc/projects/%s'
@@ -48,15 +53,9 @@ module GoodData
48
53
 
49
54
  attr_accessor :connection, :json
50
55
 
51
- alias_method :to_json, :json
52
- alias_method :raw_data, :json
53
-
54
- include GoodData::Mixin::RestResource
55
-
56
- Project.root_key :project
57
-
58
- include GoodData::Mixin::Author
59
- include GoodData::Mixin::Contributor
56
+ include Mixin::Author
57
+ include Mixin::Contributor
58
+ include Mixin::UriGetter
60
59
 
61
60
  class << self
62
61
  # Returns an array of all projects accessible by
@@ -129,7 +128,7 @@ module GoodData
129
128
  project.save
130
129
  # until it is enabled or deleted, recur. This should still end if there is a exception thrown out from RESTClient. This sometimes happens from WebApp when request is too long
131
130
  while project.state.to_s != 'enabled'
132
- if project.state.to_s == 'deleted'
131
+ if project.deleted?
133
132
  # if project is switched to deleted state, fail. This is usually problem of creating a template which is invalid.
134
133
  fail 'Project was marked as deleted during creation. This usually means you were trying to create from template and it failed.'
135
134
  end
@@ -181,6 +180,18 @@ module GoodData
181
180
 
182
181
  alias_method :create_dashboard, :add_dashboard
183
182
 
183
+ def add_user_group(data)
184
+ g = GoodData::UserGroup.create(data.merge(project: self))
185
+
186
+ begin
187
+ g.save
188
+ rescue RestClient::Conflict
189
+ user_groups(data[:name])
190
+ end
191
+ end
192
+
193
+ alias_method :create_group, :add_user_group
194
+
184
195
  # Creates a metric in a project
185
196
  #
186
197
  # @param [options] Optional report options
@@ -259,7 +270,7 @@ module GoodData
259
270
  #
260
271
  # @return [GoodData::ProjectRole] Project role if found
261
272
  def blueprint(options = {})
262
- result = client.get("/gdc/projects/#{pid}/model/view", params: { includeDeprecated: true })
273
+ result = client.get("/gdc/projects/#{pid}/model/view", params: { includeDeprecated: true, includeGrain: true })
263
274
  polling_url = result['asyncTask']['link']['poll']
264
275
  model = client.poll_on_code(polling_url, options)
265
276
  bp = GoodData::Model::FromWire.from_wire(model)
@@ -293,7 +304,7 @@ module GoodData
293
304
  begin
294
305
  # Create the project first so we know that it is passing.
295
306
  # What most likely is wrong is the token and the export actaully takes majority of the time
296
- new_project = GoodData::Project.create(options.merge(:title => a_title, :client => client))
307
+ new_project = GoodData::Project.create(options.merge(:title => a_title, :client => client, :driver => content[:driver]))
297
308
  export_token = export_clone(options)
298
309
  new_project.import_clone(export_token)
299
310
  rescue
@@ -344,6 +355,10 @@ module GoodData
344
355
  result['exportArtifact']['token']
345
356
  end
346
357
 
358
+ def user_groups(id = :all, options = {})
359
+ GoodData::UserGroup[id, options.merge(project: self)]
360
+ end
361
+
347
362
  # Imports a clone into current project. The project has to be freshly
348
363
  # created.
349
364
  #
@@ -397,10 +412,17 @@ module GoodData
397
412
 
398
413
  # Deletes project
399
414
  def delete
400
- fail "Project '#{title}' with id #{uri} is already deleted" if state == :deleted
415
+ fail "Project '#{title}' with id #{uri} is already deleted" if deleted?
401
416
  client.delete(uri)
402
417
  end
403
418
 
419
+ # Returns true if project is in deleted state
420
+ #
421
+ # @return [Boolean] Returns true if object deleted. False otherwise.
422
+ def deleted?
423
+ state == :deleted
424
+ end
425
+
404
426
  # Helper for getting rid of all data in the project
405
427
  #
406
428
  # @option options [Boolean] :force has to be added otherwise the operation is not performed
@@ -894,171 +916,68 @@ module GoodData
894
916
  self
895
917
  end
896
918
 
897
- DEFAULT_REPLACE_DATE_DIMENSION_OPTIONS = {
898
- :old => nil,
899
- :new => nil,
900
- :purge => false,
901
- :dry_run => true,
902
- :mapping => {}
903
- }
904
-
905
- def replace_date_dimension(opts)
906
- fail ArgumentError, 'No :old dimension specified' if opts[:old].nil?
907
- fail ArgumentError, 'No :new dimension specified' if opts[:new].nil?
908
-
909
- # Merge with default options
910
- opts = DEFAULT_REPLACE_DATE_DIMENSION_OPTIONS.merge(opts)
911
-
912
- get_attribute = lambda do |attr|
913
- return attr if attr.is_a?(GoodData::Attribute)
914
-
915
- res = attribute_by_identifier(attr)
916
- return res if res
917
-
918
- attribute_by_title(attr)
919
- end
920
-
921
- if opts[:old] && opts[:new]
922
- fail ArgumentError, 'You specified both :old => :new and :mapping' if opts[:mapping] && !opts[:mapping].empty?
923
-
924
- attrs = attributes_by_title(/\(#{opts[:old]}\)$/)
925
-
926
- attrs.each do |old_attr|
927
- new_attr_title = old_attr.title.sub("(#{opts[:old]})", "(#{opts[:new]})")
928
- new_attr = attribute_by_title(new_attr_title)
929
-
930
- fail "Unable to find attribute '#{new_attr_title}' in date dimension '#{opts[:new]}'" if new_attr.nil?
931
-
932
- opts[:mapping][old_attr] = new_attr
933
- end
934
- end
935
-
936
- mufs = user_filters
937
-
938
- # Replaces string anywhere in JSON with another string and returns back new JSON
939
- json_replace = lambda do |object, old_uri, new_uri|
940
- old_json = JSON.generate(object.json)
941
- regexp_replace = Regexp.new(old_uri + '([^0-9])')
942
-
943
- new_json = old_json.gsub(regexp_replace, "#{new_uri}\\1")
944
- if old_json != new_json
945
- object.json = JSON.parse(new_json)
946
- object.save
947
- end
948
- object
949
- end
919
+ # Method used for walking through objects in project and trying to replace all occurences of some object for another object. This is typically used as a means for exchanging Date dimensions.
920
+ #
921
+ # @param mapping [Array<Array>] Mapping specifying what should be exchanged for what. As mapping should be used output of GoodData::Helpers.prepare_mapping.
922
+ def replace_from_mapping(mapping, opts = {})
923
+ default = {
924
+ :purge => false,
925
+ :dry_run => false
926
+ }
927
+ opts = default.merge(opts)
928
+ dry_run = opts[:dry_run]
950
929
 
951
- # delete old report definitions (only the last version of each report is kept)
952
930
  if opts[:purge]
953
931
  GoodData.logger.info 'Purging old project definitions'
954
932
  reports.peach(&:purge_report_of_unused_definitions!)
955
933
  end
956
934
 
957
- fail ArgumentError, 'No :mapping specified' if opts[:mapping].nil? || opts[:mapping].empty?
958
-
959
- # Preprocess mapping, do necessary lookup
960
- mapping = {}
961
- opts[:mapping].each do |k, v|
962
- attr_src = get_attribute.call(k)
963
- attr_dest = get_attribute.call(v)
964
-
965
- fail ArgumentError, "Unable to find attribute with identifier '#{k}'" if attr_src.nil?
966
- fail ArgumentError, "Unable to find attribute with identifier '#{v}'" if attr_dest.nil?
967
-
968
- mapping[attr_src] = attr_dest
969
- end
970
-
971
- # Iterate over all date attributes
972
- mapping.each do |old_date, new_date|
973
- GoodData.logger.info " replacing date attribute '#{old_date.title}' (#{old_date.uri}) with '#{new_date.title}' (#{new_date.uri})"
974
-
975
- # For each attribute prepare list of labels to replace
976
- labels_mapping = {}
977
-
978
- old_date.labels.each do |old_label|
979
- new_label_title = old_label.title.sub("(#{opts[:old]})", "(#{opts[:new]})")
980
-
981
- # Go through all labels, label_by_title has some issues
982
- new_date.json['attribute']['content']['displayForms'].each do |label_tmp|
983
- if label_tmp['meta']['title'] == new_label_title
984
- new_label = labels(label_tmp['meta']['uri'])
985
- labels_mapping[old_label] = new_label
935
+ fail ArgumentError, 'No mapping specified' if mapping.blank?
936
+ rds = report_definitions
937
+
938
+ {
939
+ # data_permissions: data_permissions,
940
+ variables: variables,
941
+ dashboards: dashboards,
942
+ metrics: metrics,
943
+ report_definitions: rds
944
+ }.each do |key, collection|
945
+ puts "Replacing #{key}"
946
+ collection.peach do |item|
947
+ new_item = item.replace(mapping)
948
+ if new_item.json != item.json
949
+ if dry_run
950
+ GoodData.logger.info "Would save #{new_item.uri}. Running in dry run mode"
951
+ else
952
+ GoodData.logger.info "Saving #{new_item.uri}"
953
+ new_item.save
986
954
  end
987
955
  end
988
956
  end
957
+ end
989
958
 
990
- # Now we should have all labels for this attribute and its replacement in new date dimension
991
- # First fix all affected metrics that are using this attribute
992
- dependent = old_date.usedby
993
- GoodData.logger.info 'Fixing metrics...'
994
- dependent.each do |dependent_object|
995
- next if dependent_object['category'] != 'metric'
996
-
997
- affected_metric = metrics(dependent_object['link'])
998
-
999
- GoodData.logger.info "Metric '#{dependent_object['title']}' (#{affected_metric.uri}) contains old date attribute '#{old_date.title}' ...replacing"
1000
- affected_metric.replace(old_date.uri, new_date.uri)
1001
- affected_metric.save unless opts[:dry_run]
1002
- end
1003
-
1004
- # Then search which reports are still using this attribute after replacement in metric...
1005
- dependent = old_date.usedby
1006
- GoodData.logger.info 'Fixing reports (standard)...'
1007
- dependent.each do |dependent_object|
1008
- # This does not seem to work every time... some references are kept...
1009
- next if dependent_object['category'] != 'reportDefinition'
1010
-
1011
- affected_rd = report_definitions(dependent_object['link'])
1012
-
1013
- GoodData.logger.info "reportDefinition (#{affected_rd.uri}) contains old date attribute '#{old_date.title}' ...replacing"
1014
- affected_rd.replace(old_date.uri, new_date.uri)
1015
-
1016
- # Affected_rd.replace(labels_mapping) #not sure if this is working correctly, try to do it one by one
1017
- labels_mapping.each_pair do |old_label, new_label|
1018
- affected_rd.replace(old_label.uri, new_label.uri)
1019
- end
1020
-
1021
- affected_rd.save unless opts[:dry_run]
1022
- end
1023
-
1024
- # Then search which dashboards and reports are still using this attribute after standard replacement in reports...
1025
- dependent = old_date.usedby
1026
- GoodData.logger.info 'Fixing reports (force) & dashboards...'
1027
-
1028
- # If standard replace did not work, use force...
1029
- dependent.each do |dependent_object|
1030
- case dependent_object['category']
1031
- when 'reportDefinition'
1032
- affected_rd = report_definitions(dependent_object['link'])
1033
-
1034
- GoodData.logger.info "reportDefinition '#{affected_rd.title}' (#{affected_rd.uri}) still contains old date attribute '#{old_date.title}' ...replacing by force"
1035
- json_replace.call(affected_rd, old_date.uri, new_date.uri)
1036
-
1037
- # Iterate over all labels
1038
- labels_mapping.each_pair do |old_label, new_label|
1039
- json_replace.call(affected_rd, old_label.uri, new_label.uri)
1040
- end
1041
-
1042
- affected_rd.save unless opts[:dry_run]
1043
- when 'projectDashboard'
1044
- affected_dashboard = dashboards(dependent_object['link'])
1045
-
1046
- GoodData.logger.info "Dashboard '#{affected_dashboard.title}' (#{affected_dashboard.uri}) contains old date attribute '#{old_date.title}' ...replacing by force"
1047
- json_replace.call(affected_dashboard, old_date.uri, new_date.uri)
1048
-
1049
- # Iterate over all labels
1050
- labels_mapping.each_pair do |old_label, new_label|
1051
- json_replace.call(affected_dashboard, old_label.uri, new_label.uri)
1052
- end
1053
-
1054
- affected_dashboard.save unless opts[:dry_run]
959
+ GoodData.logger.info 'Replacing hidden metrics'
960
+ local_metrics = rds.pmapcat { |rd| rd.using('metric') }.select { |m| m['deprecated'] == '1' }
961
+ puts "Found #{local_metrics.count} metrics"
962
+ local_metrics.pmap { |m| metrics(m['link']) }.peach do |item|
963
+ new_item = item.replace(mapping)
964
+ if new_item.json != item.json
965
+ if dry_run
966
+ GoodData.logger.info "Would save #{new_item.uri}. Running in dry run mode"
967
+ else
968
+ GoodData.logger.info "Saving #{new_item.uri}"
969
+ new_item.save
1055
970
  end
1056
971
  end
972
+ end
1057
973
 
1058
- mufs.each do |muf|
1059
- json_replace.call(muf, old_date.uri, new_date.uri)
974
+ GoodData.logger.info 'Replacing variable values'
975
+ variables.each do |var|
976
+ var.values.peach do |val|
977
+ val.replace(mapping).save unless dry_run
1060
978
  end
1061
979
  end
980
+ nil
1062
981
  end
1063
982
 
1064
983
  # Helper for getting reports of a project
@@ -1107,6 +1026,15 @@ module GoodData
1107
1026
  self
1108
1027
  end
1109
1028
 
1029
+ # Schedules an email with dashboard or report content
1030
+ def schedule_mail(options = GoodData::ScheduledMail::DEFAULT_OPTS)
1031
+ GoodData::ScheduledMail.create(options.merge(client: client, project: self))
1032
+ end
1033
+
1034
+ def scheduled_mails(options = { :full => false })
1035
+ GoodData::ScheduledMail[:all, options.merge(project: self, client: client)]
1036
+ end
1037
+
1110
1038
  # @param [String | Number | Object] Anything that you can pass to GoodData::Schedule[id]
1111
1039
  # @return [GoodData::Schedule | Array<GoodData::Schedule>] schedule instance or list
1112
1040
  def schedules(id = :all)
@@ -1190,7 +1118,7 @@ module GoodData
1190
1118
 
1191
1119
  alias_method :members, :users
1192
1120
 
1193
- def whitelist_users(new_users, users_list, whitelist)
1121
+ def whitelist_users(new_users, users_list, whitelist, mode = :exclude)
1194
1122
  return [new_users, users_list] unless whitelist
1195
1123
 
1196
1124
  new_whitelist_proc = proc do |user|
@@ -1201,7 +1129,11 @@ module GoodData
1201
1129
  whitelist.any? { |wl| wl.is_a?(Regexp) ? user.login =~ wl : user.login.include?(wl) }
1202
1130
  end
1203
1131
 
1204
- [new_users.reject(&new_whitelist_proc), users_list.reject(&whitelist_proc)]
1132
+ if mode == :include
1133
+ [new_users.select(&new_whitelist_proc), users_list.select(&whitelist_proc)]
1134
+ elsif mode == :exclude
1135
+ [new_users.reject(&new_whitelist_proc), users_list.reject(&whitelist_proc)]
1136
+ end
1205
1137
  end
1206
1138
 
1207
1139
  # Imports users
@@ -1214,6 +1146,9 @@ module GoodData
1214
1146
 
1215
1147
  whitelisted_new_users, whitelisted_users = whitelist_users(new_users.map(&:to_hash), users_list, options[:whitelists])
1216
1148
 
1149
+ # First check that if groups are provided we have them set up
1150
+ check_groups(new_users.map(&:to_hash).flat_map { |u| u[:user_group] || [] }.uniq)
1151
+
1217
1152
  # conform the role on list of new users so we can diff them with the users coming from the project
1218
1153
  diffable_new_with_default_role = whitelisted_new_users.map do |u|
1219
1154
  u[:role] = Array(u[:role] || u[:roles] || 'readOnlyUser')
@@ -1259,6 +1194,30 @@ module GoodData
1259
1194
  to_remove = diff[:removed].reject { |user| user[:status] == 'DISABLED' || user[:status] == :disabled }
1260
1195
  GoodData.logger.warn("Removing #{to_remove.count} users from project (#{pid})")
1261
1196
  results.concat(disable_users(to_remove))
1197
+
1198
+ # reassign to groups
1199
+ mappings = new_users.map(&:to_hash).flat_map do |user|
1200
+ groups = user[:user_group] || []
1201
+ groups.map { |g| [user[:login], g] }
1202
+ end
1203
+ unless mappings.empty?
1204
+ users_lookup = users.reduce({}) do |a, e|
1205
+ a[e.login] = e
1206
+ a
1207
+ end
1208
+ mappings.group_by { |_, g| g }.each do |g, mapping|
1209
+ # find group + set users
1210
+ # CARE YOU DO NOT KNOW URI
1211
+ user_groups(g).set_members(mapping.map { |user, _| user }.map { |login| users_lookup[login] && users_lookup[login].uri })
1212
+ end
1213
+ mentioned_groups = mappings.map(&:last).uniq
1214
+ groups_to_cleanup = user_groups.reject { |g| mentioned_groups.include?(g.name) }
1215
+ # clean all groups not mentioned with exception of whitelisted users
1216
+ groups_to_cleanup.each do |g|
1217
+ g.set_members(whitelist_users(g.members.map(&:to_hash), [], options[:whitelists], :include).first.map { |x| x[:uri] })
1218
+ end
1219
+ end
1220
+ results
1262
1221
  end
1263
1222
 
1264
1223
  def disable_users(list)
@@ -1274,6 +1233,12 @@ module GoodData
1274
1233
  end
1275
1234
  end
1276
1235
 
1236
+ def check_groups(specified_groups)
1237
+ groups = user_groups.map(&:name)
1238
+ missing_groups = specified_groups - groups
1239
+ fail "All groups have to be specified before you try to import users. Groups that are currently in project are #{groups.join(',')} and you asked for #{missing_groups.join(',')}" unless missing_groups.empty?
1240
+ end
1241
+
1277
1242
  # Update user
1278
1243
  #
1279
1244
  # @param user User to be updated
@@ -1288,7 +1253,6 @@ module GoodData
1288
1253
  fail ArgumentError, "User #{user_uri} could not be aded. #{failure.first['message']}" unless failure.blank?
1289
1254
  res
1290
1255
  end
1291
-
1292
1256
  alias_method :add_user, :set_user_roles
1293
1257
 
1294
1258
  # Update list of users