gooddata 0.6.24 → 0.6.25

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 (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