gooddata 0.6.17 → 0.6.18

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0bbc18e2feb9dc7d8f7b2fcadfd4e26474b666d0
4
- data.tar.gz: 0020310a69543fcea52bd5d0bee0ddbe25bae3ff
3
+ metadata.gz: f683852cdcca1c3ac58923b66d2875199f4941ca
4
+ data.tar.gz: c439059752871aa8edaaae9578ebb5d8070d055a
5
5
  SHA512:
6
- metadata.gz: dc66e78d316ee699b2374f1885a8cd6736addd8040931400e7fab8c287327a322828480fbd0d25a5ebaea343c8b810e926afa174b2f39239d1aec0e9f504114a
7
- data.tar.gz: 388b87c526902279fd6c01b5739f478e3d8d4787179e3062ae495caa0809ad58d47b3edaee21230425f373d5ab056097e20214b568038da7cdd07053f981b3c8
6
+ metadata.gz: 7645abe267313b36bcc9f5d9ae076be9a7b608860db6a7bd27a91919dab47af0e31d3a48a921521d546f28fe822312ff69007826886447ea0dd165b170f7e5bb
7
+ data.tar.gz: 319d48cffc079ce239e78ff4b8ab4d2c3911a5fb381a13d47fb8d91f58e7947dab7d7bbed9fbfa44a97d87689f6b1f3340612246a59c8ffcec581109e69c1765
@@ -168,7 +168,8 @@ module GoodData
168
168
  c = client(opts)
169
169
  escaped_login = CGI.escape(login)
170
170
  domain = c.domain(domain)
171
- url = "/gdc/account/domains/#{domain.name}/users?login=#{escaped_login}"
171
+ GoodData.logger.warn("Retrieving particular user \"#{login.inspect}\" from domain #{domain.name}")
172
+ url = "#{domain.uri}/users?login=#{escaped_login}"
172
173
  tmp = c.get url
173
174
  items = tmp['accountSettings']['items'] if tmp['accountSettings']
174
175
  items && items.length > 0 ? c.factory.create(GoodData::Profile, items.first) : nil
@@ -180,23 +181,30 @@ module GoodData
180
181
  # @option opts [Number] :offset The subject
181
182
  # @option opts [Number] :limit From address
182
183
  # TODO: Review opts[:limit] functionality
183
- def users(domain, opts = {})
184
- result = []
185
- page_limit = opts[:page_limit] || 1000
186
- limit = opts[:limit] || Float::INFINITY
187
- offset = opts[:offset]
188
- uri = "/gdc/account/domains/#{domain}/users?offset=#{offset}&limit=#{page_limit}"
189
- loop do
190
- tmp = client(opts).get(uri)
191
- tmp['accountSettings']['items'].each do |account|
192
- result << client(opts).create(GoodData::Profile, account)
193
- end
194
- break if result.length >= limit
184
+ def users(domain, id = :all, opts = {})
185
+ c = client(opts)
186
+ domain = c.domain(domain)
187
+ if id == :all
188
+ GoodData.logger.warn("Retrieving all users from domain #{domain.name}")
189
+ result = []
190
+ page_limit = opts[:page_limit] || 1000
191
+ limit = opts[:limit] || Float::INFINITY
192
+ offset = opts[:offset]
193
+ uri = "#{domain.uri}/users?offset=#{offset}&limit=#{page_limit}"
194
+ loop do
195
+ tmp = client(opts).get(uri)
196
+ tmp['accountSettings']['items'].each do |account|
197
+ result << client(opts).create(GoodData::Profile, account)
198
+ end
199
+ break if result.length >= limit
195
200
 
196
- uri = tmp['accountSettings']['paging']['next']
197
- break unless uri
201
+ uri = tmp['accountSettings']['paging']['next']
202
+ break unless uri
203
+ end
204
+ result
205
+ else
206
+ find_user_by_login(domain, id)
198
207
  end
199
- result
200
208
  end
201
209
 
202
210
  # Create users specified in list
@@ -264,8 +272,8 @@ module GoodData
264
272
  # @param [Array<GoodData::User>]user_list Optional cached list of users used for look-ups
265
273
  # @return [GoodDta::Membership] User
266
274
  def get_user(name, user_list = users)
267
- return member(name) if name.instance_of?(GoodData::Membership)
268
- return member(name) if name.instance_of?(GoodData::Profile)
275
+ return member(name, user_list) if name.instance_of?(GoodData::Membership)
276
+ return member(name, user_list) if name.instance_of?(GoodData::Profile)
269
277
  name = name.is_a?(Hash) ? name[:login] || name[:uri] : name
270
278
  return nil unless name
271
279
  name.downcase!
@@ -332,12 +340,16 @@ module GoodData
332
340
  # domain = GoodData::Domain['gooddata-tomas-korcak']
333
341
  # pp domain.users
334
342
  #
335
- def users(opts = {})
336
- GoodData::Domain.users(name, opts.merge(client: client))
343
+ def users(id = :all, opts = {})
344
+ GoodData::Domain.users(name, id, opts.merge(client: client))
337
345
  end
338
346
 
339
347
  alias_method :members, :users
340
348
 
349
+ def uri
350
+ "/gdc/account/domains/#{name}"
351
+ end
352
+
341
353
  private
342
354
 
343
355
  # Private setter of domain name. Used by constructor not available for external users.
@@ -205,6 +205,7 @@ module GoodData
205
205
  # @return [IO] The stream of data that represents a zipped deployed process.
206
206
  def download
207
207
  link = links['source']
208
+ client.connection.refresh_token
208
209
  client.get(link, process: false) { |_, _, result| RestClient.get(result.to_hash['location'].first) }
209
210
  end
210
211
 
@@ -350,8 +350,8 @@ module GoodData
350
350
  GoodData::Dashboard[id, project: self, client: client]
351
351
  end
352
352
 
353
- def data_permissions
354
- GoodData::MandatoryUserFilter.all(client: client, project: self)
353
+ def data_permissions(id = :all)
354
+ GoodData::MandatoryUserFilter[id, client: client, project: self]
355
355
  end
356
356
 
357
357
  # Deletes project
@@ -890,7 +890,7 @@ module GoodData
890
890
  # List of users in project
891
891
  #
892
892
  # @return [Array<GoodData::User>] List of users
893
- def users(opts = { offset: 0, limit: 10_000 })
893
+ def users(opts = { offset: 0, limit: 1_000 })
894
894
  result = []
895
895
 
896
896
  # TODO: @korczis, review this after WA-3953 get fixed
@@ -933,9 +933,11 @@ module GoodData
933
933
  def import_users(new_users, options = {})
934
934
  domain = options[:domain]
935
935
  role_list = roles
936
- users_list = users
936
+ users_list = users(all: true, offset: 0, limit: 1_000)
937
937
  new_users = new_users.map { |x| (x.is_a?(Hash) && x[:user] && x[:user].to_hash.merge(role: x[:role])) || x.to_hash }
938
938
 
939
+ GoodData.logger.warn("Importing users to project (#{pid})")
940
+
939
941
  whitelisted_new_users, whitelisted_users = whitelist_users(new_users.map(&:to_hash), users_list, options[:whitelists])
940
942
 
941
943
  # conform the role on list of new users so we can diff them with the users coming from the project
@@ -949,11 +951,12 @@ module GoodData
949
951
  role = get_role(r, role_list)
950
952
  role && role.uri
951
953
  end
954
+ u[:status] = 'ENABLED'
952
955
  u
953
956
  end
954
957
 
955
958
  # Diff users. Only login and role is important for the diff
956
- diff = GoodData::Helpers.diff(whitelisted_users, diffable_new, key: :login, fields: [:login, :role])
959
+ diff = GoodData::Helpers.diff(whitelisted_users, diffable_new, key: :login, fields: [:login, :role, :status])
957
960
 
958
961
  results = []
959
962
  # Create new users
@@ -963,15 +966,19 @@ module GoodData
963
966
  role: x[:role]
964
967
  }
965
968
  end
966
-
967
- results.concat create_users(u, roles: role_list, domain: domain, project_users: whitelisted_users)
969
+ # This is only creating users that were not in the proejcts so far. This means this will reach into domain
970
+ GoodData.logger.warn("Creating #{diff[:added].count} users in project (#{pid})")
971
+ results.concat create_users(u, roles: role_list, domain: domain, project_users: whitelisted_users, only_domain: true)
968
972
 
969
973
  # # Update existing users
974
+ GoodData.logger.warn("Updating #{diff[:changed].count} users in project (#{pid})")
970
975
  list = diff[:changed].map { |x| { user: x[:new_obj], role: x[:new_obj][:role] || x[:new_obj][:roles] } }
971
976
  results.concat(set_users_roles(list, roles: role_list, project_users: whitelisted_users))
972
977
 
973
978
  # Remove old users
974
- results.concat(disable_users(diff[:removed]))
979
+ to_remove = diff[:removed].reject { |user| user[:status] == 'DISABLED' || user[:status] == :disabled }
980
+ GoodData.logger.warn("Removing #{to_remove.count} users in project (#{pid})")
981
+ results.concat(disable_users(to_remove))
975
982
  results
976
983
  end
977
984
 
@@ -982,7 +989,8 @@ module GoodData
982
989
  generate_user_payload(u[:uri], 'DISABLED')
983
990
  end
984
991
  payloads.each_slice(100).mapcat do |payload|
985
- client.post(url, 'users' => payload)
992
+ result = client.post(url, 'users' => payload)
993
+ result['projectUsersUpdateResult'].mapcat { |k, v| v.map { |x| { type: k.to_sym, uri: x } } }
986
994
  end
987
995
  end
988
996
 
@@ -1033,7 +1041,16 @@ module GoodData
1033
1041
  role_list = options[:roles] || roles
1034
1042
  project_users = options[:project_users] || users
1035
1043
  domain = options[:domain] && client.domain(options[:domain])
1036
- domain_users = domain.nil? ? nil : domain.users
1044
+ domain_users = if domain.nil?
1045
+ options[:domain_users]
1046
+ else
1047
+ if options[:only_domain] && list.count < 100
1048
+ list.map { |l| domain.find_user_by_login(l[:user][:login]) }
1049
+ else
1050
+ domain.users
1051
+ end
1052
+ end
1053
+
1037
1054
  users_to_add = list.flat_map do |user_hash|
1038
1055
  user = user_hash[:user] || user_hash[:login]
1039
1056
  desired_roles = user_hash[:role] || user_hash[:roles] || 'readOnlyUser'
@@ -1049,7 +1066,7 @@ module GoodData
1049
1066
  results = payloads.each_slice(100).map do |payload|
1050
1067
  client.post(url, 'users' => payload)
1051
1068
  end
1052
- results.flat_map { |x| x['projectUsersUpdateResult'].flat_map { |k, v| v.map { |v_2| { status: k.to_sym, uri: v_2 } } } }
1069
+ results.flat_map { |x| x['projectUsersUpdateResult'].flat_map { |k, v| v.map { |v_2| { type: k.to_sym, uri: v_2 } } } }
1053
1070
  end
1054
1071
 
1055
1072
  alias_method :add_users, :set_users_roles
@@ -30,19 +30,27 @@ module GoodData
30
30
  break if result['userFilters']['length'] < offset
31
31
  offset += count
32
32
  end
33
- vars.map do |a|
34
- uri = a['link']
35
- data = c.get(uri)
36
- payload = {
37
- 'expression' => data['userFilter']['content']['expression'],
38
- 'related' => user_lookup[a['link']],
39
- 'level' => :user,
40
- 'type' => :filter,
41
- 'uri' => a['link']
42
- }
43
- c.create(GoodData::MandatoryUserFilter, payload, project: project)
33
+ vars.each_slice(100).mapcat do |batch|
34
+ batch.pmap do |a|
35
+ uri = a['link']
36
+ data = c.get(uri)
37
+ payload = {
38
+ 'expression' => data['userFilter']['content']['expression'],
39
+ 'related' => user_lookup[a['link']],
40
+ 'level' => :user,
41
+ 'type' => :filter,
42
+ 'uri' => a['link']
43
+ }
44
+ c.create(GoodData::MandatoryUserFilter, payload, project: project)
45
+ end
44
46
  end
45
47
  end
48
+
49
+ def count(options = { client: GoodData.connection, project: GoodData.project })
50
+ c = client(options)
51
+ project = options[:project]
52
+ c.get(project.md['query'] + '/userfilters/')['query']['entries'].count
53
+ end
46
54
  end
47
55
 
48
56
  # Creates or updates the mandatory user filter on the server
@@ -1,8 +1,5 @@
1
1
  # encoding: UTF-8
2
2
 
3
- require_relative 'user_filter_builder_create'
4
- require_relative 'user_filter_builder_execute'
5
-
6
3
  module GoodData
7
4
  module UserFilterBuilder
8
5
  # Main Entry function. Gets values and processes them to get filters
@@ -109,19 +106,168 @@ module GoodData
109
106
  end
110
107
  end
111
108
 
112
- def self.verify_existing_users(filters, options = {})
113
- project = options[:project]
109
+ def self.get_missing_users(filters, options = {})
110
+ users_cache = options[:users_cache]
111
+ filters.reject { |u| users_cache.key?(u[:login]) }
112
+ end
114
113
 
114
+ def self.verify_existing_users(filters, options = {})
115
115
  users_must_exist = options[:users_must_exist] == false ? false : true
116
- users_cache = options[:users_cache] || create_cache(project.users, :login)
116
+ users_cache = options[:users_cache]
117
+ domain = options[:domain]
117
118
 
118
119
  if users_must_exist
119
- list = users_cache.values
120
- missing_users = filters.map { |x| x[:login] }.reject { |u| project.member?(u, list) }
120
+ missing_users = filters.reject do |u|
121
+ next true if users_cache.key?(u[:login])
122
+ domain_user = (domain && domain.find_user_by_login(u[:login]))
123
+ users_cache[domain_user.login] = domain_user if domain_user
124
+ next true if domain_user
125
+ false
126
+ end
121
127
  fail "#{missing_users.count} users are not part of the project and variable cannot be resolved since :users_must_exist is set to true (#{missing_users.join(', ')})" unless missing_users.empty?
122
128
  end
123
129
  end
124
130
 
131
+ def self.create_label_cache(result, options = {})
132
+ project = options[:project]
133
+
134
+ result.reduce({}) do |a, e|
135
+ e[:filters].map do |filter|
136
+ a[filter[:label]] = project.labels(filter[:label]) unless a.key?(filter[:label])
137
+ end
138
+ a
139
+ end
140
+ end
141
+
142
+ def self.create_lookups_cache(small_labels)
143
+ small_labels.reduce({}) do |a, e|
144
+ lookup = e.values(:limit => 1_000_000).reduce({}) do |a1, e1|
145
+ a1[e1[:value]] = e1[:uri]
146
+ a1
147
+ end
148
+ a[e.uri] = lookup
149
+ a
150
+ end
151
+ end
152
+
153
+ def self.create_attrs_cache(filters, options = {})
154
+ project = options[:project]
155
+
156
+ labels = filters.flat_map do |f|
157
+ f[:filters]
158
+ end
159
+
160
+ over_cache = labels.reduce({}) do |a, e|
161
+ a[e[:over]] = e[:over]
162
+ a
163
+ end
164
+ to_cache = labels.reduce({}) do |a, e|
165
+ a[e[:to]] = e[:to]
166
+ a
167
+ end
168
+ cache = over_cache.merge(to_cache)
169
+ attr_cache = {}
170
+ cache.each_pair do |k, v|
171
+ begin
172
+ attr_cache[k] = project.attributes(v)
173
+ rescue
174
+ nil
175
+ end
176
+ end
177
+ attr_cache
178
+ end
179
+
180
+ # Walks over provided labels and picks those that have fewer than certain amount of values
181
+ # This tries to balance for speed when working with small datasets (like users)
182
+ # so it precaches the values and still be able to function for larger ones even
183
+ # though that would mean tons of requests
184
+ def self.get_small_labels(labels_cache)
185
+ labels_cache.values.select { |label| label.values_count < 100_000 }
186
+ end
187
+
188
+ # Creates a MAQL expression(s) based on the filter defintion.
189
+ # Takes the filter definition looks up any necessary values and provides API executable MAQL
190
+ def self.create_expression(filter, labels_cache, lookups_cache, attr_cache, options = {})
191
+ errors = []
192
+ values = filter[:values]
193
+ label = labels_cache[filter[:label]]
194
+ element_uris = values.map do |v|
195
+ begin
196
+ if lookups_cache.key?(label.uri)
197
+ if lookups_cache[label.uri].key?(v)
198
+ lookups_cache[label.uri][v]
199
+ else
200
+ fail
201
+ end
202
+ else
203
+ label.find_value_uri(v)
204
+ end
205
+ rescue
206
+ errors << [label.title, v]
207
+ nil
208
+ end
209
+ end
210
+ expression = if element_uris.compact.empty? && options[:restrict_if_missing_all_values] && options[:type] == :muf
211
+ '1 <> 1'
212
+ elsif element_uris.compact.empty? && options[:restrict_if_missing_all_values] && options[:type] == :variable
213
+ nil
214
+ elsif element_uris.compact.empty?
215
+ 'TRUE'
216
+ elsif filter[:over] && filter[:to]
217
+ over = attr_cache[filter[:over]]
218
+ to = attr_cache[filter[:to]]
219
+ "([#{label.attribute_uri}] IN (#{ element_uris.compact.sort.map { |e| '[' + e + ']' }.join(', ') })) OVER [#{over && over.uri}] TO [#{to && to.uri}]"
220
+ else
221
+ "[#{label.attribute_uri}] IN (#{ element_uris.compact.sort.map { |e| '[' + e + ']' }.join(', ') })"
222
+ end
223
+ [expression, errors]
224
+ end
225
+
226
+ # Encapuslates the creation of filter
227
+ def self.create_user_filter(expression, related)
228
+ {
229
+ 'related' => related,
230
+ 'level' => :user,
231
+ 'expression' => expression,
232
+ 'type' => :filter
233
+ }
234
+ end
235
+
236
+ # Resolves and creates maql statements from filter definitions.
237
+ # This method does not perform any modifications on API but
238
+ # collects all the information that is needed to do so.
239
+ # Method collects all info from the user and current state in project and compares.
240
+ # Returns suggestion of what should be deleted and what should be created
241
+ # If there is some discrepancies in the data (missing values, nonexistent users) it
242
+ # finishes and collects all the errors at once
243
+ #
244
+ # @param filters [Array<Hash>] Filters definition
245
+ # @return [Array] first is list of MAQL statements
246
+ def self.maqlify_filters(filters, options = {})
247
+ # project = options[:project]
248
+ users_cache = options[:users_cache] # || create_cache(project.users, :login)
249
+ labels_cache = create_label_cache(filters, options)
250
+ small_labels = get_small_labels(labels_cache)
251
+ lookups_cache = create_lookups_cache(small_labels)
252
+ attrs_cache = create_attrs_cache(filters, options)
253
+
254
+ errors = []
255
+ results = filters.pmapcat do |filter|
256
+ login = filter[:login]
257
+ filter[:filters].pmapcat do |f|
258
+ expression, error = create_expression(f, labels_cache, lookups_cache, attrs_cache, options)
259
+ errors << error unless error.empty?
260
+ profiles_uri = (users_cache[login] && users_cache[login].uri)
261
+ if profiles_uri && expression
262
+ [create_user_filter(expression, profiles_uri)]
263
+ else
264
+ []
265
+ end
266
+ end
267
+ end
268
+ [results, errors]
269
+ end
270
+
125
271
  def self.resolve_user_filter(user = [], project = [])
126
272
  user ||= []
127
273
  project ||= []
@@ -150,6 +296,83 @@ module GoodData
150
296
  [to_create, to_delete]
151
297
  end
152
298
 
299
+ # Executes the update for variables. It resolves what is new and needed to update.
300
+ # @param filters [Array<Hash>] Filter Definitions
301
+ # @param filters [Variable] Variable instance to be updated
302
+ # @param options [Hash]
303
+ # @option options [Boolean] :dry_run If dry run is true. No changes to he proejct are made but list of changes is provided
304
+ # @return [Array] list of filters that needs to be created and deleted
305
+ def self.execute_variables(filters, var, options = {})
306
+ client = options[:client]
307
+ project = options[:project]
308
+ dry_run = options[:dry_run]
309
+ to_create, to_delete = execute(filters, var.user_values, VariableUserFilter, options.merge(type: :variable))
310
+ return [to_create, to_delete] if dry_run
311
+
312
+ # TODO: get values that are about to be deleted and created and update them.
313
+ # This will make sure there is no downitme in filter existence
314
+ unless options[:do_not_touch_filters_that_are_not_mentioned]
315
+ to_delete.each { |_, group| group.each(&:delete) }
316
+ end
317
+ data = to_create.values.flatten.map(&:to_hash).map { |var_val| var_val.merge(prompt: var.uri) }
318
+ data.each_slice(200) do |slice|
319
+ client.post("/gdc/md/#{project.obj_id}/variables/user", :variables => slice)
320
+ end
321
+ [to_create, to_delete]
322
+ end
323
+
324
+ def self.execute_mufs(filters, options = {})
325
+ client = options[:client]
326
+ project = options[:project]
327
+
328
+ dry_run = options[:dry_run]
329
+ to_create, to_delete = execute(filters, project.data_permissions, MandatoryUserFilter, options.merge(type: :muf))
330
+ GoodData.logger.warn("Data permissions computed: #{to_create.count} to create and #{to_delete.count} to delete")
331
+ return [to_create, to_delete] if dry_run
332
+
333
+ to_create.each_slice(100).flat_map do |batch|
334
+ batch.peach do |related_uri, group|
335
+ group.each(&:save)
336
+
337
+ res = client.get("/gdc/md/#{project.pid}/userfilters?users=#{related_uri}")
338
+ items = res['userFilters']['items'].empty? ? [] : res['userFilters']['items'].first['userFilters']
339
+
340
+ payload = {
341
+ 'userFilters' => {
342
+ 'items' => [{
343
+ 'user' => related_uri,
344
+ 'userFilters' => items.concat(group.map(&:uri))
345
+ }]
346
+ }
347
+ }
348
+ client.post("/gdc/md/#{project.pid}/userfilters", payload)
349
+ end
350
+ end
351
+ unless options[:do_not_touch_filters_that_are_not_mentioned]
352
+ to_delete.each_slice(100).flat_map do |batch|
353
+ batch.peach do |related_uri, group|
354
+ if related_uri
355
+ res = client.get("/gdc/md/#{project.pid}/userfilters?users=#{related_uri}")
356
+ items = res['userFilters']['items'].empty? ? [] : res['userFilters']['items'].first['userFilters']
357
+ payload = {
358
+ 'userFilters' => {
359
+ 'items' => [
360
+ {
361
+ 'user' => related_uri,
362
+ 'userFilters' => items - group.map(&:uri)
363
+ }
364
+ ]
365
+ }
366
+ }
367
+ client.post("/gdc/md/#{project.pid}/userfilters", payload)
368
+ end
369
+ group.peach(&:delete)
370
+ end
371
+ end
372
+ end
373
+ [to_create, to_delete]
374
+ end
375
+
153
376
  private
154
377
 
155
378
  # Reads values from File/Array. Abstracts away the fact if it is column based,
@@ -175,5 +398,70 @@ module GoodData
175
398
  end
176
399
  memo
177
400
  end
401
+
402
+ # Executes the procedure necessary for loading user filters. This method has what
403
+ # is common for both implementations. Funcion
404
+ # * makes sure that filters are in normalized form.
405
+ # * verifies that users are in the project (and domain)
406
+ # * creates maql expressions of the filters provided
407
+ # * resolves the filters against current values in the project
408
+ # @param user_filters [Array] Filters that user is trying to set up
409
+ # @param project_filters [Array] List of filters currently in the project
410
+ # @param klass [Class] Class can be aither UserFilter or VariableFilter
411
+ # @param options [Hash] Filter definitions
412
+ # @return [Array<Hash>]
413
+ def self.execute(user_filters, project_filters, klass, options = {})
414
+ client = options[:client]
415
+ project = options[:project]
416
+
417
+ ignore_missing_values = options[:ignore_missing_values]
418
+ users_must_exist = options[:users_must_exist] == false ? false : true
419
+ filters = normalize_filters(user_filters)
420
+ domain = options[:domain]
421
+ users = project.users
422
+ # users = domain ? project.users : project.users
423
+ users_cache = create_cache(users, :login)
424
+ missing_users = get_missing_users(filters, options.merge(users_cache: users_cache))
425
+ user_filters, errors = if missing_users.empty?
426
+ verify_existing_users(filters, project: project, users_must_exist: users_must_exist, users_cache: users_cache)
427
+ maqlify_filters(filters, options.merge(users_cache: users_cache, users_must_exist: users_must_exist))
428
+ elsif missing_users.count < 100
429
+ verify_existing_users(filters, project: project, users_must_exist: users_must_exist, users_cache: users_cache, domain: domain)
430
+ maqlify_filters(filters, options.merge(users_cache: users_cache, users_must_exist: users_must_exist, domain: domain))
431
+ else
432
+ users += domain.users
433
+ users_cache = create_cache(users, :login)
434
+ verify_existing_users(filters, project: project, users_must_exist: users_must_exist, users_cache: users_cache, domain: domain)
435
+ maqlify_filters(filters, options.merge(users_cache: users_cache, users_must_exist: users_must_exist, domain: domain))
436
+ end
437
+
438
+ fail "Validation failed #{errors}" if !ignore_missing_values && !errors.empty?
439
+
440
+ filters = user_filters.map { |data| client.create(klass, data, project: project) }
441
+ resolve_user_filters(filters, project_filters)
442
+ end
443
+
444
+ # Gets definition of filters from user. They might either come in the full definition
445
+ # as hash or a simplified version. The simplified version do not cover all the possible
446
+ # features but it is much simpler to remember and suitable for quick hacking around
447
+ # @param filters [Array<Array | Hash>]
448
+ # @return [Array<Hash>]
449
+ def self.normalize_filters(filters)
450
+ filters.map do |filter|
451
+ if filter.is_a?(Hash)
452
+ filter
453
+ else
454
+ {
455
+ :login => filter.first,
456
+ :filters => [
457
+ {
458
+ :label => filter[1],
459
+ :values => filter[2..-1]
460
+ }
461
+ ]
462
+ }
463
+ end
464
+ end
465
+ end
178
466
  end
179
467
  end
@@ -153,6 +153,10 @@ module GoodData
153
153
  GoodData::Domain[domain_name, :client => self]
154
154
  end
155
155
 
156
+ def project_is_accessible?(id)
157
+ projects(id) && true rescue false
158
+ end
159
+
156
160
  def projects(id = :all)
157
161
  GoodData::Project[id, client: self]
158
162
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  # GoodData Module
4
4
  module GoodData
5
- VERSION = '0.6.17'
5
+ VERSION = '0.6.18'
6
6
 
7
7
  class << self
8
8
  # Version
@@ -63,7 +63,7 @@ describe GoodData::Domain do
63
63
  end
64
64
 
65
65
  it 'Accepts pagination options - limit' do
66
- users = @domain.users(limit: 10)
66
+ users = @domain.users(:all, limit: 10)
67
67
  expect(users).to be_instance_of(Array)
68
68
  users.each do |user|
69
69
  expect(user).to be_an_instance_of(GoodData::Profile)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gooddata
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.17
4
+ version: 0.6.18
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pavel Kolesnikov
@@ -759,8 +759,6 @@ files:
759
759
  - lib/gooddata/models/user_filters/mandatory_user_filter.rb
760
760
  - lib/gooddata/models/user_filters/user_filter.rb
761
761
  - lib/gooddata/models/user_filters/user_filter_builder.rb
762
- - lib/gooddata/models/user_filters/user_filter_builder_create.rb
763
- - lib/gooddata/models/user_filters/user_filter_builder_execute.rb
764
762
  - lib/gooddata/models/user_filters/user_filters.rb
765
763
  - lib/gooddata/models/user_filters/variable_user_filter.rb
766
764
  - lib/gooddata/rest/README.md
@@ -1,115 +0,0 @@
1
- # encoding: UTF-8
2
-
3
- module GoodData
4
- module UserFilterBuilder
5
- def self.create_label_cache(result, options = {})
6
- project = options[:project]
7
-
8
- result.reduce({}) do |a, e|
9
- e[:filters].map do |filter|
10
- a[filter[:label]] = project.labels(filter[:label]) unless a.key?(filter[:label])
11
- end
12
- a
13
- end
14
- end
15
-
16
- def self.create_lookups_cache(small_labels)
17
- small_labels.reduce({}) do |a, e|
18
- lookup = e.values(:limit => 1_000_000).reduce({}) do |a1, e1|
19
- a1[e1[:value]] = e1[:uri]
20
- a1
21
- end
22
- a[e.uri] = lookup
23
- a
24
- end
25
- end
26
-
27
- # Walks over provided labels and picks those that have fewer than certain amount of values
28
- # This tries to balance for speed when working with small datasets (like users)
29
- # so it precaches the values and still be able to function for larger ones even
30
- # though that would mean tons of requests
31
- def self.get_small_labels(labels_cache)
32
- labels_cache.values.select { |label| label.values_count < 100_000 }
33
- end
34
-
35
- # Creates a MAQL expression(s) based on the filter defintion.
36
- # Takes the filter definition looks up any necessary values and provides API executable MAQL
37
- def self.create_expression(filter, labels_cache, lookups_cache, options = {})
38
- errors = []
39
- values = filter[:values]
40
- label = labels_cache[filter[:label]]
41
- element_uris = values.map do |v|
42
- begin
43
- if lookups_cache.key?(label.uri)
44
- if lookups_cache[label.uri].key?(v)
45
- lookups_cache[label.uri][v]
46
- else
47
- fail
48
- end
49
- else
50
- label.find_value_uri(v)
51
- end
52
- rescue
53
- errors << [label.title, v]
54
- nil
55
- end
56
- end
57
- expression = if element_uris.compact.empty? && options[:restrict_if_missing_all_values] && options[:type] == :muf
58
- '1 <> 1'
59
- elsif element_uris.compact.empty? && options[:restrict_if_missing_all_values] && options[:type] == :variable
60
- nil
61
- elsif element_uris.compact.empty?
62
- 'TRUE'
63
- elsif filter[:over] && filter[:to]
64
- "([#{label.attribute_uri}] IN (#{ element_uris.compact.sort.map { |e| '[' + e + ']' }.join(', ') })) OVER [#{filter[:over]}] TO [#{filter[:to]}]"
65
- else
66
- "[#{label.attribute_uri}] IN (#{ element_uris.compact.sort.map { |e| '[' + e + ']' }.join(', ') })"
67
- end
68
- [expression, errors]
69
- end
70
-
71
- # Encapuslates the creation of filter
72
- def self.create_user_filter(expression, related)
73
- {
74
- 'related' => related,
75
- 'level' => :user,
76
- 'expression' => expression,
77
- 'type' => :filter
78
- }
79
- end
80
-
81
- # Resolves and creates maql statements from filter definitions.
82
- # This method does not perform any modifications on API but
83
- # collects all the information that is needed to do so.
84
- # Method collects all info from the user and current state in project and compares.
85
- # Returns suggestion of what should be deleted and what should be created
86
- # If there is some discrepancies in the data (missing values, nonexistent users) it
87
- # finishes and collects all the errors at once
88
- #
89
- # @param filters [Array<Hash>] Filters definition
90
- # @return [Array] first is list of MAQL statements
91
- def self.maqlify_filters(filters, options = {})
92
- project = options[:project]
93
- users_cache = options[:users_cache] || create_cache(project.users, :login)
94
- labels_cache = create_label_cache(filters, options)
95
- small_labels = get_small_labels(labels_cache)
96
- lookups_cache = create_lookups_cache(small_labels)
97
-
98
- errors = []
99
- results = filters.mapcat do |filter|
100
- login = filter[:login]
101
- filter[:filters].mapcat do |f|
102
- expression, error = create_expression(f, labels_cache, lookups_cache, options)
103
- errors << error unless error.empty?
104
- profiles_uri = (users_cache[login] && users_cache[login].uri)
105
- if profiles_uri && expression
106
- [create_user_filter(expression, profiles_uri)]
107
- else
108
- []
109
- end
110
- end
111
- end
112
- [results, errors]
113
- end
114
- end
115
- end
@@ -1,133 +0,0 @@
1
- # encoding: UTF-8
2
-
3
- module GoodData
4
- module UserFilterBuilder
5
- # Executes the update for variables. It resolves what is new and needed to update.
6
- # @param filters [Array<Hash>] Filter Definitions
7
- # @param filters [Variable] Variable instance to be updated
8
- # @param options [Hash]
9
- # @option options [Boolean] :dry_run If dry run is true. No changes to he proejct are made but list of changes is provided
10
- # @return [Array] list of filters that needs to be created and deleted
11
- def self.execute_variables(filters, var, options = {})
12
- client = options[:client]
13
- project = options[:project]
14
- dry_run = options[:dry_run]
15
- to_create, to_delete = execute(filters, var.user_values, VariableUserFilter, options.merge(type: :variable))
16
- return [to_create, to_delete] if dry_run
17
-
18
- # TODO: get values that are about to be deleted and created and update them.
19
- # This will make sure there is no downitme in filter existence
20
- unless options[:do_not_touch_filters_that_are_not_mentioned]
21
- to_delete.each { |_, group| group.each(&:delete) }
22
- end
23
- data = to_create.values.flatten.map(&:to_hash).map { |var_val| var_val.merge(prompt: var.uri) }
24
- data.each_slice(200) do |slice|
25
- client.post("/gdc/md/#{project.obj_id}/variables/user", :variables => slice)
26
- end
27
- [to_create, to_delete]
28
- end
29
-
30
- def self.execute_mufs(filters, options = {})
31
- client = options[:client]
32
- project = options[:project]
33
-
34
- dry_run = options[:dry_run]
35
- to_create, to_delete = execute(filters, project.data_permissions, MandatoryUserFilter, options.merge(type: :muf))
36
- return [to_create, to_delete] if dry_run
37
-
38
- to_create.peach do |related_uri, group|
39
- group.each(&:save)
40
-
41
- res = client.get("/gdc/md/#{project.pid}/userfilters?users=#{related_uri}")
42
- items = res['userFilters']['items'].empty? ? [] : res['userFilters']['items'].first['userFilters']
43
-
44
- payload = {
45
- 'userFilters' => {
46
- 'items' => [{
47
- 'user' => related_uri,
48
- 'userFilters' => items.concat(group.map(&:uri))
49
- }]
50
- }
51
- }
52
- client.post("/gdc/md/#{project.pid}/userfilters", payload)
53
- end
54
- unless options[:do_not_touch_filters_that_are_not_mentioned]
55
- to_delete.peach do |related_uri, group|
56
- if related_uri
57
- res = client.get("/gdc/md/#{project.pid}/userfilters?users=#{related_uri}")
58
- items = res['userFilters']['items'].empty? ? [] : res['userFilters']['items'].first['userFilters']
59
- payload = {
60
- 'userFilters' => {
61
- 'items' => [
62
- {
63
- 'user' => related_uri,
64
- 'userFilters' => items - group.map(&:uri)
65
- }
66
- ]
67
- }
68
- }
69
- client.post("/gdc/md/#{project.pid}/userfilters", payload)
70
- end
71
- group.each(&:delete)
72
- end
73
- end
74
- [to_create, to_delete]
75
- end
76
-
77
- private
78
-
79
- # Executes the procedure necessary for loading user filters. This method has what
80
- # is common for both implementations. Funcion
81
- # * makes sure that filters are in normalized form.
82
- # * verifies that users are in the project (and domain)
83
- # * creates maql expressions of the filters provided
84
- # * resolves the filters against current values in the project
85
- # @param user_filters [Array] Filters that user is trying to set up
86
- # @param project_filters [Array] List of filters currently in the project
87
- # @param klass [Class] Class can be aither UserFilter or VariableFilter
88
- # @param options [Hash] Filter definitions
89
- # @return [Array<Hash>]
90
- def self.execute(user_filters, project_filters, klass, options = {})
91
- client = options[:client]
92
- project = options[:project]
93
-
94
- ignore_missing_values = options[:ignore_missing_values]
95
- users_must_exist = options[:users_must_exist] == false ? false : true
96
- filters = normalize_filters(user_filters)
97
- domain = options[:domain]
98
- users = domain ? project.users + domain.users : project.users
99
- users_cache = create_cache(users, :login)
100
- verify_existing_users(filters, options.merge(users_must_exist: users_must_exist, users_cache: users_cache))
101
- user_filters, errors = maqlify_filters(filters, options.merge(users_cache: users_cache, users_must_exist: users_must_exist))
102
- fail "Validation failed #{errors}" if !ignore_missing_values && !errors.empty?
103
-
104
- filters = user_filters.map { |data| client.create(klass, data, project: project) }
105
- resolve_user_filters(filters, project_filters)
106
- end
107
-
108
- private
109
-
110
- # Gets definition of filters from user. They might either come in the full definition
111
- # as hash or a simplified version. The simplified version do not cover all the possible
112
- # features but it is much simpler to remember and suitable for quick hacking around
113
- # @param filters [Array<Array | Hash>]
114
- # @return [Array<Hash>]
115
- def self.normalize_filters(filters)
116
- filters.map do |filter|
117
- if filter.is_a?(Hash)
118
- filter
119
- else
120
- {
121
- :login => filter.first,
122
- :filters => [
123
- {
124
- :label => filter[1],
125
- :values => filter[2..-1]
126
- }
127
- ]
128
- }
129
- end
130
- end
131
- end
132
- end
133
- end