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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0bbc18e2feb9dc7d8f7b2fcadfd4e26474b666d0
|
4
|
+
data.tar.gz: 0020310a69543fcea52bd5d0bee0ddbe25bae3ff
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dc66e78d316ee699b2374f1885a8cd6736addd8040931400e7fab8c287327a322828480fbd0d25a5ebaea343c8b810e926afa174b2f39239d1aec0e9f504114a
|
7
|
+
data.tar.gz: 388b87c526902279fd6c01b5739f478e3d8d4787179e3062ae495caa0809ad58d47b3edaee21230425f373d5ab056097e20214b568038da7cdd07053f981b3c8
|
data/.rubocop.yml
CHANGED
@@ -130,7 +130,7 @@ GoodData::CLI.module_eval do
|
|
130
130
|
show.action do |global_options, options, _args|
|
131
131
|
opts = options.merge(global_options)
|
132
132
|
client = GoodData.connect(opts)
|
133
|
-
spec
|
133
|
+
spec = GoodData::Command::Project.get_spec_and_project_id('.')[0]
|
134
134
|
new_project = GoodData::Command::Project.build(opts.merge(spec: spec, client: client))
|
135
135
|
puts "Project was created. New project PID is #{new_project.pid}, URI is #{new_project.uri}."
|
136
136
|
end
|
@@ -14,7 +14,7 @@ module GoodData
|
|
14
14
|
# GoodData.logging_on
|
15
15
|
#
|
16
16
|
def logging_on
|
17
|
-
|
17
|
+
@logger = default_logger if logger.is_a? NilLogger
|
18
18
|
end
|
19
19
|
|
20
20
|
# Turn logging on
|
@@ -24,15 +24,15 @@ module GoodData
|
|
24
24
|
# GoodData.logging_off
|
25
25
|
#
|
26
26
|
def logging_off
|
27
|
-
|
27
|
+
@logger = NilLogger.new
|
28
28
|
end
|
29
29
|
|
30
30
|
def logging_on?
|
31
|
-
|
31
|
+
!@logger.instance_of?(NilLogger)
|
32
32
|
end
|
33
33
|
|
34
34
|
# Returns the logger instance. The default implementation
|
35
|
-
#
|
35
|
+
# is a logger to stdout on INFO level
|
36
36
|
# For some serious logging, set the logger instance using
|
37
37
|
# the logger= method
|
38
38
|
#
|
@@ -42,7 +42,7 @@ module GoodData
|
|
42
42
|
# GoodData.logger = Logger.new(STDOUT)
|
43
43
|
#
|
44
44
|
def logger
|
45
|
-
@logger ||=
|
45
|
+
@logger ||= default_logger
|
46
46
|
end
|
47
47
|
|
48
48
|
def stats_on
|
@@ -56,5 +56,15 @@ module GoodData
|
|
56
56
|
def stats_off
|
57
57
|
@stats = false
|
58
58
|
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
# The default logger - stdout and INFO level
|
63
|
+
#
|
64
|
+
def default_logger
|
65
|
+
log = Logger.new(STDOUT)
|
66
|
+
log.level = Logger::INFO
|
67
|
+
log
|
68
|
+
end
|
59
69
|
end
|
60
70
|
end
|
data/lib/gooddata/core/rest.rb
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
3
|
module GoodData
|
4
|
-
DEFAULT_SLEEP_INTERVAL = 10
|
5
|
-
|
6
4
|
class << self
|
7
5
|
# Performs a HTTP GET request.
|
8
6
|
#
|
@@ -122,21 +120,9 @@ module GoodData
|
|
122
120
|
# @param options [Hash] Options
|
123
121
|
# @return [Hash] Result of polling
|
124
122
|
def poll_on_code(link, options = {})
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
while response.code == code
|
129
|
-
sleep sleep_interval
|
130
|
-
GoodData::Rest::Client.retryable(:tries => 3, :refresh_token => proc { connection.refresh_token }) do
|
131
|
-
sleep sleep_interval
|
132
|
-
response = GoodData.get(link, :process => false)
|
133
|
-
end
|
134
|
-
end
|
135
|
-
if options[:process] == false
|
136
|
-
response
|
137
|
-
else
|
138
|
-
GoodData.get(link)
|
139
|
-
end
|
123
|
+
client = options[:client]
|
124
|
+
fail ArgumentError, 'No :client specified' if client.nil?
|
125
|
+
client.poll_on_code(link, options)
|
140
126
|
end
|
141
127
|
|
142
128
|
# Generalizaton of poller. Since we have quite a variation of how async proceses are handled
|
@@ -151,17 +137,7 @@ module GoodData
|
|
151
137
|
def poll_on_response(link, options = {}, &bl)
|
152
138
|
client = options[:client]
|
153
139
|
fail ArgumentError, 'No :client specified' if client.nil?
|
154
|
-
|
155
|
-
sleep_interval = options[:sleep_interval] || DEFAULT_SLEEP_INTERVAL
|
156
|
-
response = get(link)
|
157
|
-
while bl.call(response)
|
158
|
-
sleep sleep_interval
|
159
|
-
GoodData::Rest::Client.retryable(:tries => 3, :refresh_token => proc { client.connection.refresh_token }) do
|
160
|
-
sleep sleep_interval
|
161
|
-
response = get(link)
|
162
|
-
end
|
163
|
-
end
|
164
|
-
response
|
140
|
+
client.poll_on_response(link, options, &bl)
|
165
141
|
end
|
166
142
|
|
167
143
|
private
|
@@ -3,147 +3,11 @@
|
|
3
3
|
require 'active_support/all'
|
4
4
|
require 'pathname'
|
5
5
|
|
6
|
+
require_relative 'global_helpers_params'
|
7
|
+
|
6
8
|
module GoodData
|
7
9
|
module Helpers
|
8
10
|
class << self
|
9
|
-
# A helper which allows you to diff two lists of objects. The objects
|
10
|
-
# can be arbitrary objects as long as they respond to to_hash because
|
11
|
-
# the diff is eventually done on hashes. It allows you to specify
|
12
|
-
# several options to allow you to limit on what the sameness test is done
|
13
|
-
#
|
14
|
-
# @param [Array<Object>] old_list List of objects that serves as a base for comparison
|
15
|
-
# @param [Array<Object>] new_list List of objects that is compared agianst the old_list
|
16
|
-
# @return [Hash] A structure that contains the result of the comparison. There are
|
17
|
-
# four keys.
|
18
|
-
# :added contains the list that are in new_list but were not in the old_list
|
19
|
-
# :added contains the list that are in old_list but were not in the new_list
|
20
|
-
# :same contains objects that are in both lists and they are the same
|
21
|
-
# :changed contains list of objects that changed along ith original, the new one
|
22
|
-
# and the list of changes
|
23
|
-
def diff(old_list, new_list, options = {})
|
24
|
-
old_list = old_list.map(&:to_hash)
|
25
|
-
new_list = new_list.map(&:to_hash)
|
26
|
-
|
27
|
-
fields = options[:fields]
|
28
|
-
lookup_key = options[:key]
|
29
|
-
|
30
|
-
old_lookup = Hash[old_list.map { |v| [v[lookup_key], v] }]
|
31
|
-
|
32
|
-
res = {
|
33
|
-
:added => [],
|
34
|
-
:removed => [],
|
35
|
-
:changed => [],
|
36
|
-
:same => []
|
37
|
-
}
|
38
|
-
|
39
|
-
new_list.each do |new_obj|
|
40
|
-
old_obj = old_lookup[new_obj[lookup_key]]
|
41
|
-
if old_obj.nil?
|
42
|
-
res[:added] << new_obj
|
43
|
-
next
|
44
|
-
end
|
45
|
-
|
46
|
-
if fields
|
47
|
-
sliced_old_obj = old_obj.slice(*fields)
|
48
|
-
sliced_new_obj = new_obj.slice(*fields)
|
49
|
-
else
|
50
|
-
sliced_old_obj = old_obj
|
51
|
-
sliced_new_obj = new_obj
|
52
|
-
end
|
53
|
-
if sliced_old_obj != sliced_new_obj
|
54
|
-
difference = sliced_new_obj.to_a - sliced_old_obj.to_a
|
55
|
-
differences = Hash[*difference.mapcat { |x| x }]
|
56
|
-
res[:changed] << {
|
57
|
-
old_obj: old_obj,
|
58
|
-
new_obj: new_obj,
|
59
|
-
diff: differences
|
60
|
-
}
|
61
|
-
else
|
62
|
-
res[:same] << old_obj
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
new_lookup = Hash[new_list.map { |v| [v[lookup_key], v] }]
|
67
|
-
old_list.each do |old_obj|
|
68
|
-
new_obj = new_lookup[old_obj[lookup_key]]
|
69
|
-
if new_obj.nil?
|
70
|
-
res[:removed] << old_obj
|
71
|
-
next
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
res
|
76
|
-
end
|
77
|
-
|
78
|
-
def create_lookup(collection, on)
|
79
|
-
lookup = {}
|
80
|
-
if on.is_a?(Array)
|
81
|
-
collection.each do |e|
|
82
|
-
key = e.values_at(*on)
|
83
|
-
lookup[key] = [] unless lookup.key?(key)
|
84
|
-
lookup[key] << e
|
85
|
-
end
|
86
|
-
else
|
87
|
-
collection.each do |e|
|
88
|
-
key = e[on]
|
89
|
-
lookup[key] = [] unless lookup.key?(key)
|
90
|
-
lookup[key] << e
|
91
|
-
end
|
92
|
-
end
|
93
|
-
lookup
|
94
|
-
end
|
95
|
-
|
96
|
-
ENCODED_PARAMS_KEY = :gd_encoded_params
|
97
|
-
ENCODED_HIDDEN_PARAMS_KEY = :gd_encoded_hidden_params
|
98
|
-
|
99
|
-
# Encodes parameters for passing them to GD execution platform.
|
100
|
-
# Core types are kept and complex types (arrays, structures, etc) are JSON encoded into key hash "gd_encoded_params" or "gd_encoded_hidden_params", depending on the 'hidden' method param.
|
101
|
-
# The two different keys are used because the params and hidden params are merged by the platform and if we use the same key, the param would be overwritten.
|
102
|
-
#
|
103
|
-
# Core types are following:
|
104
|
-
# - Boolean (true, false)
|
105
|
-
# - Fixnum
|
106
|
-
# - Float
|
107
|
-
# - Nil
|
108
|
-
# - String
|
109
|
-
#
|
110
|
-
# @param [Hash] params Parameters to be encoded
|
111
|
-
# @return [Hash] Encoded parameters
|
112
|
-
def encode_params(params, hidden = false)
|
113
|
-
res = {}
|
114
|
-
nested = {}
|
115
|
-
core_types = [FalseClass, Fixnum, Float, NilClass, TrueClass, String]
|
116
|
-
params.each do |k, v|
|
117
|
-
if core_types.include?(v.class)
|
118
|
-
res[k] = v
|
119
|
-
else
|
120
|
-
nested[k] = v
|
121
|
-
end
|
122
|
-
end
|
123
|
-
key = hidden ? ENCODED_HIDDEN_PARAMS_KEY : ENCODED_PARAMS_KEY
|
124
|
-
res[key] = nested.to_json unless nested.empty?
|
125
|
-
res
|
126
|
-
end
|
127
|
-
|
128
|
-
# Decodes params as they came from the platform
|
129
|
-
# The "data" key is supposed to be json and it's parsed - if this
|
130
|
-
def decode_params(params)
|
131
|
-
key = ENCODED_PARAMS_KEY.to_s
|
132
|
-
hidden_key = ENCODED_HIDDEN_PARAMS_KEY.to_s
|
133
|
-
data_params = params[key] || '{}'
|
134
|
-
hidden_data_params = params[hidden_key] || '{}'
|
135
|
-
|
136
|
-
begin
|
137
|
-
parsed_data_params = JSON.parse(data_params)
|
138
|
-
parsed_hidden_data_params = JSON.parse(hidden_data_params)
|
139
|
-
rescue JSON::ParserError => e
|
140
|
-
raise e.class, "Error reading json from '#{key}' or '#{hidden_key}' in params #{params}\n #{e.message}"
|
141
|
-
end
|
142
|
-
params.delete(key)
|
143
|
-
params.delete(hidden_key)
|
144
|
-
params.merge(parsed_data_params).merge(parsed_hidden_data_params)
|
145
|
-
end
|
146
|
-
|
147
11
|
def error(msg)
|
148
12
|
STDERR.puts(msg)
|
149
13
|
exit 1
|
@@ -252,6 +116,18 @@ module GoodData
|
|
252
116
|
h
|
253
117
|
end
|
254
118
|
end
|
119
|
+
|
120
|
+
def stringify_keys_deep!(h)
|
121
|
+
if Hash == h.class
|
122
|
+
Hash[
|
123
|
+
h.map do |k, v|
|
124
|
+
[k.respond_to?(:to_s) ? k.to_s : k, stringify_keys_deep!(v)]
|
125
|
+
end
|
126
|
+
]
|
127
|
+
else
|
128
|
+
h
|
129
|
+
end
|
130
|
+
end
|
255
131
|
end
|
256
132
|
end
|
257
133
|
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module GoodData
|
4
|
+
module Helpers
|
5
|
+
class << self
|
6
|
+
ENCODED_PARAMS_KEY = :gd_encoded_params
|
7
|
+
ENCODED_HIDDEN_PARAMS_KEY = :gd_encoded_hidden_params
|
8
|
+
|
9
|
+
# Encodes parameters for passing them to GD execution platform.
|
10
|
+
# Core types are kept and complex types (arrays, structures, etc) are JSON encoded into key hash "gd_encoded_params" or "gd_encoded_hidden_params", depending on the 'hidden' method param.
|
11
|
+
# The two different keys are used because the params and hidden params are merged by the platform and if we use the same key, the param would be overwritten.
|
12
|
+
#
|
13
|
+
# Core types are following:
|
14
|
+
# - Boolean (true, false)
|
15
|
+
# - Fixnum
|
16
|
+
# - Float
|
17
|
+
# - Nil
|
18
|
+
# - String
|
19
|
+
#
|
20
|
+
# @param [Hash] params Parameters to be encoded
|
21
|
+
# @return [Hash] Encoded parameters
|
22
|
+
def encode_params(params, hidden = false)
|
23
|
+
res = {}
|
24
|
+
nested = {}
|
25
|
+
core_types = [FalseClass, Fixnum, Float, NilClass, TrueClass, String]
|
26
|
+
params.each do |k, v|
|
27
|
+
if core_types.include?(v.class)
|
28
|
+
res[k] = v
|
29
|
+
else
|
30
|
+
nested[k] = v
|
31
|
+
end
|
32
|
+
end
|
33
|
+
key = hidden ? ENCODED_HIDDEN_PARAMS_KEY : ENCODED_PARAMS_KEY
|
34
|
+
res[key] = nested.to_json unless nested.empty?
|
35
|
+
res
|
36
|
+
end
|
37
|
+
|
38
|
+
# Decodes params as they came from the platform
|
39
|
+
# The "data" key is supposed to be json and it's parsed - if this
|
40
|
+
def decode_params(params)
|
41
|
+
key = ENCODED_PARAMS_KEY.to_s
|
42
|
+
hidden_key = ENCODED_HIDDEN_PARAMS_KEY.to_s
|
43
|
+
data_params = params[key] || '{}'
|
44
|
+
hidden_data_params = params[hidden_key] || '{}'
|
45
|
+
|
46
|
+
begin
|
47
|
+
parsed_data_params = JSON.parse(data_params)
|
48
|
+
parsed_hidden_data_params = JSON.parse(hidden_data_params)
|
49
|
+
rescue JSON::ParserError => e
|
50
|
+
raise e.class, "Error reading json from '#{key}' or '#{hidden_key}' in params #{params}\n #{e.message}"
|
51
|
+
end
|
52
|
+
params.delete(key)
|
53
|
+
params.delete(hidden_key)
|
54
|
+
params.merge(parsed_data_params).merge(parsed_hidden_data_params)
|
55
|
+
end
|
56
|
+
|
57
|
+
# A helper which allows you to diff two lists of objects. The objects
|
58
|
+
# can be arbitrary objects as long as they respond to to_hash because
|
59
|
+
# the diff is eventually done on hashes. It allows you to specify
|
60
|
+
# several options to allow you to limit on what the sameness test is done
|
61
|
+
#
|
62
|
+
# @param [Array<Object>] old_list List of objects that serves as a base for comparison
|
63
|
+
# @param [Array<Object>] new_list List of objects that is compared agianst the old_list
|
64
|
+
# @return [Hash] A structure that contains the result of the comparison. There are
|
65
|
+
# four keys.
|
66
|
+
# :added contains the list that are in new_list but were not in the old_list
|
67
|
+
# :added contains the list that are in old_list but were not in the new_list
|
68
|
+
# :same contains objects that are in both lists and they are the same
|
69
|
+
# :changed contains list of objects that changed along ith original, the new one
|
70
|
+
# and the list of changes
|
71
|
+
def diff(old_list, new_list, options = {})
|
72
|
+
old_list = old_list.map(&:to_hash)
|
73
|
+
new_list = new_list.map(&:to_hash)
|
74
|
+
|
75
|
+
fields = options[:fields]
|
76
|
+
lookup_key = options[:key]
|
77
|
+
|
78
|
+
old_lookup = Hash[old_list.map { |v| [v[lookup_key], v] }]
|
79
|
+
|
80
|
+
res = {
|
81
|
+
:added => [],
|
82
|
+
:removed => [],
|
83
|
+
:changed => [],
|
84
|
+
:same => []
|
85
|
+
}
|
86
|
+
|
87
|
+
new_list.each do |new_obj|
|
88
|
+
old_obj = old_lookup[new_obj[lookup_key]]
|
89
|
+
if old_obj.nil?
|
90
|
+
res[:added] << new_obj
|
91
|
+
next
|
92
|
+
end
|
93
|
+
|
94
|
+
if fields
|
95
|
+
sliced_old_obj = old_obj.slice(*fields)
|
96
|
+
sliced_new_obj = new_obj.slice(*fields)
|
97
|
+
else
|
98
|
+
sliced_old_obj = old_obj
|
99
|
+
sliced_new_obj = new_obj
|
100
|
+
end
|
101
|
+
if sliced_old_obj != sliced_new_obj
|
102
|
+
difference = sliced_new_obj.to_a - sliced_old_obj.to_a
|
103
|
+
differences = Hash[*difference.mapcat { |x| x }]
|
104
|
+
res[:changed] << {
|
105
|
+
old_obj: old_obj,
|
106
|
+
new_obj: new_obj,
|
107
|
+
diff: differences
|
108
|
+
}
|
109
|
+
else
|
110
|
+
res[:same] << old_obj
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
new_lookup = Hash[new_list.map { |v| [v[lookup_key], v] }]
|
115
|
+
old_list.each do |old_obj|
|
116
|
+
new_obj = new_lookup[old_obj[lookup_key]]
|
117
|
+
if new_obj.nil?
|
118
|
+
res[:removed] << old_obj
|
119
|
+
next
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
res
|
124
|
+
end
|
125
|
+
|
126
|
+
def create_lookup(collection, on)
|
127
|
+
lookup = {}
|
128
|
+
if on.is_a?(Array)
|
129
|
+
collection.each do |e|
|
130
|
+
key = e.values_at(*on)
|
131
|
+
lookup[key] = [] unless lookup.key?(key)
|
132
|
+
lookup[key] << e
|
133
|
+
end
|
134
|
+
else
|
135
|
+
collection.each do |e|
|
136
|
+
key = e[on]
|
137
|
+
lookup[key] = [] unless lookup.key?(key)
|
138
|
+
lookup[key] << e
|
139
|
+
end
|
140
|
+
end
|
141
|
+
lookup
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|