gooddata 0.6.17 → 0.6.18

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