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.
- 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
|
|