gooddata 0.6.15 → 0.6.16
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/gooddata/bricks/middleware/fs_download_middleware.rb +42 -0
- data/lib/gooddata/bricks/middleware/fs_upload_middleware.rb +8 -12
- data/lib/gooddata/bricks/middleware/gooddata_middleware.rb +10 -13
- data/lib/gooddata/bricks/middleware/logger_middleware.rb +7 -2
- data/lib/gooddata/core/rest.rb +7 -8
- data/lib/gooddata/helpers/data_helper.rb +81 -0
- data/lib/gooddata/helpers/global_helpers.rb +1 -1
- data/lib/gooddata/models/domain.rb +22 -48
- data/lib/gooddata/models/membership.rb +2 -0
- data/lib/gooddata/models/metadata/attribute.rb +1 -1
- data/lib/gooddata/models/metadata/dataset.rb +1 -1
- data/lib/gooddata/models/process.rb +3 -2
- data/lib/gooddata/models/profile.rb +8 -4
- data/lib/gooddata/models/project.rb +128 -80
- data/lib/gooddata/models/project_role.rb +26 -0
- data/lib/gooddata/models/user_filters/user_filter_builder.rb +10 -9
- data/lib/gooddata/rest/client.rb +2 -4
- data/lib/gooddata/rest/connection.rb +3 -2
- data/lib/gooddata/version.rb +1 -1
- data/spec/helpers/project_helper.rb +1 -1
- data/spec/integration/project_spec.rb +10 -5
- data/spec/integration/rest_spec.rb +4 -4
- data/spec/integration/user_filters_spec.rb +2 -1
- data/spec/unit/models/metric_spec.rb +9 -9
- data/spec/unit/models/project_spec.rb +0 -272
- data/spec/unit/models/unit_project.rb +122 -0
- metadata +4 -1
@@ -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
|
-
|
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
|
-
|
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
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
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
|
-
|
928
|
-
|
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]
|
964
|
+
role: x[:role]
|
936
965
|
}
|
937
966
|
end
|
938
|
-
|
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
|
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
|
950
|
-
|
951
|
-
|
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
|
-
|
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 =
|
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
|
-
|
971
|
-
|
972
|
-
|
973
|
-
|
974
|
-
|
975
|
-
|
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
|
-
|
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
|
-
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
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 = {
|
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 = {
|
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 = {
|
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 = {
|
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 = {
|
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.
|
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.
|
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 = {
|
369
|
+
def self.execute(user_filters, project_filters, klass, options = {})
|
369
370
|
client = options[:client]
|
370
371
|
project = options[:project]
|
371
372
|
|