gooddata 0.6.15 → 0.6.16

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.
@@ -73,14 +73,19 @@ module GoodData
73
73
  # @param attributes [Hash] Hash with initial attributes
74
74
  # @return [GoodData::Profile] New profile instance
75
75
  def create(attributes)
76
- json = EMPTY_OBJECT.dup
76
+ res = create_object(attributes)
77
+ res.save!
78
+ res
79
+ end
80
+
81
+ def create_object(attributes)
82
+ json = EMPTY_OBJECT.deep_dup
83
+ json['accountSetting']['links']['self'] = attributes[:uri] if attributes[:uri]
77
84
  res = client.create(GoodData::Profile, json)
78
85
 
79
86
  attributes.each do |k, v|
80
87
  res.send("#{k}=", v) if ASSIGNABLE_MEMBERS.include? k
81
88
  end
82
-
83
- res.save!
84
89
  res
85
90
  end
86
91
 
@@ -347,7 +352,6 @@ module GoodData
347
352
  # @return [String] Resource URI
348
353
  def uri
349
354
  GoodData::Helpers.get_path(@json, %w(accountSetting links self))
350
- # @json['accountSetting']['links']['self']
351
355
  end
352
356
 
353
357
  def data
@@ -25,6 +25,18 @@ module GoodData
25
25
  SLIS_PATH = '/ldm/singleloadinterface'
26
26
  DEFAULT_INVITE_MESSAGE = 'Join us!'
27
27
 
28
+ EMPTY_OBJECT = {
29
+ 'project' => {
30
+ 'meta' => {
31
+ 'summary' => 'No summary'
32
+ },
33
+ 'content' => {
34
+ 'guidedNavigation' => 1,
35
+ 'driver' => 'Pg'
36
+ }
37
+ }
38
+ }
39
+
28
40
  attr_accessor :connection, :json
29
41
 
30
42
  alias_method :to_json, :json
@@ -71,6 +83,19 @@ module GoodData
71
83
  end
72
84
  end
73
85
 
86
+ def create_object(data = {})
87
+ c = client(data)
88
+ new_data = EMPTY_OBJECT.deep_dup.tap do |d|
89
+ d['project']['meta']['title'] = data[:title]
90
+ d['project']['meta']['summary'] = data[:summary] if data[:summary]
91
+ d['project']['meta']['projectTemplate'] = data[:template] if data[:template]
92
+ d['project']['content']['guidedNavigation'] = data[:guided_navigation] if data[:guided_navigation]
93
+ d['project']['content']['authorizationToken'] = data[:auth_token] if data[:auth_token]
94
+ d['project']['content']['driver'] = data[:driver] if data[:driver]
95
+ end
96
+ c.create(Project, new_data)
97
+ end
98
+
74
99
  # Create a project from a given attributes
75
100
  # Expected keys:
76
101
  # - :title (mandatory)
@@ -86,23 +111,7 @@ module GoodData
86
111
  auth_token = opts[:auth_token]
87
112
  fail ArgumentError, 'You have to provide your token for creating projects as :auth_token parameter' if auth_token.nil? || auth_token.empty?
88
113
 
89
- json = {
90
- 'project' =>
91
- {
92
- 'meta' => {
93
- 'title' => opts[:title],
94
- 'summary' => opts[:summary] || 'No summary'
95
- },
96
- 'content' => {
97
- 'guidedNavigation' => opts[:guided_navigation] || 1,
98
- 'authorizationToken' => auth_token,
99
- 'driver' => opts[:driver] || 'Pg'
100
- }
101
- }
102
- }
103
-
104
- json['project']['meta']['projectTemplate'] = opts[:template] if opts[:template] && !opts[:template].empty?
105
- project = c.create(Project, json)
114
+ project = create_object(opts)
106
115
  project.save
107
116
  # until it is enabled or deleted, recur. This should still end if there is a exception thrown out from RESTClient. This sometimes happens from WebApp when request is too long
108
117
  while project.state.to_s != 'enabled'
@@ -421,7 +430,7 @@ module GoodData
421
430
 
422
431
  # Get WebDav directory for project data
423
432
  # @return [String]
424
- def get_project_webdav_path(_file)
433
+ def project_webdav_path
425
434
  u = URI(links['uploads'])
426
435
  URI.join(u.to_s.chomp(u.path.to_s), '/project-uploads/', "#{pid}/")
427
436
  end
@@ -503,11 +512,19 @@ module GoodData
503
512
 
504
513
  # Get WebDav directory for user data
505
514
  # @return [String]
506
- def get_user_webdav_path(_file)
515
+ def user_webdav_path
507
516
  u = URI(links['uploads'])
508
517
  URI.join(u.to_s.chomp(u.path.to_s), '/uploads/')
509
518
  end
510
519
 
520
+ def upload_file(file)
521
+ GoodData.upload_to_project_webdav(file, project: self)
522
+ end
523
+
524
+ def download_file(file, where)
525
+ GoodData.download_from_project_webdav(file, where, project: self)
526
+ end
527
+
511
528
  # Gets user by its email, full_name, login or uri
512
529
  alias_method :member, :get_user
513
530
 
@@ -900,93 +917,109 @@ module GoodData
900
917
  alias_method :members, :users
901
918
 
902
919
  def whitelist_users(new_users, users_list, whitelist)
903
- # whitelist_users
904
920
  return [new_users, users_list] unless whitelist
905
921
 
906
- whitelist_proc = proc do |user|
922
+ new_whitelist_proc = proc do |user|
907
923
  whitelist.any? { |wl| wl.is_a?(Regexp) ? user[:login] =~ wl : user[:login].include?(wl) }
908
924
  end
909
925
 
910
- [new_users.reject(&whitelist_proc), users_list.reject(&whitelist_proc)]
926
+ whitelist_proc = proc do |user|
927
+ whitelist.any? { |wl| wl.is_a?(Regexp) ? user.login =~ wl : user.login.include?(wl) }
928
+ end
929
+
930
+ [new_users.reject(&new_whitelist_proc), users_list.reject(&whitelist_proc)]
911
931
  end
912
932
 
913
933
  # Imports users
914
934
  def import_users(new_users, options = {})
915
935
  domain = options[:domain]
916
- users_list = users.map(&:to_hash)
936
+ role_list = roles
937
+ users_list = users
917
938
  new_users = new_users.map { |x| (x.is_a?(Hash) && x[:user] && x[:user].to_hash.merge(role: x[:role])) || x.to_hash }
918
939
 
919
940
  whitelisted_new_users, whitelisted_users = whitelist_users(new_users.map(&:to_hash), users_list, options[:whitelists])
920
941
 
921
- # Diff users
922
- diff = GoodData::Helpers.diff(whitelisted_users, whitelisted_new_users, key: :login)
923
- results = []
924
- # Create domain users
925
- results.concat domain.create_users(diff[:added])
942
+ # conform the role on list of new users so we can diff them with the users coming from the project
943
+ diffable_new_with_default_role = whitelisted_new_users.map do |u|
944
+ u[:role] = Array(u[:role] || u[:roles] || 'readOnlyUser')
945
+ u
946
+ end
926
947
 
927
- # Update domain users
928
- domain.create_users(diff[:changed].map { |u| u[:new_obj] })
948
+ diffable_new = diffable_new_with_default_role.map do |u|
949
+ u[:role] = u[:role].map do |r|
950
+ role = get_role(r, role_list)
951
+ role && role.uri
952
+ end
953
+ u
954
+ end
955
+
956
+ # Diff users. Only login and role is important for the diff
957
+ diff = GoodData::Helpers.diff(whitelisted_users, diffable_new, key: :login, fields: [:login, :role])
929
958
 
959
+ results = []
930
960
  # Create new users
931
- role_list = roles
932
961
  u = diff[:added].map do |x|
933
962
  {
934
963
  user: x,
935
- role: x[:role] || x[:roles]
964
+ role: x[:role]
936
965
  }
937
966
  end
938
- results.concat create_users(u, roles: role_list, domain: domain)
967
+
968
+ results.concat create_users(u, roles: role_list, domain: domain, project_users: whitelisted_users)
939
969
 
940
970
  # # Update existing users
941
971
  list = diff[:changed].map { |x| { user: x[:new_obj], role: x[:new_obj][:role] || x[:new_obj][:roles] } }
942
- results.concat set_users_roles(list, roles: role_list)
972
+ results.concat(set_users_roles(list, roles: role_list, project_users: whitelisted_users))
943
973
 
944
974
  # Remove old users
945
975
  results.concat(disable_users(diff[:removed]))
946
976
  results
947
977
  end
948
978
 
949
- def disable_users(list, options = {})
950
- project_users = options[:project_users] || users
951
- list.map { |u| get_user(u, project_users) }.pmap(&:disable)
979
+ def disable_users(list)
980
+ list = list.map(&:to_hash)
981
+ url = "#{uri}/users"
982
+ payloads = list.map do |u|
983
+ generate_user_payload(u[:uri], 'DISABLED')
984
+ end
985
+ payloads.each_slice(100).mapcat do |payload|
986
+ client.post(url, 'users' => payload)
987
+ end
952
988
  end
953
989
 
954
- # Update user
955
- #
956
- # @param user User to be updated
957
- # @param desired_roles Roles to be assigned to user
958
- # @param role_list Optional cached list of roles used for lookups
959
- def set_user_roles(login, desired_roles, options = {})
990
+ def verify_user_to_add(login, desired_roles, options = {})
991
+ user = login if login.respond_to?(:uri) && !login.uri.nil?
960
992
  role_list = options[:roles] || roles
993
+ desired_roles = Array(desired_roles)
994
+ roles = desired_roles.map do |role_name|
995
+ role = get_role(role_name, role_list)
996
+ fail ArgumentError, "Invalid role '#{role_name}' specified for user '#{user.email}'" if role.nil?
997
+ role.uri
998
+ end
999
+ return [user.uri, roles] if user
1000
+
961
1001
  domain = client.domain(options[:domain]) if options[:domain]
962
- project_users = options[:project_users] || users
963
1002
  domain_users = options[:domain_users] || (domain && domain.users)
1003
+ project_users = options[:project_users] || users
964
1004
 
965
1005
  project_user = get_user(login, project_users)
966
- domain_user = domain.get_user(login, domain_users) if domain && !project_user
1006
+ domain_user = if domain && !project_user && !user
1007
+ domain.get_user(login, domain_users) if domain && !project_user
1008
+ end
967
1009
  user = project_user || domain_user
968
1010
  fail ArgumentError, "Invalid user '#{login}' specified" unless user
1011
+ [user.uri, roles]
1012
+ end
969
1013
 
970
- desired_roles = [desired_roles] unless desired_roles.is_a? Array
971
- roles = desired_roles.map do |role_name|
972
- role = get_role(role_name, role_list)
973
- fail ArgumentError, "Invalid role '#{role_name}' specified for user '#{user.email}'" if role.nil?
974
- role.uri
975
- end
976
-
1014
+ # Update user
1015
+ #
1016
+ # @param user User to be updated
1017
+ # @param desired_roles Roles to be assigned to user
1018
+ # @param role_list Optional cached list of roles used for lookups
1019
+ def set_user_roles(login, desired_roles, options = {})
1020
+ user_uri, roles = verify_user_to_add(login, desired_roles, options)
977
1021
  url = "#{uri}/users"
978
- payload = {
979
- 'user' => {
980
- 'content' => {
981
- 'status' => 'ENABLED',
982
- 'userRoles' => roles
983
- },
984
- 'links' => {
985
- 'self' => user.uri
986
- }
987
- }
988
- }
989
-
1022
+ payload = generate_user_payload(user_uri, 'ENABLED', roles)
990
1023
  client.post(url, payload)
991
1024
  end
992
1025
 
@@ -997,29 +1030,27 @@ module GoodData
997
1030
  # @param list List of users to be updated
998
1031
  # @param role_list Optional list of cached roles to prevent unnecessary server round-trips
999
1032
  def set_users_roles(list, options = {})
1033
+ return [] if list.empty?
1000
1034
  role_list = options[:roles] || roles
1001
1035
  project_users = options[:project_users] || users
1002
1036
  domain = options[:domain] && client.domain(options[:domain])
1003
1037
  domain_users = domain.nil? ? nil : domain.users
1004
-
1005
- list.pmap do |user_hash|
1038
+ users_to_add = list.flat_map do |user_hash|
1039
+ user = user_hash[:user] || user_hash[:login]
1040
+ desired_roles = user_hash[:role] || user_hash[:roles] || 'readOnlyUser'
1006
1041
  begin
1007
- user = user_hash[:user]
1008
- desired_roles = user_hash[:role] || user_hash[:roles] || 'readOnlyUser'
1009
- result = add_user(user, desired_roles, options.merge(domain: domain,
1010
- domain_users: domain_users,
1011
- roles: role_list,
1012
- project_users: project_users))
1013
-
1014
- {
1015
- type: :role_set,
1016
- user: user,
1017
- result: result
1018
- }
1019
- rescue ArgumentError, RuntimeError => e
1020
- { type: :error, reason: e }
1042
+ login, roles = verify_user_to_add(user, desired_roles, options.merge(domain_users: domain_users, project_users: project_users, roles: role_list))
1043
+ [{ login: login, roles: roles }]
1044
+ rescue
1045
+ []
1021
1046
  end
1022
1047
  end
1048
+ payloads = users_to_add.map { |u| generate_user_payload(u[:login], 'ENABLED', u[:roles]) }
1049
+ url = "#{uri}/users"
1050
+ results = payloads.each_slice(100).map do |payload|
1051
+ client.post(url, 'users' => payload)
1052
+ end
1053
+ results.flat_map { |x| x['projectUsersUpdateResult'].flat_map { |k, v| v.map { |v_2| { status: k.to_sym, uri: v_2 } } } }
1023
1054
  end
1024
1055
 
1025
1056
  alias_method :add_users, :set_users_roles
@@ -1051,5 +1082,22 @@ module GoodData
1051
1082
  def variables(id = :all, options = { client: client, project: self })
1052
1083
  GoodData::Variable[id, options]
1053
1084
  end
1085
+
1086
+ private
1087
+
1088
+ def generate_user_payload(user_uri, status = 'ENABLED', roles_uri = nil)
1089
+ payload = {
1090
+ 'user' => {
1091
+ 'content' => {
1092
+ 'status' => status
1093
+ },
1094
+ 'links' => {
1095
+ 'self' => user_uri
1096
+ }
1097
+ }
1098
+ }
1099
+ payload['user']['content']['userRoles'] = roles_uri if roles_uri
1100
+ payload
1101
+ end
1054
1102
  end
1055
1103
  end
@@ -20,6 +20,30 @@ module GoodData
20
20
  include GoodData::Mixin::Contributor
21
21
  include GoodData::Mixin::Timestamps
22
22
 
23
+ EMPTY_OBJECT = {
24
+ 'projectRole' => {
25
+ 'permissions' => {},
26
+ 'links' => {},
27
+ 'meta' => {}
28
+ }
29
+ }
30
+
31
+ def self.create_object(data)
32
+ meta_data = {}.tap do |d|
33
+ d[:created] = data[:created] || Time.now
34
+ d[:identifier] = data[:identifier]
35
+ d[:updated] = data[:updated] || d[:created] || Time.now
36
+ d[:title] = data[:title]
37
+ d[:summary] = data[:summary]
38
+ end
39
+ new_data = EMPTY_OBJECT.deep_dup.tap do |d|
40
+ d['projectRole']['links']['self'] = data[:uri] if data[:uri]
41
+ d['projectRole']['meta'] = d['projectRole']['meta'].merge(meta_data.stringify_keys)
42
+ d['projectRole']['permissions'] = d['projectRole']['permissions'].merge((data[:permissions] || {}).stringify_keys)
43
+ end
44
+ new(new_data)
45
+ end
46
+
23
47
  def initialize(json)
24
48
  @json = json
25
49
  end
@@ -45,6 +69,8 @@ module GoodData
45
69
  #
46
70
  # @return [string] URI of this project role
47
71
  def uri
72
+ return @json['projectRole']['links']['self'] if @json['projectRole']['links']['self']
73
+ return nil unless @json['projectRole']['links']['roleUsers']
48
74
  @json['projectRole']['links']['roleUsers'].split('/')[0...-1].join('/')
49
75
  end
50
76
 
@@ -33,6 +33,7 @@ module GoodData
33
33
  def self.read_file(file, options = {})
34
34
  memo = {}
35
35
  params = row_based?(options) ? { headers: false } : { headers: true }
36
+
36
37
  CSV.foreach(file, params.merge(return_headers: false)) do |e|
37
38
  key, data = process_line(e, options)
38
39
  memo[key] = [] unless memo.key?(key)
@@ -105,7 +106,7 @@ module GoodData
105
106
  end
106
107
  end
107
108
 
108
- def self.verify_existing_users(filters, options = { project: GoodData.project, client: GoodData.connection })
109
+ def self.verify_existing_users(filters, options = {})
109
110
  project = options[:project]
110
111
 
111
112
  users_must_exist = options[:users_must_exist] == false ? false : true
@@ -118,7 +119,7 @@ module GoodData
118
119
  end
119
120
  end
120
121
 
121
- def self.create_label_cache(result, options = { project: GoodData.project, client: GoodData.connection })
122
+ def self.create_label_cache(result, options = {})
122
123
  project = options[:project]
123
124
 
124
125
  result.reduce({}) do |a, e|
@@ -166,7 +167,7 @@ module GoodData
166
167
  label.find_value_uri(v)
167
168
  end
168
169
  rescue
169
- errors << [label, v]
170
+ errors << [label.title, v]
170
171
  nil
171
172
  end
172
173
  end
@@ -204,7 +205,7 @@ module GoodData
204
205
  #
205
206
  # @param filters [Array<Hash>] Filters definition
206
207
  # @return [Array] first is list of MAQL statements
207
- def self.maqlify_filters(filters, options = { project: GoodData.project, client: GoodData.connection })
208
+ def self.maqlify_filters(filters, options = {})
208
209
  project = options[:project]
209
210
  users_cache = options[:users_cache] || create_cache(project.users, :login)
210
211
  labels_cache = create_label_cache(filters, options)
@@ -262,7 +263,7 @@ module GoodData
262
263
  # @param options [Hash]
263
264
  # @option options [Boolean] :dry_run If dry run is true. No changes to he proejct are made but list of changes is provided
264
265
  # @return [Array] list of filters that needs to be created and deleted
265
- def self.execute_variables(filters, var, options = { client: GoodData.connection, project: GoodData.project })
266
+ def self.execute_variables(filters, var, options = {})
266
267
  client = options[:client]
267
268
  project = options[:project]
268
269
  dry_run = options[:dry_run]
@@ -281,7 +282,7 @@ module GoodData
281
282
  [to_create, to_delete]
282
283
  end
283
284
 
284
- def self.execute_mufs(filters, options = { client: GoodData.connection, project: GoodData.project })
285
+ def self.execute_mufs(filters, options = {})
285
286
  client = options[:client]
286
287
  project = options[:project]
287
288
 
@@ -289,7 +290,7 @@ module GoodData
289
290
  to_create, to_delete = execute(filters, project.data_permissions, MandatoryUserFilter, options.merge(type: :muf))
290
291
  return [to_create, to_delete] if dry_run
291
292
 
292
- to_create.each_pair do |related_uri, group|
293
+ to_create.peach do |related_uri, group|
293
294
  group.each(&:save)
294
295
 
295
296
  res = client.get("/gdc/md/#{project.pid}/userfilters?users=#{related_uri}")
@@ -306,7 +307,7 @@ module GoodData
306
307
  client.post("/gdc/md/#{project.pid}/userfilters", payload)
307
308
  end
308
309
  unless options[:do_not_touch_filters_that_are_not_mentioned]
309
- to_delete.each do |related_uri, group|
310
+ to_delete.peach do |related_uri, group|
310
311
  if related_uri
311
312
  res = client.get("/gdc/md/#{project.pid}/userfilters?users=#{related_uri}")
312
313
  items = res['userFilters']['items'].empty? ? [] : res['userFilters']['items'].first['userFilters']
@@ -365,7 +366,7 @@ module GoodData
365
366
  # @param klass [Class] Class can be aither UserFilter or VariableFilter
366
367
  # @param options [Hash] Filter definitions
367
368
  # @return [Array<Hash>]
368
- def self.execute(user_filters, project_filters, klass, options = { client: GoodData.connection, project: GoodData.project })
369
+ def self.execute(user_filters, project_filters, klass, options = {})
369
370
  client = options[:client]
370
371
  project = options[:project]
371
372