gooddata 0.6.16 → 0.6.17
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/.rubocop.yml +4 -1
- data/lib/gooddata/cli/commands/project_cmd.rb +1 -1
- data/lib/gooddata/core/logging.rb +15 -5
- data/lib/gooddata/core/rest.rb +4 -28
- data/lib/gooddata/helpers/global_helpers.rb +14 -138
- data/lib/gooddata/helpers/global_helpers_params.rb +145 -0
- data/lib/gooddata/mixins/md_object_indexer.rb +2 -2
- data/lib/gooddata/models/domain.rb +1 -1
- data/lib/gooddata/models/execution.rb +29 -1
- data/lib/gooddata/models/from_wire.rb +6 -0
- data/lib/gooddata/models/from_wire_parse.rb +125 -0
- data/lib/gooddata/models/metadata/attribute.rb +1 -1
- data/lib/gooddata/models/metadata/label.rb +11 -10
- data/lib/gooddata/models/model.rb +4 -0
- data/lib/gooddata/models/profile.rb +12 -2
- data/lib/gooddata/models/project.rb +6 -3
- data/lib/gooddata/models/project_blueprint.rb +4 -4
- data/lib/gooddata/models/project_creator.rb +8 -10
- data/lib/gooddata/models/report_data_result.rb +4 -2
- data/lib/gooddata/models/schedule.rb +121 -66
- data/lib/gooddata/models/to_wire.rb +12 -3
- data/lib/gooddata/models/user_filters/user_filter_builder.rb +3 -234
- data/lib/gooddata/models/user_filters/user_filter_builder_create.rb +115 -0
- data/lib/gooddata/models/user_filters/user_filter_builder_execute.rb +133 -0
- data/lib/gooddata/rest/client.rb +27 -13
- data/lib/gooddata/rest/connection.rb +102 -23
- data/lib/gooddata/version.rb +1 -1
- data/spec/data/gd_gse_data_blueprint.json +1 -0
- data/spec/data/test_project_model_spec.json +5 -2
- data/spec/data/wire_models/model_view.json +3 -0
- data/spec/data/wire_test_project.json +8 -1
- data/spec/integration/full_project_spec.rb +1 -1
- data/spec/unit/core/connection_spec.rb +16 -0
- data/spec/unit/core/logging_spec.rb +54 -6
- data/spec/unit/models/domain_spec.rb +10 -4
- data/spec/unit/models/execution_spec.rb +102 -0
- data/spec/unit/models/from_wire_spec.rb +11 -2
- data/spec/unit/models/model_spec.rb +2 -2
- data/spec/unit/models/project_blueprint_spec.rb +1 -1
- data/spec/unit/models/schedule_spec.rb +34 -24
- data/spec/unit/models/to_wire_spec.rb +9 -1
- metadata +8 -3
@@ -18,7 +18,8 @@ module GoodData
|
|
18
18
|
{
|
19
19
|
attribute: {
|
20
20
|
identifier: GoodData::Model.identifier_for(dataset, type: :anchor_no_label),
|
21
|
-
title: "Records of #{ GoodData::Model.title(dataset) }"
|
21
|
+
title: "Records of #{ GoodData::Model.title(dataset) }",
|
22
|
+
folder: dataset[:folder] || GoodData::Model.title(dataset)
|
22
23
|
}
|
23
24
|
}
|
24
25
|
end
|
@@ -43,10 +44,11 @@ module GoodData
|
|
43
44
|
def self.attribute_to_wire(dataset, attribute)
|
44
45
|
default_label = DatasetBlueprint.default_label_for_attribute(dataset, attribute)
|
45
46
|
label = default_label[:type].to_sym == :label ? default_label : default_label.merge(type: :primary_label)
|
46
|
-
{
|
47
|
+
payload = {
|
47
48
|
attribute: {
|
48
49
|
identifier: GoodData::Model.identifier_for(dataset, attribute),
|
49
50
|
title: GoodData::Model.title(attribute),
|
51
|
+
folder: attribute[:folder] || dataset[:folder] || GoodData::Model.title(dataset),
|
50
52
|
labels: ([attribute.merge(type: :primary_label)] + DatasetBlueprint.labels_for_attribute(dataset, attribute)).map do |l|
|
51
53
|
{
|
52
54
|
label: {
|
@@ -60,6 +62,9 @@ module GoodData
|
|
60
62
|
defaultLabel: GoodData::Model.identifier_for(dataset, label, attribute)
|
61
63
|
}
|
62
64
|
}
|
65
|
+
payload.tap do |p|
|
66
|
+
p[:attribute][:description] = GoodData::Model.description(attribute) if GoodData::Model.description(attribute)
|
67
|
+
end
|
63
68
|
end
|
64
69
|
|
65
70
|
# Converts dataset to wire format.
|
@@ -100,13 +105,17 @@ module GoodData
|
|
100
105
|
# @param fact [Hash] Fact blueprint
|
101
106
|
# @return [Hash] Manifest for a particular reference
|
102
107
|
def self.fact_to_wire(dataset, fact)
|
103
|
-
{
|
108
|
+
payload = {
|
104
109
|
fact: {
|
105
110
|
identifier: GoodData::Model.identifier_for(dataset, fact),
|
106
111
|
title: GoodData::Model.title(fact),
|
112
|
+
folder: fact[:folder] || dataset[:folder] || GoodData::Model.title(dataset),
|
107
113
|
dataType: fact[:gd_data_type] || DEFAULT_FACT_DATATYPE
|
108
114
|
}
|
109
115
|
}
|
116
|
+
payload.tap do |p|
|
117
|
+
p[:fact][:description] = GoodData::Model.description(fact) if GoodData::Model.description(fact)
|
118
|
+
end
|
110
119
|
end
|
111
120
|
|
112
121
|
# Converts references to wire format.
|
@@ -1,5 +1,8 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
|
+
require_relative 'user_filter_builder_create'
|
4
|
+
require_relative 'user_filter_builder_execute'
|
5
|
+
|
3
6
|
module GoodData
|
4
7
|
module UserFilterBuilder
|
5
8
|
# Main Entry function. Gets values and processes them to get filters
|
@@ -119,116 +122,6 @@ module GoodData
|
|
119
122
|
end
|
120
123
|
end
|
121
124
|
|
122
|
-
def self.create_label_cache(result, options = {})
|
123
|
-
project = options[:project]
|
124
|
-
|
125
|
-
result.reduce({}) do |a, e|
|
126
|
-
e[:filters].map do |filter|
|
127
|
-
a[filter[:label]] = project.labels(filter[:label]) unless a.key?(filter[:label])
|
128
|
-
end
|
129
|
-
a
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
def self.create_lookups_cache(small_labels)
|
134
|
-
small_labels.reduce({}) do |a, e|
|
135
|
-
lookup = e.values(:limit => 1_000_000).reduce({}) do |a1, e1|
|
136
|
-
a1[e1[:value]] = e1[:uri]
|
137
|
-
a1
|
138
|
-
end
|
139
|
-
a[e.uri] = lookup
|
140
|
-
a
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
# Walks over provided labels and picks those that have fewer than certain amount of values
|
145
|
-
# This tries to balance for speed when working with small datasets (like users)
|
146
|
-
# so it precaches the values and still be able to function for larger ones even
|
147
|
-
# though that would mean tons of requests
|
148
|
-
def self.get_small_labels(labels_cache)
|
149
|
-
labels_cache.values.select { |label| label.values_count < 100_000 }
|
150
|
-
end
|
151
|
-
|
152
|
-
# Creates a MAQL expression(s) based on the filter defintion.
|
153
|
-
# Takes the filter definition looks up any necessary values and provides API executable MAQL
|
154
|
-
def self.create_expression(filter, labels_cache, lookups_cache, options = {})
|
155
|
-
errors = []
|
156
|
-
values = filter[:values]
|
157
|
-
label = labels_cache[filter[:label]]
|
158
|
-
element_uris = values.map do |v|
|
159
|
-
begin
|
160
|
-
if lookups_cache.key?(label.uri)
|
161
|
-
if lookups_cache[label.uri].key?(v)
|
162
|
-
lookups_cache[label.uri][v]
|
163
|
-
else
|
164
|
-
fail
|
165
|
-
end
|
166
|
-
else
|
167
|
-
label.find_value_uri(v)
|
168
|
-
end
|
169
|
-
rescue
|
170
|
-
errors << [label.title, v]
|
171
|
-
nil
|
172
|
-
end
|
173
|
-
end
|
174
|
-
expression = if element_uris.compact.empty? && options[:restrict_if_missing_all_values] && options[:type] == :muf
|
175
|
-
'1 <> 1'
|
176
|
-
elsif element_uris.compact.empty? && options[:restrict_if_missing_all_values] && options[:type] == :variable
|
177
|
-
nil
|
178
|
-
elsif element_uris.compact.empty?
|
179
|
-
'TRUE'
|
180
|
-
elsif filter[:over] && filter[:to]
|
181
|
-
"([#{label.attribute_uri}] IN (#{ element_uris.compact.sort.map { |e| '[' + e + ']' }.join(', ') })) OVER [#{filter[:over]}] TO [#{filter[:to]}]"
|
182
|
-
else
|
183
|
-
"[#{label.attribute_uri}] IN (#{ element_uris.compact.sort.map { |e| '[' + e + ']' }.join(', ') })"
|
184
|
-
end
|
185
|
-
[expression, errors]
|
186
|
-
end
|
187
|
-
|
188
|
-
# Encapuslates the creation of filter
|
189
|
-
def self.create_user_filter(expression, related)
|
190
|
-
{
|
191
|
-
'related' => related,
|
192
|
-
'level' => :user,
|
193
|
-
'expression' => expression,
|
194
|
-
'type' => :filter
|
195
|
-
}
|
196
|
-
end
|
197
|
-
|
198
|
-
# Resolves and creates maql statements from filter definitions.
|
199
|
-
# This method does not perform any modifications on API but
|
200
|
-
# collects all the information that is needed to do so.
|
201
|
-
# Method collects all info from the user and current state in project and compares.
|
202
|
-
# Returns suggestion of what should be deleted and what should be created
|
203
|
-
# If there is some discrepancies in the data (missing values, nonexistent users) it
|
204
|
-
# finishes and collects all the errors at once
|
205
|
-
#
|
206
|
-
# @param filters [Array<Hash>] Filters definition
|
207
|
-
# @return [Array] first is list of MAQL statements
|
208
|
-
def self.maqlify_filters(filters, options = {})
|
209
|
-
project = options[:project]
|
210
|
-
users_cache = options[:users_cache] || create_cache(project.users, :login)
|
211
|
-
labels_cache = create_label_cache(filters, options)
|
212
|
-
small_labels = get_small_labels(labels_cache)
|
213
|
-
lookups_cache = create_lookups_cache(small_labels)
|
214
|
-
|
215
|
-
errors = []
|
216
|
-
results = filters.mapcat do |filter|
|
217
|
-
login = filter[:login]
|
218
|
-
filter[:filters].mapcat do |f|
|
219
|
-
expression, error = create_expression(f, labels_cache, lookups_cache, options)
|
220
|
-
errors << error unless error.empty?
|
221
|
-
profiles_uri = (users_cache[login] && users_cache[login].uri)
|
222
|
-
if profiles_uri && expression
|
223
|
-
[create_user_filter(expression, profiles_uri)]
|
224
|
-
else
|
225
|
-
[]
|
226
|
-
end
|
227
|
-
end
|
228
|
-
end
|
229
|
-
[results, errors]
|
230
|
-
end
|
231
|
-
|
232
125
|
def self.resolve_user_filter(user = [], project = [])
|
233
126
|
user ||= []
|
234
127
|
project ||= []
|
@@ -257,78 +150,6 @@ module GoodData
|
|
257
150
|
[to_create, to_delete]
|
258
151
|
end
|
259
152
|
|
260
|
-
# Executes the update for variables. It resolves what is new and needed to update.
|
261
|
-
# @param filters [Array<Hash>] Filter Definitions
|
262
|
-
# @param filters [Variable] Variable instance to be updated
|
263
|
-
# @param options [Hash]
|
264
|
-
# @option options [Boolean] :dry_run If dry run is true. No changes to he proejct are made but list of changes is provided
|
265
|
-
# @return [Array] list of filters that needs to be created and deleted
|
266
|
-
def self.execute_variables(filters, var, options = {})
|
267
|
-
client = options[:client]
|
268
|
-
project = options[:project]
|
269
|
-
dry_run = options[:dry_run]
|
270
|
-
to_create, to_delete = execute(filters, var.user_values, VariableUserFilter, options.merge(type: :variable))
|
271
|
-
return [to_create, to_delete] if dry_run
|
272
|
-
|
273
|
-
# TODO: get values that are about to be deleted and created and update them.
|
274
|
-
# This will make sure there is no downitme in filter existence
|
275
|
-
unless options[:do_not_touch_filters_that_are_not_mentioned]
|
276
|
-
to_delete.each { |_, group| group.each(&:delete) }
|
277
|
-
end
|
278
|
-
data = to_create.values.flatten.map(&:to_hash).map { |var_val| var_val.merge(prompt: var.uri) }
|
279
|
-
data.each_slice(200) do |slice|
|
280
|
-
client.post("/gdc/md/#{project.obj_id}/variables/user", :variables => slice)
|
281
|
-
end
|
282
|
-
[to_create, to_delete]
|
283
|
-
end
|
284
|
-
|
285
|
-
def self.execute_mufs(filters, options = {})
|
286
|
-
client = options[:client]
|
287
|
-
project = options[:project]
|
288
|
-
|
289
|
-
dry_run = options[:dry_run]
|
290
|
-
to_create, to_delete = execute(filters, project.data_permissions, MandatoryUserFilter, options.merge(type: :muf))
|
291
|
-
return [to_create, to_delete] if dry_run
|
292
|
-
|
293
|
-
to_create.peach do |related_uri, group|
|
294
|
-
group.each(&:save)
|
295
|
-
|
296
|
-
res = client.get("/gdc/md/#{project.pid}/userfilters?users=#{related_uri}")
|
297
|
-
items = res['userFilters']['items'].empty? ? [] : res['userFilters']['items'].first['userFilters']
|
298
|
-
|
299
|
-
payload = {
|
300
|
-
'userFilters' => {
|
301
|
-
'items' => [{
|
302
|
-
'user' => related_uri,
|
303
|
-
'userFilters' => items.concat(group.map(&:uri))
|
304
|
-
}]
|
305
|
-
}
|
306
|
-
}
|
307
|
-
client.post("/gdc/md/#{project.pid}/userfilters", payload)
|
308
|
-
end
|
309
|
-
unless options[:do_not_touch_filters_that_are_not_mentioned]
|
310
|
-
to_delete.peach do |related_uri, group|
|
311
|
-
if related_uri
|
312
|
-
res = client.get("/gdc/md/#{project.pid}/userfilters?users=#{related_uri}")
|
313
|
-
items = res['userFilters']['items'].empty? ? [] : res['userFilters']['items'].first['userFilters']
|
314
|
-
payload = {
|
315
|
-
'userFilters' => {
|
316
|
-
'items' => [
|
317
|
-
{
|
318
|
-
'user' => related_uri,
|
319
|
-
'userFilters' => items - group.map(&:uri)
|
320
|
-
}
|
321
|
-
]
|
322
|
-
}
|
323
|
-
}
|
324
|
-
client.post("/gdc/md/#{project.pid}/userfilters", payload)
|
325
|
-
end
|
326
|
-
group.each(&:delete)
|
327
|
-
end
|
328
|
-
end
|
329
|
-
[to_create, to_delete]
|
330
|
-
end
|
331
|
-
|
332
153
|
private
|
333
154
|
|
334
155
|
# Reads values from File/Array. Abstracts away the fact if it is column based,
|
@@ -354,57 +175,5 @@ module GoodData
|
|
354
175
|
end
|
355
176
|
memo
|
356
177
|
end
|
357
|
-
|
358
|
-
# Executes the procedure necessary for loading user filters. This method has what
|
359
|
-
# is common for both implementations. Funcion
|
360
|
-
# * makes sure that filters are in normalized form.
|
361
|
-
# * verifies that users are in the project (and domain)
|
362
|
-
# * creates maql expressions of the filters provided
|
363
|
-
# * resolves the filters against current values in the project
|
364
|
-
# @param user_filters [Array] Filters that user is trying to set up
|
365
|
-
# @param project_filters [Array] List of filters currently in the project
|
366
|
-
# @param klass [Class] Class can be aither UserFilter or VariableFilter
|
367
|
-
# @param options [Hash] Filter definitions
|
368
|
-
# @return [Array<Hash>]
|
369
|
-
def self.execute(user_filters, project_filters, klass, options = {})
|
370
|
-
client = options[:client]
|
371
|
-
project = options[:project]
|
372
|
-
|
373
|
-
ignore_missing_values = options[:ignore_missing_values]
|
374
|
-
users_must_exist = options[:users_must_exist] == false ? false : true
|
375
|
-
filters = normalize_filters(user_filters)
|
376
|
-
domain = options[:domain]
|
377
|
-
users = domain ? project.users + domain.users : project.users
|
378
|
-
users_cache = create_cache(users, :login)
|
379
|
-
verify_existing_users(filters, options.merge(users_must_exist: users_must_exist, users_cache: users_cache))
|
380
|
-
user_filters, errors = maqlify_filters(filters, options.merge(users_cache: users_cache, users_must_exist: users_must_exist))
|
381
|
-
fail "Validation failed #{errors}" if !ignore_missing_values && !errors.empty?
|
382
|
-
|
383
|
-
filters = user_filters.map { |data| client.create(klass, data, project: project) }
|
384
|
-
resolve_user_filters(filters, project_filters)
|
385
|
-
end
|
386
|
-
|
387
|
-
# Gets definition of filters from user. They might either come in the full definition
|
388
|
-
# as hash or a simplified version. The simplified version do not cover all the possible
|
389
|
-
# features but it is much simpler to remember and suitable for quick hacking around
|
390
|
-
# @param filters [Array<Array | Hash>]
|
391
|
-
# @return [Array<Hash>]
|
392
|
-
def self.normalize_filters(filters)
|
393
|
-
filters.map do |filter|
|
394
|
-
if filter.is_a?(Hash)
|
395
|
-
filter
|
396
|
-
else
|
397
|
-
{
|
398
|
-
:login => filter.first,
|
399
|
-
:filters => [
|
400
|
-
{
|
401
|
-
:label => filter[1],
|
402
|
-
:values => filter[2..-1]
|
403
|
-
}
|
404
|
-
]
|
405
|
-
}
|
406
|
-
end
|
407
|
-
end
|
408
|
-
end
|
409
178
|
end
|
410
179
|
end
|
@@ -0,0 +1,115 @@
|
|
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
|
@@ -0,0 +1,133 @@
|
|
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
|