gooddata 0.6.19 → 0.6.20
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +38 -0
- data/Rakefile +17 -3
- data/gooddata.gemspec +8 -7
- data/lib/gooddata/bricks/middleware/base_middleware.rb +1 -1
- data/lib/gooddata/cli/commands/run_ruby_cmd.rb +2 -2
- data/lib/gooddata/cli/shared.rb +2 -1
- data/lib/gooddata/commands/auth.rb +58 -5
- data/lib/gooddata/commands/runners.rb +2 -6
- data/lib/gooddata/extensions/big_decimal.rb +4 -0
- data/lib/gooddata/extensions/false.rb +11 -0
- data/lib/gooddata/extensions/hash.rb +6 -17
- data/lib/gooddata/extensions/nil.rb +11 -0
- data/lib/gooddata/extensions/numeric.rb +11 -0
- data/lib/gooddata/extensions/object.rb +11 -0
- data/lib/gooddata/extensions/symbol.rb +11 -0
- data/lib/gooddata/extensions/true.rb +11 -0
- data/lib/gooddata/helpers/auth_helpers.rb +32 -2
- data/lib/gooddata/helpers/data_helper.rb +1 -1
- data/lib/gooddata/helpers/global_helpers.rb +98 -31
- data/lib/gooddata/mixins/md_finders.rb +15 -15
- data/lib/gooddata/mixins/md_object_query.rb +12 -2
- data/lib/gooddata/models/blueprint/blueprint_field.rb +2 -2
- data/lib/gooddata/models/blueprint/dataset_blueprint.rb +2 -2
- data/lib/gooddata/models/blueprint/project_blueprint.rb +3 -3
- data/lib/gooddata/models/blueprint/schema_blueprint.rb +1 -1
- data/lib/gooddata/models/datawarehouse.rb +1 -0
- data/lib/gooddata/models/domain.rb +13 -16
- data/lib/gooddata/models/from_wire.rb +0 -2
- data/lib/gooddata/models/membership.rb +1 -1
- data/lib/gooddata/models/metadata/attribute.rb +1 -1
- data/lib/gooddata/models/metadata/dashboard.rb +1 -1
- data/lib/gooddata/models/metadata/dataset.rb +1 -1
- data/lib/gooddata/models/metadata/dimension.rb +1 -1
- data/lib/gooddata/models/metadata/fact.rb +1 -1
- data/lib/gooddata/models/metadata/label.rb +16 -17
- data/lib/gooddata/models/metadata/metric.rb +1 -1
- data/lib/gooddata/models/metadata/report.rb +1 -1
- data/lib/gooddata/models/metadata/report_definition.rb +7 -7
- data/lib/gooddata/models/metadata/variable.rb +1 -1
- data/lib/gooddata/models/model.rb +2 -2
- data/lib/gooddata/models/profile.rb +2 -2
- data/lib/gooddata/models/project.rb +21 -23
- data/lib/gooddata/models/project_role.rb +3 -3
- data/lib/gooddata/models/schedule.rb +18 -4
- data/lib/gooddata/models/user_filters/mandatory_user_filter.rb +12 -15
- data/lib/gooddata/models/user_filters/user_filter.rb +8 -8
- data/lib/gooddata/models/user_filters/user_filter_builder.rb +16 -13
- data/lib/gooddata/models/user_filters/variable_user_filter.rb +1 -1
- data/lib/gooddata/rest/client.rb +4 -2
- data/lib/gooddata/rest/connection.rb +37 -30
- data/lib/gooddata/rest/connections/rest_client_connection.rb +1 -1
- data/lib/gooddata/version.rb +1 -1
- data/spec/environment/develop.rb +4 -4
- data/spec/environment/hotfix.rb +1 -1
- data/spec/environment/release.rb +1 -1
- data/spec/integration/full_project_spec.rb +3 -3
- data/spec/integration/over_to_user_filters_spec.rb +1 -0
- data/spec/integration/project_spec.rb +1 -1
- data/spec/integration/user_filters_spec.rb +0 -1
- data/spec/unit/commands/command_auth_spec.rb +10 -0
- data/spec/unit/extensions/hash_spec.rb +1 -1
- data/spec/unit/helpers_spec.rb +0 -8
- data/spec/unit/models/domain_spec.rb +1 -9
- data/spec/unit/models/from_wire_spec.rb +1 -19
- data/spec/unit/models/membership_spec.rb +1 -1
- data/spec/unit/models/metadata_spec.rb +1 -1
- data/spec/unit/models/profile_spec.rb +23 -47
- data/spec/unit/models/schedule_spec.rb +47 -3
- metadata +174 -50
- data/lib/gooddata/models/from_wire_parse.rb +0 -125
@@ -11,7 +11,7 @@ module GoodData
|
|
11
11
|
|
12
12
|
def initialize(opts = {})
|
13
13
|
opts = opts.is_a?(String) ? { type: :staging, path: opts } : opts
|
14
|
-
opts =
|
14
|
+
opts = GoodData::Helpers.symbolize_keys(opts)
|
15
15
|
@source = opts[:type]
|
16
16
|
@options = opts
|
17
17
|
@realized = false
|
@@ -1,12 +1,16 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
|
-
require 'active_support/all'
|
4
3
|
require 'pathname'
|
4
|
+
require 'hashie'
|
5
5
|
|
6
6
|
require_relative 'global_helpers_params'
|
7
7
|
|
8
8
|
module GoodData
|
9
9
|
module Helpers
|
10
|
+
class DeepMergeableHash < Hash
|
11
|
+
include Hashie::Extensions::DeepMerge
|
12
|
+
end
|
13
|
+
|
10
14
|
class << self
|
11
15
|
def error(msg)
|
12
16
|
STDERR.puts(msg)
|
@@ -51,9 +55,11 @@ module GoodData
|
|
51
55
|
end
|
52
56
|
end
|
53
57
|
|
54
|
-
|
55
|
-
|
56
|
-
|
58
|
+
def titleize(str)
|
59
|
+
titleized = str.gsub(/[\.|_](.)/) { |x| x.upcase }
|
60
|
+
titleized = titleized.gsub('_', ' ')
|
61
|
+
titleized[0] = titleized[0].upcase
|
62
|
+
titleized
|
57
63
|
end
|
58
64
|
|
59
65
|
def join(master, slave, on, on2, options = {})
|
@@ -91,41 +97,102 @@ module GoodData
|
|
91
97
|
RUBY_PLATFORM =~ /-darwin\d/
|
92
98
|
end
|
93
99
|
|
94
|
-
# TODO: Implement without using ActiveSupport
|
95
|
-
def sanitize_string(str, filter = /[^a-z_]/, replacement = '')
|
96
|
-
str = ActiveSupport::Inflector.transliterate(str).downcase
|
97
|
-
str.gsub(filter, replacement)
|
98
|
-
end
|
99
|
-
|
100
100
|
def underline(x)
|
101
101
|
'=' * x.size
|
102
102
|
end
|
103
103
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
def symbolize_keys_deep!(h)
|
109
|
-
if Hash == h
|
110
|
-
Hash[
|
111
|
-
h.map do |k, v|
|
112
|
-
[k.respond_to?(:to_sym) ? k.to_sym : k, symbolize_keys_deep!(v)]
|
113
|
-
end
|
114
|
-
]
|
115
|
-
else
|
116
|
-
h
|
104
|
+
def transform_keys!(an_object)
|
105
|
+
return enum_for(:transform_keys!) unless block_given?
|
106
|
+
an_object.keys.each do |key|
|
107
|
+
an_object[yield(key)] = delete(key)
|
117
108
|
end
|
109
|
+
an_object
|
118
110
|
end
|
119
111
|
|
120
|
-
def
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
112
|
+
def symbolize_keys!(an_object)
|
113
|
+
transform_keys!(an_object) do |key|
|
114
|
+
begin
|
115
|
+
key.to_sym
|
116
|
+
rescue
|
117
|
+
key
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def symbolize_keys(an_object)
|
123
|
+
transform_keys(an_object) do |key|
|
124
|
+
begin
|
125
|
+
key.to_sym
|
126
|
+
rescue
|
127
|
+
key
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def transform_keys(an_object)
|
133
|
+
return enum_for(:transform_keys) unless block_given?
|
134
|
+
result = an_object.class.new
|
135
|
+
an_object.each_key do |key|
|
136
|
+
result[yield(key)] = an_object[key]
|
137
|
+
end
|
138
|
+
result
|
139
|
+
end
|
140
|
+
|
141
|
+
def deep_symbolize_keys(an_object)
|
142
|
+
deep_transform_keys(an_object) do |key|
|
143
|
+
begin
|
144
|
+
key.to_sym
|
145
|
+
rescue
|
146
|
+
key
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def stringify_keys(an_object)
|
152
|
+
transform_keys(an_object) { |key| key.to_s }
|
153
|
+
end
|
154
|
+
|
155
|
+
def deep_stringify_keys(an_object)
|
156
|
+
deep_transform_keys(an_object) { |key| key.to_s }
|
157
|
+
end
|
158
|
+
|
159
|
+
def deep_transform_keys(an_object, &block)
|
160
|
+
_deep_transform_keys_in_object(an_object, &block)
|
161
|
+
end
|
162
|
+
|
163
|
+
def _deep_transform_keys_in_object(object, &block)
|
164
|
+
case object
|
165
|
+
when Hash
|
166
|
+
object.each_with_object({}) do |(key, value), result|
|
167
|
+
result[yield(key)] = _deep_transform_keys_in_object(value, &block)
|
168
|
+
end
|
169
|
+
when Array
|
170
|
+
object.map { |e| _deep_transform_keys_in_object(e, &block) }
|
127
171
|
else
|
128
|
-
|
172
|
+
object
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def deep_dup(an_object)
|
177
|
+
case an_object
|
178
|
+
when Array
|
179
|
+
an_object.map { |it| GoodData::Helpers.deep_dup(it) }
|
180
|
+
when Hash
|
181
|
+
an_object.each_with_object(an_object.dup) do |(key, value), hash|
|
182
|
+
hash[GoodData::Helpers.deep_dup(key)] = GoodData::Helpers.deep_dup(value)
|
183
|
+
end
|
184
|
+
when Object
|
185
|
+
an_object.duplicable? ? an_object.dup : an_object
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def undot(params)
|
190
|
+
# for each key-value config given
|
191
|
+
params.map do |k, v|
|
192
|
+
# dot notation to hash
|
193
|
+
k.split('__').reverse.reduce(v) do |memo, obj|
|
194
|
+
GoodData::Helper.DeepMergeableHash[{ obj => memo }]
|
195
|
+
end
|
129
196
|
end
|
130
197
|
end
|
131
198
|
end
|
@@ -11,11 +11,11 @@ module GoodData
|
|
11
11
|
def find_first_by_identifier(identifier, options = { :client => GoodData.connection, :project => GoodData.project })
|
12
12
|
all = self[:all, options.merge(full: false)]
|
13
13
|
item = if identifier.is_a?(Regexp)
|
14
|
-
all.find { |r| r
|
14
|
+
all.find { |r| r.identifier =~ identifier }
|
15
15
|
else
|
16
|
-
all.find { |r| r
|
16
|
+
all.find { |r| r.identifier == identifier }
|
17
17
|
end
|
18
|
-
self[item
|
18
|
+
self[item.uri, options] unless item.nil?
|
19
19
|
end
|
20
20
|
|
21
21
|
# Finds a specific type of the object by identifier. Returns all matches. Returns full object.
|
@@ -24,13 +24,13 @@ module GoodData
|
|
24
24
|
# @param title [Regexp] regular expression that has to match
|
25
25
|
# @return [Array<GoodData::MdObject>] Array of MdObject
|
26
26
|
def find_by_identifier(identifier, options = { :client => GoodData.connection, :project => GoodData.project })
|
27
|
-
all = self[:all, options
|
27
|
+
all = self[:all, options]
|
28
28
|
items = if identifier.is_a?(Regexp)
|
29
|
-
all.select { |r| r
|
29
|
+
all.select { |r| r.title =~ identifier }
|
30
30
|
else
|
31
|
-
all.select { |r| r
|
31
|
+
all.select { |r| r.title == identifier }
|
32
32
|
end
|
33
|
-
items.pmap { |item| self[item
|
33
|
+
items.pmap { |item| self[item.uri, options] unless item.nil? }
|
34
34
|
end
|
35
35
|
|
36
36
|
def find_by_tag(tag, opts = { :client => GoodData.connection, :project => GoodData.project })
|
@@ -51,13 +51,13 @@ module GoodData
|
|
51
51
|
# @param title [Regexp] regular expression that has to match
|
52
52
|
# @return [Array<GoodData::MdObject>] Array of MdObject
|
53
53
|
def find_first_by_title(title, options = { :client => GoodData.connection, :project => GoodData.project })
|
54
|
-
all = self[:all, options
|
54
|
+
all = self[:all, options]
|
55
55
|
item = if title.is_a?(Regexp)
|
56
|
-
all.find { |r| r
|
56
|
+
all.find { |r| r.title =~ title }
|
57
57
|
else
|
58
|
-
all.find { |r| r
|
58
|
+
all.find { |r| r.title == title }
|
59
59
|
end
|
60
|
-
self[item
|
60
|
+
self[item.uri, options] unless item.nil?
|
61
61
|
end
|
62
62
|
|
63
63
|
# Finds a specific type of the object by title. Returns all matches. Returns full object.
|
@@ -66,13 +66,13 @@ module GoodData
|
|
66
66
|
# @param title [Regexp] regular expression that has to match
|
67
67
|
# @return [Array<GoodData::MdObject>] Array of MdObject
|
68
68
|
def find_by_title(title, options = { :client => GoodData.connection, :project => GoodData.project })
|
69
|
-
all = self[:all, options
|
69
|
+
all = self[:all, options]
|
70
70
|
items = if title.is_a?(Regexp)
|
71
|
-
all.select { |r| r
|
71
|
+
all.select { |r| r.title =~ title }
|
72
72
|
else
|
73
|
-
all.select { |r| r
|
73
|
+
all.select { |r| r.title == title }
|
74
74
|
end
|
75
|
-
items.pmap { |item| self[item
|
75
|
+
items.pmap { |item| self[item.uri, options] unless item.nil? }
|
76
76
|
end
|
77
77
|
end
|
78
78
|
end
|
@@ -33,8 +33,18 @@ module GoodData
|
|
33
33
|
project = GoodData::Project[p, options]
|
34
34
|
fail ArgumentError, 'Wrong :project specified' if project.nil?
|
35
35
|
|
36
|
-
|
37
|
-
|
36
|
+
offset = 0
|
37
|
+
page_limit = 50
|
38
|
+
Enumerator.new do |y|
|
39
|
+
loop do
|
40
|
+
result = client.get(project.md['objects'] + '/query', params: { category: query_obj_type, limit: page_limit, offset: offset })
|
41
|
+
result['objects']['items'].each do |item|
|
42
|
+
y << (klass ? client.create(klass, item, project: project) : item)
|
43
|
+
end
|
44
|
+
break if result['objects']['paging']['count'] < page_limit
|
45
|
+
offset += page_limit
|
46
|
+
end
|
47
|
+
end
|
38
48
|
end
|
39
49
|
|
40
50
|
def dependency(uri, key = nil, opts = { :client => GoodData.connection })
|
@@ -10,7 +10,7 @@ module GoodData
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def initialize(data, dataset)
|
13
|
-
@data =
|
13
|
+
@data = GoodData::Helpers.symbolize_keys(data)
|
14
14
|
@data[:type] = @data[:type].to_sym
|
15
15
|
@dataset_blueprint = dataset
|
16
16
|
end
|
@@ -39,7 +39,7 @@ module GoodData
|
|
39
39
|
end
|
40
40
|
|
41
41
|
def title
|
42
|
-
@data[:title] || @data[:id]
|
42
|
+
@data[:title] || GoodData::Helpers.titleize(@data[:id])
|
43
43
|
end
|
44
44
|
|
45
45
|
# Validates the fields in the field
|
@@ -243,7 +243,7 @@ module GoodData
|
|
243
243
|
#
|
244
244
|
# @return [GoodData::Model::DatasetBlueprint] matching fields
|
245
245
|
def dup
|
246
|
-
DatasetBlueprint.new(
|
246
|
+
DatasetBlueprint.new(GoodData::Helpers.deep_dup(data), project_blueprint)
|
247
247
|
end
|
248
248
|
|
249
249
|
# Returns facts of a dataset
|
@@ -367,7 +367,7 @@ module GoodData
|
|
367
367
|
identifiers = facts.map { |f| identifier_for(f) }
|
368
368
|
identifiers.zip(facts).map do |id, fact|
|
369
369
|
Metric.xcreate(
|
370
|
-
:title => fact[:name]
|
370
|
+
:title => GoodData::Helpers.titleize(fact[:name]),
|
371
371
|
:expression => "SELECT SUM(![#{id}])")
|
372
372
|
end
|
373
373
|
end
|
@@ -51,7 +51,7 @@ module GoodData
|
|
51
51
|
def self.remove_dataset(project, dataset_id)
|
52
52
|
dataset = dataset_id.is_a?(String) ? find_dataset(project, dataset_id) : dataset_name
|
53
53
|
index = project[:datasets].index(dataset)
|
54
|
-
dupped_project =
|
54
|
+
dupped_project = GoodData::Helpers.deep_dup(project)
|
55
55
|
dupped_project[:datasets].delete_at(index)
|
56
56
|
dupped_project
|
57
57
|
end
|
@@ -338,7 +338,7 @@ module GoodData
|
|
338
338
|
# @param a_blueprint [GoodData::Model::DatasetBlueprint] Dataset blueprint to be merged
|
339
339
|
# @return [GoodData::Model::DatasetBlueprint]
|
340
340
|
def dup
|
341
|
-
ProjectBlueprint.new(
|
341
|
+
ProjectBlueprint.new(GoodData::Helpers.deep_dup(data))
|
342
342
|
end
|
343
343
|
|
344
344
|
# Returns list of facts from all the datasets in a blueprint
|
@@ -396,7 +396,7 @@ module GoodData
|
|
396
396
|
else
|
397
397
|
init_data
|
398
398
|
end
|
399
|
-
@data =
|
399
|
+
@data = GoodData::Helpers.symbolize_keys(GoodData::Helpers.deep_dup(some_data))
|
400
400
|
(@data[:datasets] || []).each do |d|
|
401
401
|
d[:type] = d[:type].to_sym
|
402
402
|
d[:columns].each do |c|
|
@@ -17,6 +17,7 @@ module GoodData
|
|
17
17
|
c = client(opts)
|
18
18
|
fail ArgumentError, 'No :client specified' if c.nil?
|
19
19
|
|
20
|
+
opts = { :auth_token => Helpers::AuthHelper.read_token }.merge(opts)
|
20
21
|
auth_token = opts[:auth_token] || opts[:token]
|
21
22
|
fail ArgumentError, 'You have to provide your token for creating projects as :auth_token parameter' if auth_token.nil? || auth_token.empty?
|
22
23
|
|
@@ -183,26 +183,23 @@ module GoodData
|
|
183
183
|
# @option opts [Number] :limit From address
|
184
184
|
# TODO: Review opts[:limit] functionality
|
185
185
|
def users(domain, id = :all, opts = {})
|
186
|
-
|
187
|
-
domain =
|
186
|
+
client = client(opts)
|
187
|
+
domain = client.domain(domain)
|
188
188
|
if id == :all
|
189
189
|
GoodData.logger.warn("Retrieving all users from domain #{domain.name}")
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
190
|
+
Enumerator.new do |y|
|
191
|
+
page_limit = opts[:page_limit] || 1000
|
192
|
+
offset = opts[:offset] || 0
|
193
|
+
loop do
|
194
|
+
tmp = client(opts).get("#{domain.uri}/users", params: { offset: offset, limit: page_limit })
|
195
|
+
tmp['accountSettings']['items'].each do |user_data|
|
196
|
+
user = client.create(GoodData::Profile, user_data)
|
197
|
+
y << user
|
198
|
+
end
|
199
|
+
break if tmp['accountSettings']['items'].count < page_limit
|
200
|
+
offset += page_limit
|
199
201
|
end
|
200
|
-
break if result.length >= limit
|
201
|
-
|
202
|
-
uri = tmp['accountSettings']['paging']['next']
|
203
|
-
break unless uri
|
204
202
|
end
|
205
|
-
result
|
206
203
|
else
|
207
204
|
find_user_by_login(domain, id)
|
208
205
|
end
|
@@ -381,7 +381,7 @@ module GoodData
|
|
381
381
|
end
|
382
382
|
|
383
383
|
def to_hash
|
384
|
-
tmp = content.merge(meta).merge('uri' => uri)
|
384
|
+
tmp = GoodData::Helpers.symbolize_keys(content.merge(meta).merge('uri' => uri))
|
385
385
|
[
|
386
386
|
[:userRoles, :role],
|
387
387
|
[:companyName, :company_name],
|
@@ -21,7 +21,7 @@ module GoodData
|
|
21
21
|
# @option options [Boolean] :full if passed true the subclass can decide to pull in full objects. This is desirable from the usability POV but unfortunately has negative impact on performance so it is not the default
|
22
22
|
# @return [Array<GoodData::MdObject> | Array<Hash>] Return the appropriate metadata objects or their representation
|
23
23
|
def all(options = { :client => GoodData.connection, :project => GoodData.project })
|
24
|
-
query('
|
24
|
+
query('attribute', Attribute, options)
|
25
25
|
end
|
26
26
|
|
27
27
|
# Finds the value of an atribute and gives you the textual form for the label that is acquired by calling primary_label method
|
@@ -20,7 +20,7 @@ module GoodData
|
|
20
20
|
# @option options [Boolean] :full if passed true the subclass can decide to pull in full objects. This is desirable from the usability POV but unfortunately has negative impact on performance so it is not the default
|
21
21
|
# @return [Array<GoodData::MdObject> | Array<Hash>] Return the appropriate metadata objects or their representation
|
22
22
|
def all(options = { :client => GoodData.connection, :project => GoodData.project })
|
23
|
-
query('
|
23
|
+
query('projectDashboard', Dashboard, options)
|
24
24
|
end
|
25
25
|
|
26
26
|
def create_report_tab(tab)
|
@@ -13,7 +13,7 @@ module GoodData
|
|
13
13
|
# @option options [Boolean] :full if passed true the subclass can decide to pull in full objects. This is desirable from the usability POV but unfortunately has negative impact on performance so it is not the default
|
14
14
|
# @return [Array<GoodData::MdObject> | Array<Hash>] Return the appropriate metadata objects or their representation
|
15
15
|
def all(options = { :client => GoodData.connection, :project => GoodData.project })
|
16
|
-
query('
|
16
|
+
query('dataSet', Dataset, options)
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
@@ -17,7 +17,7 @@ module GoodData
|
|
17
17
|
# @option options [Boolean] :full if passed true the subclass can decide to pull in full objects. This is desirable from the usability POV but unfortunately has negative impact on performance so it is not the default
|
18
18
|
# @return [Array<GoodData::MdObject> | Array<Hash>] Return the appropriate metadata objects or their representation
|
19
19
|
def all(options = { :client => GoodData.connection, :project => GoodData.project })
|
20
|
-
query('
|
20
|
+
query('dimension', Dimension, options)
|
21
21
|
end
|
22
22
|
|
23
23
|
# Returns a Project object identified by given string
|
@@ -22,7 +22,7 @@ module GoodData
|
|
22
22
|
# @option options [Boolean] :full if passed true the subclass can decide to pull in full objects. This is desirable from the usability POV but unfortunately has negative impact on performance so it is not the default
|
23
23
|
# @return [Array<GoodData::MdObject> | Array<Hash>] Return the appropriate metadata objects or their representation
|
24
24
|
def all(options = { :client => GoodData.connection, :project => GoodData.project })
|
25
|
-
query('
|
25
|
+
query('fact', Fact, options)
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|