gooddata 0.6.15 → 0.6.16

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