gooddata 0.6.11 → 0.6.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +6 -0
  3. data/.travis.yml +5 -0
  4. data/CHANGELOG.md +34 -1
  5. data/CLI.md +1 -1
  6. data/authors.sh +4 -0
  7. data/lib/gooddata.rb +1 -1
  8. data/lib/gooddata/cli/commands/api_cmd.rb +0 -2
  9. data/lib/gooddata/cli/commands/auth_cmd.rb +0 -3
  10. data/lib/gooddata/cli/commands/console_cmd.rb +1 -2
  11. data/lib/gooddata/cli/commands/domain_cmd.rb +0 -2
  12. data/lib/gooddata/cli/commands/process_cmd.rb +0 -2
  13. data/lib/gooddata/cli/commands/project_cmd.rb +0 -2
  14. data/lib/gooddata/cli/commands/projects_cmd.rb +0 -2
  15. data/lib/gooddata/cli/commands/run_ruby_cmd.rb +2 -3
  16. data/lib/gooddata/cli/commands/scaffold_cmd.rb +0 -3
  17. data/lib/gooddata/cli/commands/user_cmd.rb +0 -2
  18. data/lib/gooddata/cli/shared.rb +1 -2
  19. data/lib/gooddata/commands/datawarehouse.rb +24 -0
  20. data/lib/gooddata/commands/process.rb +0 -1
  21. data/lib/gooddata/commands/project.rb +1 -1
  22. data/lib/gooddata/commands/scaffold.rb +0 -1
  23. data/lib/gooddata/core/connection.rb +376 -0
  24. data/lib/gooddata/core/logging.rb +13 -0
  25. data/lib/gooddata/core/rest.rb +40 -16
  26. data/lib/gooddata/exceptions/user_in_different_domain.rb +11 -0
  27. data/lib/gooddata/extensions/enumerable.rb +8 -0
  28. data/lib/gooddata/goodzilla/goodzilla.rb +24 -0
  29. data/lib/gooddata/helpers/global_helpers.rb +126 -12
  30. data/lib/gooddata/mixins/author.rb +11 -5
  31. data/lib/gooddata/mixins/is_dimension.rb +13 -0
  32. data/lib/gooddata/mixins/md_object_indexer.rb +17 -1
  33. data/lib/gooddata/mixins/md_object_query.rb +10 -2
  34. data/lib/gooddata/mixins/md_relations.rb +2 -2
  35. data/lib/gooddata/mixins/rest_resource.rb +1 -0
  36. data/lib/gooddata/models/data_result.rb +0 -1
  37. data/lib/gooddata/models/datawarehouse.rb +90 -0
  38. data/lib/gooddata/models/domain.rb +202 -76
  39. data/lib/gooddata/models/execution.rb +11 -0
  40. data/lib/gooddata/models/from_wire.rb +4 -4
  41. data/lib/gooddata/models/invitation.rb +0 -5
  42. data/lib/gooddata/models/membership.rb +121 -91
  43. data/lib/gooddata/models/metadata.rb +1 -2
  44. data/lib/gooddata/models/metadata/attribute.rb +7 -0
  45. data/lib/gooddata/models/metadata/dashboard.rb +1 -1
  46. data/lib/gooddata/models/metadata/dimension.rb +52 -0
  47. data/lib/gooddata/models/metadata/fact.rb +1 -1
  48. data/lib/gooddata/models/metadata/label.rb +21 -7
  49. data/lib/gooddata/models/metadata/metric.rb +1 -23
  50. data/lib/gooddata/models/metadata/report.rb +2 -2
  51. data/lib/gooddata/models/metadata/report_definition.rb +22 -2
  52. data/lib/gooddata/models/metadata/variable.rb +81 -0
  53. data/lib/gooddata/models/model.rb +2 -1
  54. data/lib/gooddata/models/process.rb +3 -4
  55. data/lib/gooddata/models/profile.rb +50 -82
  56. data/lib/gooddata/models/project.rb +170 -213
  57. data/lib/gooddata/models/project_blueprint.rb +14 -5
  58. data/lib/gooddata/models/project_creator.rb +2 -2
  59. data/lib/gooddata/models/schedule.rb +10 -8
  60. data/lib/gooddata/models/to_wire.rb +2 -2
  61. data/lib/gooddata/models/user_filters/mandatory_user_filter.rb +67 -0
  62. data/lib/gooddata/models/user_filters/user_filter.rb +96 -0
  63. data/lib/gooddata/models/user_filters/user_filter_builder.rb +409 -0
  64. data/lib/gooddata/{rest/connections/connections.rb → models/user_filters/user_filters.rb} +1 -0
  65. data/lib/gooddata/models/user_filters/variable_user_filter.rb +14 -0
  66. data/lib/gooddata/rest/client.rb +32 -21
  67. data/lib/gooddata/rest/connection.rb +283 -11
  68. data/lib/gooddata/rest/connections/rest_client_connection.rb +47 -109
  69. data/lib/gooddata/version.rb +1 -1
  70. data/spec/data/column_based_permissions.csv +7 -0
  71. data/spec/data/column_based_permissions2.csv +6 -0
  72. data/spec/data/hello_world_process/hello_world.rb +3 -1
  73. data/spec/data/line_based_permissions.csv +3 -0
  74. data/spec/data/m_n_model/blueprint.json +76 -0
  75. data/spec/data/{model_view.json → wire_models/model_view.json} +0 -0
  76. data/spec/data/wire_models/nu_model.json +3046 -0
  77. data/spec/helpers/process_helper.rb +2 -2
  78. data/spec/helpers/project_helper.rb +29 -0
  79. data/spec/helpers/schedule_helper.rb +1 -1
  80. data/spec/integration/command_datawarehouse_spec.rb +32 -0
  81. data/spec/integration/create_project_spec.rb +0 -1
  82. data/spec/integration/full_process_schedule_spec.rb +13 -5
  83. data/spec/integration/full_project_spec.rb +2 -1
  84. data/spec/integration/over_to_user_filters_spec.rb +92 -0
  85. data/spec/integration/project_spec.rb +233 -0
  86. data/spec/integration/rest_spec.rb +209 -0
  87. data/spec/integration/user_filters_spec.rb +193 -0
  88. data/spec/integration/variables_spec.rb +196 -0
  89. data/spec/unit/commands/command_auth_spec.rb +0 -7
  90. data/spec/unit/commands/command_process_spec.rb +10 -13
  91. data/spec/unit/core/connection_spec.rb +0 -19
  92. data/spec/unit/helpers/global_helpers_spec.rb +57 -0
  93. data/spec/unit/models/domain_spec.rb +80 -40
  94. data/spec/unit/models/from_wire_spec.rb +8 -1
  95. data/spec/unit/models/params_spec.rb +6 -6
  96. data/spec/unit/models/profile_spec.rb +23 -22
  97. data/spec/unit/models/project_blueprint_spec.rb +1 -6
  98. data/spec/unit/models/project_spec.rb +331 -286
  99. data/spec/unit/models/schedule_spec.rb +39 -14
  100. data/spec/unit/models/user_filters_spec.rb +89 -0
  101. data/spec/unit/models/variable_spec.rb +259 -0
  102. metadata +31 -7
  103. data/lib/gooddata/rest/connections/dummy_connection.rb +0 -52
  104. data/spec/unit/core/rest_spec.rb +0 -106
@@ -17,8 +17,6 @@ module GoodData
17
17
  include GoodData::Mixin::RestResource
18
18
  root_key :metric
19
19
 
20
- PARSE_MAQL_OBJECT_REGEXP = /\[([^\]]+)\]/
21
-
22
20
  class << self
23
21
  # Method intended to get all objects of that type in a specified project
24
22
  #
@@ -212,27 +210,7 @@ module GoodData
212
210
  # Looks up the readable values of the objects used inside of MAQL epxpressions. Labels and elements titles are based on the primary label.
213
211
  # @return [String] Ther resulting MAQL like expression
214
212
  def pretty_expression
215
- opts = {
216
- :client => client,
217
- :project => project
218
- }
219
-
220
- temp = expression.dup
221
- pairs = expression.scan(PARSE_MAQL_OBJECT_REGEXP).pmap do |uri|
222
- uri = uri.first
223
- if uri =~ /elements/
224
- [uri, Attribute.find_element_value(uri, opts)]
225
- else
226
- [uri, GoodData::MdObject[uri, opts].title]
227
- end
228
- end
229
-
230
- pairs.each do |el|
231
- uri = el[0]
232
- obj = el[1]
233
- temp.sub!(uri, obj)
234
- end
235
- temp
213
+ SmallGoodZilla.pretty_print(expression, client: client, project: project)
236
214
  end
237
215
  end
238
216
  end
@@ -120,8 +120,8 @@ module GoodData
120
120
  #
121
121
  # @return [String] Returns data
122
122
  def export(format)
123
- result = GoodData.post('/gdc/xtab2/executor3', 'report_req' => { 'report' => uri })
124
- result1 = GoodData.post('/gdc/exporter/executor', :result_req => { :format => format, :result => result })
123
+ result = client.post('/gdc/xtab2/executor3', 'report_req' => { 'report' => uri })
124
+ result1 = client.post('/gdc/exporter/executor', :result_req => { :format => format, :result => result })
125
125
  GoodData.poll_on_code(result1['uri'], process: false)
126
126
  end
127
127
 
@@ -44,6 +44,12 @@ module GoodData
44
44
  }
45
45
  end
46
46
 
47
+ def create_filters_part(filters)
48
+ filters.select { |f| f.class == GoodData::Variable }.map do |v|
49
+ { expression: "[#{v.uri}]" }
50
+ end
51
+ end
52
+
47
53
  def create_part(stuff)
48
54
  stuff = Array(stuff)
49
55
  parts = stuff.reduce([]) do |memo, item|
@@ -146,7 +152,6 @@ module GoodData
146
152
  }
147
153
  }
148
154
  uri = "/gdc/app/projects/#{project.pid}/execute"
149
-
150
155
  client.post(uri, data)
151
156
  end
152
157
 
@@ -170,6 +175,13 @@ module GoodData
170
175
  end
171
176
  end
172
177
 
178
+ # Return true if the report definition is a chart
179
+ #
180
+ # @return [Boolean] Return true if report definition is a chart
181
+ def chart?
182
+ !table?
183
+ end
184
+
173
185
  def create(options = { :client => GoodData.connection, :project => GoodData.project })
174
186
  client = options[:client]
175
187
  fail ArgumentError, 'No :client specified' if client.nil?
@@ -182,6 +194,7 @@ module GoodData
182
194
 
183
195
  left = Array(options[:left])
184
196
  top = Array(options[:top])
197
+ filters = options[:filters] || []
185
198
 
186
199
  left = ReportDefinition.find(left, options)
187
200
  top = ReportDefinition.find(top, options)
@@ -204,7 +217,7 @@ module GoodData
204
217
  'rows' => ReportDefinition.create_part(left)
205
218
  },
206
219
  'format' => 'grid',
207
- 'filters' => []
220
+ 'filters' => ReportDefinition.create_filters_part(filters)
208
221
  },
209
222
  'meta' => {
210
223
  'tags' => '',
@@ -373,5 +386,12 @@ module GoodData
373
386
  end
374
387
  self
375
388
  end
389
+
390
+ # Return true if the report definition is a table
391
+ #
392
+ # @return [Boolean] Return true if report definition is a table
393
+ def table?
394
+ content['format'] == 'grid'
395
+ end
376
396
  end
377
397
  end
@@ -0,0 +1,81 @@
1
+ # encoding: UTF-8
2
+
3
+ require_relative '../metadata'
4
+
5
+ require_relative 'metadata'
6
+
7
+ module GoodData
8
+ class Variable < MdObject
9
+ root_key :prompt
10
+
11
+ class << self
12
+ # Method intended to get all objects of that type in a specified project
13
+ #
14
+ # @param options [Hash] the options hash
15
+ # @option options [Boolean] :full if passed true the subclass can decide to pull in full objects. This is desirable from the usability POV but unfortunately has negative impact on performance so it is not the default
16
+ # @return [Array<GoodData::MdObject> | Array<Hash>] Return the appropriate metadata objects or their representation
17
+ def all(options = { :client => GoodData.connection, :project => GoodData.project })
18
+ query('prompts', Variable, options)
19
+ end
20
+
21
+ def create(data, options = { :client => GoodData.connection, :project => GoodData.project })
22
+ title = data[:title]
23
+ project = options[:project]
24
+ c = client(options)
25
+ attribute = project.attributes(data[:attribute])
26
+
27
+ payload = {
28
+ 'prompt' => {
29
+ 'content' => {
30
+ 'attribute' => attribute.uri,
31
+ 'type' => 'filter'
32
+ },
33
+ 'meta' => {
34
+ 'tags' => '',
35
+ 'deprecated' => '0',
36
+ 'summary' => '',
37
+ 'title' => title,
38
+ 'category' => 'prompt'
39
+ }
40
+ }
41
+ }
42
+ c.create(self, payload, project: project)
43
+ end
44
+ end
45
+
46
+ # Retrieves variable values
47
+ #
48
+ # @return [Array<GoodData::VariableUserFilter>] Values of variable
49
+ def values
50
+ payload = {
51
+ variablesSearch: {
52
+ variables: [
53
+ uri
54
+ ],
55
+ context: []
56
+ }
57
+ }
58
+ client.post("/gdc/md/#{project.pid}/variables/search", payload)['variables'].map { |f| client.create(GoodData::VariableUserFilter, f, project: project) }
59
+ end
60
+
61
+ # Retrieves variable values and returns only those related to user
62
+ #
63
+ # @return [Array<GoodData::VariableUserFilter>] Values of variable related to user
64
+ def user_values
65
+ values.select { |x| x.level == :user }
66
+ end
67
+
68
+ # Retrieves variable values and returns only those related to project
69
+ #
70
+ # @return [Array<GoodData::VariableUserFilter>] Values of variable related to project
71
+ def project_values
72
+ values.select { |x| x.level == :project }
73
+ end
74
+
75
+ # Deletes all the values and eventually the variable itself
76
+ def delete
77
+ values.pmap(&:delete)
78
+ super
79
+ end
80
+ end
81
+ end
@@ -6,6 +6,7 @@ require_relative 'metadata/metadata'
6
6
 
7
7
  require_relative 'links'
8
8
  require_relative 'module_constants'
9
+ require_relative 'user_filters/user_filters'
9
10
 
10
11
  require 'fileutils'
11
12
  require 'multi_json'
@@ -122,7 +123,7 @@ module GoodData
122
123
 
123
124
  if res['taskStatus'] == 'ERROR' # rubocop:disable Style/GuardClause
124
125
  s = StringIO.new
125
- client.download_from_user_webdav(File.basename(dir) + '/upload_status.json', s)
126
+ client.download_from_user_webdav(File.basename(dir) + '/upload_status.json', s, :client => client, :project => project)
126
127
  js = MultiJson.load(s.string)
127
128
  fail "Load Failed with error #{JSON.pretty_generate(js)}"
128
129
  end
@@ -51,7 +51,6 @@ module GoodData
51
51
  Process[:all]
52
52
  end
53
53
 
54
- # TODO: Check the params.
55
54
  def with_deploy(dir, options = {}, &block)
56
55
  client = options[:client]
57
56
  fail ArgumentError, 'No :client specified' if client.nil?
@@ -87,7 +86,7 @@ module GoodData
87
86
  project = GoodData::Project[p, opts]
88
87
  fail ArgumentError, 'Wrong :project specified' if project.nil?
89
88
 
90
- zip_and_upload path, files_to_exclude, opts
89
+ zip_and_upload(path, files_to_exclude, opts)
91
90
  end
92
91
 
93
92
  # Deploy a new process or redeploy existing one.
@@ -119,7 +118,7 @@ module GoodData
119
118
  verbose = options[:verbose] || false
120
119
  puts HighLine.color("Deploying #{path}", HighLine::BOLD) if verbose
121
120
 
122
- deployed_path = Process.upload_package(path, files_to_exclude, :client => client, :project => project)
121
+ deployed_path = Process.upload_package(path, files_to_exclude, client: client, project: project)
123
122
  data = {
124
123
  :process => {
125
124
  :name => deploy_name,
@@ -197,7 +196,7 @@ module GoodData
197
196
  # @option options [String] :name Readable name of the process
198
197
  # @option options [Boolean] :verbose (false) Switch on verbose mode for detailed logging
199
198
  def deploy(path, options = {})
200
- Process.deploy(path, options.merge(:process_id => process_id, :client => client, :project => project))
199
+ Process.deploy(path, client: client, process_id: process_id, :project => project).merge(options)
201
200
  end
202
201
 
203
202
  # Downloads the process from S3 in a zipped form.
@@ -68,26 +68,13 @@ module GoodData
68
68
  c.factory.create(Profile, response)
69
69
  end
70
70
 
71
- # Apply changes to object.
72
- #
73
- # @param obj [GoodData::Profile] Object to be modified
74
- # @param changes [Hash] Hash with modifications
75
- # @return [GoodData::Profile] Modified object
76
- def apply(obj, changes)
77
- changes.each do |param, val|
78
- next unless ASSIGNABLE_MEMBERS.include? param
79
- obj.send("#{param}=", val)
80
- end
81
- obj
82
- end
83
-
84
71
  # Creates new instance from hash with attributes
85
72
  #
86
73
  # @param attributes [Hash] Hash with initial attributes
87
74
  # @return [GoodData::Profile] New profile instance
88
75
  def create(attributes)
89
76
  json = EMPTY_OBJECT.dup
90
- res = GoodData::Profile.new(json)
77
+ res = client.create(GoodData::Profile, json)
91
78
 
92
79
  attributes.each do |k, v|
93
80
  res.send("#{k}=", v) if ASSIGNABLE_MEMBERS.include? k
@@ -97,62 +84,20 @@ module GoodData
97
84
  res
98
85
  end
99
86
 
100
- # Gets user currently logged in
101
- # @return [GoodData::Profile] User currently logged-in
102
- def current
103
- GoodData.connection.user
87
+ def diff(item_1, item_2)
88
+ x = diff_list([item_1], [item_2])
89
+ return {} if x[:changed].empty?
90
+ x[:changed].first[:diff]
104
91
  end
105
92
 
106
- # Gets hash representing diff of profiles
107
- #
108
- # @param user1 [GoodData::Profile] Original user
109
- # @param user2 [GoodData::Profile] User to compare with
110
- # @return [Hash] Hash representing diff
111
- def diff(user1, user2)
112
- res = {}
113
- ASSIGNABLE_MEMBERS.each do |k|
114
- l_value = user1.send("#{k}")
115
- r_value = user2.send("#{k}")
116
- res[k] = r_value if l_value != r_value
117
- end
118
- res
93
+ def diff_list(list_1, list_2)
94
+ GoodData::Helpers.diff(list_1, list_2, key: :login)
119
95
  end
120
96
 
121
- def diff_list(list1, list2)
122
- tmp = Hash[list1.map { |v| [v.email, v] }]
123
-
124
- res = {
125
- :added => [],
126
- :removed => [],
127
- :changed => []
128
- }
129
-
130
- list2.each do |user_new|
131
- user_existing = tmp[user_new.email]
132
- if user_existing.nil?
133
- res[:added] << user_new
134
- next
135
- end
136
-
137
- next if user_existing == user_new
138
-
139
- diff = self.diff(user_existing, user_new)
140
- res[:changed] << {
141
- :user => user_existing,
142
- :diff => diff
143
- }
144
- end
145
-
146
- tmp = Hash[list2.map { |v| [v.email, v] }]
147
- list1.each do |user_existing|
148
- user_new = tmp[user_existing.email]
149
- if user_new.nil?
150
- res[:removed] << user_existing
151
- next
152
- end
153
- end
154
-
155
- res
97
+ # Gets user currently logged in
98
+ # @return [GoodData::Profile] User currently logged-in
99
+ def current
100
+ client.user
156
101
  end
157
102
  end
158
103
 
@@ -169,13 +114,8 @@ module GoodData
169
114
  # @param right [GoodData::Profile] Project to compare with
170
115
  # @return [Boolean] True if same else false
171
116
  def ==(other)
172
- res = true
173
- ASSIGNABLE_MEMBERS.each do |k|
174
- l_val = send("#{k}")
175
- r_val = other.send("#{k}")
176
- res = false if l_val != r_val
177
- end
178
- res
117
+ return false unless other.respond_to?(:to_hash)
118
+ to_hash == other.to_hash
179
119
  end
180
120
 
181
121
  # Checks objects for non-equality
@@ -190,9 +130,9 @@ module GoodData
190
130
  #
191
131
  # @param changes [Hash] Hash with modifications
192
132
  # @return [GoodData::Profile] Modified object
193
- def apply(changes)
194
- GoodData::Profile.apply(self, changes)
195
- end
133
+ # def apply(changes)
134
+ # GoodData::Profile.apply(self, changes)
135
+ # end
196
136
 
197
137
  # Gets the company name
198
138
  #
@@ -374,7 +314,7 @@ module GoodData
374
314
 
375
315
  if uri && !uri.empty?
376
316
  url = "/gdc/account/profile/#{obj_id}"
377
- @json = GoodData.put url, raw
317
+ @json = client.put url, raw
378
318
  @dirty = false
379
319
  end
380
320
  end
@@ -406,14 +346,42 @@ module GoodData
406
346
  #
407
347
  # @return [String] Resource URI
408
348
  def uri
409
- @json['accountSetting']['links']['self']
349
+ GoodData::Helpers.get_path(@json, %w(accountSetting links self))
350
+ # @json['accountSetting']['links']['self']
410
351
  end
411
352
 
412
- private
353
+ def data
354
+ data = @json || {}
355
+ data['accountSetting'] || {}
356
+ end
413
357
 
414
- def initialize(json)
415
- @json = json
416
- @user = @json['accountSetting']['firstName'] + ' ' + @json['accountSetting']['lastName']
358
+ def links
359
+ data['links'] || {}
360
+ end
361
+
362
+ def content
363
+ keys = (data.keys - ['links'])
364
+ data.slice(*keys)
365
+ end
366
+
367
+ def name
368
+ (first_name || '') + (last_name || '')
369
+ end
370
+
371
+ def to_hash
372
+ tmp = content.merge(uri: uri).symbolize_keys
373
+ [
374
+ [:companyName, :company],
375
+ [:phoneNumber, :phone],
376
+ [:firstName, :first_name],
377
+ [:lastName, :last_name],
378
+ [:authenticationModes, :authentication_modes]
379
+ ].each do |vals|
380
+ wire, rb = vals
381
+ tmp[rb] = tmp[wire]
382
+ tmp.delete(wire)
383
+ end
384
+ tmp
417
385
  end
418
386
  end
419
387
  end
@@ -152,6 +152,10 @@ module GoodData
152
152
  end
153
153
  end
154
154
 
155
+ # Creates a metric in a project
156
+ #
157
+ # @param [options] Optional report options
158
+ # @return [GoodData::Report] Instance of new report
155
159
  def add_metric(metric, options = {})
156
160
  default = { client: client, project: self }
157
161
  if metric.is_a?(String)
@@ -189,7 +193,7 @@ module GoodData
189
193
  #
190
194
  # @return [Boolean] True if user has admin role in the project, false otherwise.
191
195
  def am_i_admin?
192
- user_has_role?(GoodData.user, 'admin')
196
+ user_has_role?(client.user, 'admin')
193
197
  end
194
198
 
195
199
  # Helper for getting attributes of a project
@@ -252,6 +256,10 @@ module GoodData
252
256
  end
253
257
  end
254
258
 
259
+ def dimensions(id = :all)
260
+ GoodData::Dimension[id, client: client, project: self]
261
+ end
262
+
255
263
  # Export a clone from a project to be later imported.
256
264
  # If you do not want to do anything special and you do not need fine grained
257
265
  # controle use clone method which does all the heavy lifting for you.
@@ -302,17 +310,20 @@ module GoodData
302
310
  end
303
311
 
304
312
  def compute_report(spec = {})
305
- GoodData::ReportDefinition.execute(spec.merge(:client => client, :project => self))
313
+ GoodData::ReportDefinition.execute(spec.merge(client: client, project: self))
306
314
  end
307
315
 
308
316
  def compute_metric(expression)
309
- GoodData::Metric.xexecute(expression, :client => client, :project => self)
317
+ GoodData::Metric.xexecute(expression, client: client, project: self)
310
318
  end
311
319
 
312
320
  def create_schedule(process, date, executable, options = {})
313
- GoodData::Schedule.create(process, date, executable, options.merge(:client => client, :project => self))
321
+ GoodData::Schedule.create(process, date, executable, options.merge(client: client, project: self))
314
322
  end
315
323
 
324
+ def create_variable(data)
325
+ GoodData::Variable.create(data, client: client, project: self)
326
+ end
316
327
  # Helper for getting dashboards of a project
317
328
  #
318
329
  # @param [String | Number | Object] Anything that you can pass to GoodData::Dashboard[id]
@@ -325,6 +336,10 @@ module GoodData
325
336
  blueprint.datasets
326
337
  end
327
338
 
339
+ def data_permissions
340
+ GoodData::MandatoryUserFilter.all(client: client, project: self)
341
+ end
342
+
328
343
  # Deletes project
329
344
  def delete
330
345
  fail "Project '#{title}' with id #{uri} is already deleted" if state == :deleted
@@ -401,6 +416,13 @@ module GoodData
401
416
  GoodData::Attribute.find_element_value(uri, client: client, project: self)
402
417
  end
403
418
 
419
+ # Get WebDav directory for project data
420
+ # @return [String]
421
+ def get_project_webdav_path(_file)
422
+ u = URI(links['uploads'])
423
+ URI.join(u.to_s.chomp(u.path.to_s), '/project-uploads/', "#{pid}/")
424
+ end
425
+
404
426
  # Gets project role by its identifier
405
427
  #
406
428
  # @param [String] role_name Title of role to look for
@@ -454,122 +476,55 @@ module GoodData
454
476
  nil
455
477
  end
456
478
 
457
- # Gets user by its email, full_name, login or uri
479
+ # Gets user by its login or uri in various shapes
480
+ # It does not find by other information because that is not unique. If you want to search by name or email please
481
+ # use fuzzy_get_user.
458
482
  #
459
483
  # @param [String] name Name to look for
460
484
  # @param [Array<GoodData::User>]user_list Optional cached list of users used for look-ups
461
485
  # @return [GoodDta::Membership] User
462
- def get_user(name, user_list = users)
463
- return name if name.instance_of?(GoodData::Membership)
464
- return member(name) if name.instance_of?(GoodData::Profile)
465
- name.downcase!
466
- user_list.each do |user|
467
- return user if user.uri.downcase == name ||
468
- user.login.downcase == name ||
469
- user.email.downcase == name
486
+ def get_user(slug, user_list = users)
487
+ search_crit = if slug.respond_to?(:login)
488
+ slug.login || slug.uri
489
+ elsif slug.is_a?(Hash)
490
+ slug[:login] || slug[:uri]
491
+ else
492
+ slug
493
+ end
494
+ return nil unless search_crit
495
+ user_list.find do |user|
496
+ user.uri == search_crit.downcase ||
497
+ user.login.downcase == search_crit.downcase
470
498
  end
471
- nil
472
499
  end
473
500
 
474
- # Exports project users to file
475
- def import_users(path, opts = { :header => true }, &_block)
476
- opts[:path] = path
477
-
478
- ##########################
479
- # Caching/Cached objects
480
- ##########################
481
- domains = {}
482
- current_users = users
483
- role_list = roles
484
-
485
- ##########################
486
- # Load users from CSV
487
- ##########################
488
- new_users = GoodData::Helpers.csv_read(opts) do |row|
489
- json = {}
490
- if block_given?
491
- json = yield row
492
- else
493
- json = {
494
- 'user' => {
495
- 'content' => {
496
- 'email' => row[0],
497
- 'login' => row[1],
498
- 'firstname' => row[2],
499
- 'lastname' => row[3]
500
- },
501
- 'meta' => {}
502
- }
503
- }
504
- end
505
-
506
- GoodData::User.new(json)
507
- end
508
-
509
- ##########################
510
- # Diff users
511
- ##########################
512
- diff = GoodData::User.diff_list(current_users, new_users)
513
-
514
- ##########################
515
- # Create new users
516
- ##########################
517
- diff[:added].map do |user|
518
- # TODO: Add user here
519
- domain_name = user.json['user']['content']['domain']
520
-
521
- # Lookup for domain in cache'
522
- domain = domains[domain_name]
523
-
524
- # Get domain info from REST, add to cache
525
- if domain.nil?
526
- domain = {
527
- :domain => GoodData::Domain[domain_name],
528
- :users => GoodData::Domain[domain_name].users
529
- }
530
-
531
- domain[:users_map] = Hash[domain[:users].map { |u| [u.email, u] }]
532
- domains[domain_name] = domain
533
- end
534
-
535
- # Check if user exists in domain
536
- domain_user = domain[:users_map][user.email]
537
-
538
- # Create domain user if needed
539
- unless domain_user
540
- password = user.json['user']['content']['password']
541
-
542
- # Fill necessary user data
543
- user_data = {
544
- :login => user.login,
545
- :firstName => user.first_name,
546
- :lastName => user.last_name,
547
- :password => password,
548
- :verifyPassword => password,
549
- :email => user.login
550
- }
551
-
552
- # Add created user to cache
553
- domain_user = domain[:domain].add_user(user_data)
554
- domain[:users] << domain_user
555
- domain[:users_map][user.email] = domain_user
556
- end
501
+ # Get WebDav directory for user data
502
+ # @return [String]
503
+ def get_user_webdav_path(_file)
504
+ u = URI(links['uploads'])
505
+ URI.join(u.to_s.chomp(u.path.to_s), '/uploads/')
506
+ end
557
507
 
558
- # Lookup for role
559
- role_name = user.json['user']['content']['role'] || 'readOnlyUser'
560
- role = get_role_by_identifier(role_name, role_list)
561
- next if role.nil?
508
+ # Gets user by its email, full_name, login or uri
509
+ alias_method :member, :get_user
562
510
 
563
- # Assign user project role
564
- add_user(domain_user, [role.uri])
511
+ # Gets user by its email, full_name, login or uri.
512
+ #
513
+ # @param [String] name Name to look for
514
+ # @param [Array<GoodData::User>]user_list Optional cached list of users used for look-ups
515
+ # @return [GoodDta::Membership] User
516
+ def fuzzy_get_user(name, user_list = users)
517
+ return name if name.instance_of?(GoodData::Membership)
518
+ return member(name) if name.instance_of?(GoodData::Profile)
519
+ name = name.is_a?(Hash) ? name[:login] || name[:uri] : name
520
+ return nil unless name
521
+ name.downcase!
522
+ user_list.select do |user|
523
+ user.uri.downcase == name ||
524
+ user.login.downcase == name ||
525
+ user.email.downcase == name
565
526
  end
566
-
567
- ##########################
568
- # Remove old users
569
- ##########################
570
- # diff[:removed].map do |user|
571
- # user.disable(self)
572
- # end
527
+ nil
573
528
  end
574
529
 
575
530
  # Checks whether user has particular role in given proejct
@@ -665,20 +620,6 @@ module GoodData
665
620
  @md ||= client.create(Links, client.get(data['links']['metadata']))
666
621
  end
667
622
 
668
- # Gets membership for profile specified
669
- #
670
- # @param [GoodData::Profile] profile - Profile to be checked
671
- # @param [Array<GoodData::Membership>] list Optional list of members to check against
672
- # @return [GoodData::Membership] Membership if found
673
- def member(profile, list = members)
674
- if profile.is_a? String
675
- return list.find do |m|
676
- m.uri == profile || m.login == profile
677
- end
678
- end
679
- list.find { |m| m.login == profile.login }
680
- end
681
-
682
623
  # Get data from project specific metadata storage
683
624
  #
684
625
  # @param [Symbol | String] :all or nothing for all keys or a string for value of specific key
@@ -719,6 +660,10 @@ module GoodData
719
660
  !member(profile, list).nil?
720
661
  end
721
662
 
663
+ def members?(profiles, list = members)
664
+ profiles.map { |p| member?(p, list) }
665
+ end
666
+
722
667
  # Gets raw resource ID
723
668
  #
724
669
  # @return [String] Raw resource ID
@@ -850,7 +795,7 @@ module GoodData
850
795
  tmp = client.get(url)
851
796
  tmp['projectRoles']['roles'].pmap do |role_url|
852
797
  json = client.get role_url
853
- client.create(GoodData::ProjectRole, json)
798
+ client.create(GoodData::ProjectRole, json, project: self)
854
799
  end
855
800
  end
856
801
 
@@ -926,103 +871,81 @@ module GoodData
926
871
  # List of users in project
927
872
  #
928
873
  # @return [Array<GoodData::User>] List of users
929
- def users
930
- tmp = client.get @json['project']['links']['users']
931
- tmp['users'].map do |user|
932
- client.factory.create(GoodData::Membership, user)
874
+ def users(opts = { offset: 0, limit: 100 })
875
+ result = []
876
+
877
+ # TODO: @korczis, review this after WA-3953 get fixed
878
+ offset = 0 || opts[:offset]
879
+ uri = "/gdc/projects/#{pid}/users?offset=#{offset}&limit=#{opts[:limit]}"
880
+ loop do
881
+ break unless uri
882
+ tmp = client(opts).get(uri)
883
+ tmp['users'].each do |user|
884
+ result << client.factory.create(GoodData::Membership, user, project: self)
885
+ end
886
+ offset += opts[:limit]
887
+ if tmp['users'].length == opts[:limit]
888
+ uri = "/gdc/projects/#{pid}/users?offset=#{offset}&limit=#{opts[:limit]}"
889
+ else
890
+ uri = nil
891
+ end
933
892
  end
893
+
894
+ opts[:all] ? result : result.select(&:enabled?).reject(&:deleted?)
934
895
  end
935
896
 
936
897
  alias_method :members, :users
937
898
 
938
- def users_create(list, role_list = roles)
939
- domains = {}
940
- list.map do |user|
941
- # TODO: Add user here
942
- domain_name = user.json['user']['content']['domain']
943
-
944
- # Lookup for domain in cache'
945
- domain = domains[domain_name]
946
-
947
- # Get domain info from REST, add to cache
948
- if domain.nil?
949
- d = GoodData::Domain[domain_name, { :client => client }]
950
- domain = {
951
- :domain => d,
952
- :users => d.users(:client => client)
953
- }
899
+ def whitelist_users(new_users, users_list, whitelist)
900
+ # whitelist_users
901
+ return [new_users, users_list] unless whitelist
954
902
 
955
- domain[:users_map] = Hash[domain[:users].map { |u| [u.email, u] }]
956
- domains[domain_name] = domain
957
- end
903
+ whitelist_proc = proc do |user|
904
+ whitelist.any? { |wl| wl.is_a?(Regexp) ? user[:login] =~ wl : user[:login].include?(wl) }
905
+ end
958
906
 
959
- # Check if user exists in domain
960
- domain_user = domain[:users_map][user.email]
961
- fail ArgumentError, "Trying to add user '#{user.login}' which is not valid user in domain '#{domain_name}'" if domain_user.nil?
907
+ [new_users.reject(&whitelist_proc), users_list.reject(&whitelist_proc)]
908
+ end
962
909
 
963
- # Lookup for role
964
- role_name = user.json['user']['content']['role'] || 'readOnlyUser'
965
- role = get_role(role_name, role_list)
966
- fail ArgumentError, "Invalid role name specified '#{role_name}' for user '#{user.email}'" if role.nil?
910
+ # Imports users
911
+ def import_users(new_users, options = {})
912
+ domain = options[:domain]
913
+ users_list = users.map(&:to_hash)
914
+ new_users = new_users.map { |x| (x.is_a?(Hash) && x[:user] && x[:user].to_hash.merge(role: x[:role])) || x.to_hash }
967
915
 
968
- # Assign user project role
969
- set_user_roles(domain_user, [role.uri], role_list)
970
- end
971
- end
916
+ whitelisted_new_users, whitelisted_users = whitelist_users(new_users.map(&:to_hash), users_list, options[:whitelists])
972
917
 
973
- # Imports users from CSV
974
- #
975
- # # Features
976
- # - Create new users
977
- # - Delete old users
978
- # - Update existing users
979
- #
980
- # CSV Format
981
- # TODO: Describe CSV Format here
982
- #
983
- # @param path CSV file to be loaded
984
- # @param opts Optional additional options
985
- def users_import(new_users, domain = nil)
986
918
  # Diff users
987
- diff = GoodData::Membership.diff_list(users, new_users)
988
-
919
+ diff = GoodData::Helpers.diff(whitelisted_users, whitelisted_new_users, key: :login)
920
+ results = []
989
921
  # Create domain users
990
- GoodData::Domain.users_create(diff[:added], domain)
922
+ results.concat domain.create_users(diff[:added])
923
+
924
+ # Update domain users
925
+ domain.create_users(diff[:changed].map { |u| u[:new_obj] })
991
926
 
992
927
  # Create new users
993
928
  role_list = roles
994
- users_create(diff[:added], role_list)
995
-
996
- # Get changed users objects from hash
997
- list = diff[:changed].map do |user|
998
- user[:user]
999
- end
1000
-
1001
- # Join list of changed users with 'same' users
1002
- list = list.zip(diff[:same]).flatten.compact
1003
-
1004
- new_users_map = Hash[new_users.map { |u| [u.email, u] }]
1005
-
1006
- # Create list with user, desired_roles hashes
1007
- list = list.map do |user|
929
+ u = diff[:added].map do |x|
1008
930
  {
1009
- :user => user,
1010
- :roles => new_users_map[user.email].json['user']['content']['role'].split(' ').map(&:downcase).sort
931
+ user: x,
932
+ role: x[:role] || x[:roles]
1011
933
  }
1012
934
  end
935
+ results.concat create_users(u, roles: role_list, domain: domain)
1013
936
 
1014
- # Update existing users
1015
- set_users_roles(list, role_list)
937
+ # # Update existing users
938
+ list = diff[:changed].map { |x| { user: x[:new_obj], role: x[:new_obj][:role] || x[:new_obj][:roles] } }
939
+ results.concat set_users_roles(list, roles: role_list)
1016
940
 
1017
941
  # Remove old users
1018
- users_remove(diff[:removed])
942
+ results.concat(disable_users(diff[:removed]))
943
+ results
1019
944
  end
1020
945
 
1021
- # Disable users
1022
- #
1023
- # @param list List of users to be disabled
1024
- def users_remove(list)
1025
- list.pmap(&:disable)
946
+ def disable_users(list, options = {})
947
+ project_users = options[:project_users] || users
948
+ list.map { |u| get_user(u, project_users) }.pmap(&:disable)
1026
949
  end
1027
950
 
1028
951
  # Update user
@@ -1030,14 +953,18 @@ module GoodData
1030
953
  # @param user User to be updated
1031
954
  # @param desired_roles Roles to be assigned to user
1032
955
  # @param role_list Optional cached list of roles used for lookups
1033
- def set_user_roles(user, desired_roles, role_list = roles)
1034
- if user.is_a? String
1035
- user = get_user(user)
1036
- fail ArgumentError, "Invalid user '#{user}' specified" if user.nil?
1037
- end
956
+ def set_user_roles(login, desired_roles, options = {})
957
+ role_list = options[:roles] || roles
958
+ domain = client.domain(options[:domain]) if options[:domain]
959
+ project_users = options[:project_users] || users
960
+ domain_users = options[:domain_users] || (domain && domain.users)
1038
961
 
1039
- desired_roles = [desired_roles] unless desired_roles.is_a? Array
962
+ project_user = get_user(login, project_users)
963
+ domain_user = domain.get_user(login, domain_users) if domain && !project_user
964
+ user = project_user || domain_user
965
+ fail ArgumentError, "Invalid user '#{login}' specified" unless user
1040
966
 
967
+ desired_roles = [desired_roles] unless desired_roles.is_a? Array
1041
968
  roles = desired_roles.map do |role_name|
1042
969
  role = get_role(role_name, role_list)
1043
970
  fail ArgumentError, "Invalid role '#{role_name}' specified for user '#{user.email}'" if role.nil?
@@ -1057,7 +984,7 @@ module GoodData
1057
984
  }
1058
985
  }
1059
986
 
1060
- client.post url, payload
987
+ client.post(url, payload)
1061
988
  end
1062
989
 
1063
990
  alias_method :add_user, :set_user_roles
@@ -1066,17 +993,43 @@ module GoodData
1066
993
  #
1067
994
  # @param list List of users to be updated
1068
995
  # @param role_list Optional list of cached roles to prevent unnecessary server round-trips
1069
- def set_users_roles(list, _role_list = roles)
996
+ def set_users_roles(list, options = {})
997
+ role_list = options[:roles] || roles
998
+ project_users = options[:project_users] || users
999
+ domain = options[:domain] && client.domain(options[:domain])
1000
+ domain_users = domain.nil? ? nil : domain.users
1001
+
1070
1002
  list.pmap do |user_hash|
1071
- user = user_hash[:user]
1072
- roles = user_hash[:role] || user_hash[:roles]
1073
- {
1074
- :user => user,
1075
- :result => set_user_roles(user, roles)
1076
- }
1003
+ begin
1004
+ user = user_hash[:user]
1005
+ desired_roles = user_hash[:role] || user_hash[:roles] || 'readOnlyUser'
1006
+ result = add_user(user, desired_roles, options.merge(domain: domain,
1007
+ domain_users: domain_users,
1008
+ roles: role_list,
1009
+ project_users: project_users))
1010
+
1011
+ {
1012
+ type: :role_set,
1013
+ user: user,
1014
+ result: result
1015
+ }
1016
+ rescue ArgumentError, RuntimeError => e
1017
+ { type: :error, reason: e }
1018
+ end
1077
1019
  end
1078
1020
  end
1079
1021
 
1022
+ alias_method :add_users, :set_users_roles
1023
+ alias_method :create_users, :set_users_roles
1024
+
1025
+ def add_data_permissions(filters, options = {})
1026
+ GoodData::UserFilterBuilder.execute_mufs(filters, { client: client, project: self }.merge(options))
1027
+ end
1028
+
1029
+ def add_variable_permissions(filters, var, options = {})
1030
+ GoodData::UserFilterBuilder.execute_variables(filters, var, { client: client, project: self }.merge(options))
1031
+ end
1032
+
1080
1033
  # Run validation on project
1081
1034
  # Valid settins for validation are (default all):
1082
1035
  # ldm - Checks the consistency of LDM objects.
@@ -1091,5 +1044,9 @@ module GoodData
1091
1044
  body['wTaskStatus'] && body['wTaskStatus']['status'] == 'RUNNING'
1092
1045
  end
1093
1046
  end
1047
+
1048
+ def variables(id = :all, options = { client: client, project: self })
1049
+ GoodData::Variable[id, options]
1050
+ end
1094
1051
  end
1095
1052
  end