gooddata 0.6.7 → 0.6.8
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/CHANGELOG.md +19 -1
- data/README.md +10 -2
- data/TODO.md +32 -0
- data/gooddata.gemspec +5 -0
- data/lib/gooddata.rb +4 -0
- data/lib/gooddata/app/app.rb +12 -0
- data/lib/gooddata/bricks/middleware/gooddata_middleware.rb +4 -3
- data/lib/gooddata/bricks/middleware/restforce_middleware.rb +2 -1
- data/lib/gooddata/cli/commands/console_cmd.rb +23 -5
- data/lib/gooddata/cli/commands/domain_cmd.rb +9 -10
- data/lib/gooddata/cli/commands/process_cmd.rb +11 -9
- data/lib/gooddata/cli/commands/project_cmd.rb +25 -27
- data/lib/gooddata/cli/commands/projects_cmd.rb +2 -2
- data/lib/gooddata/cli/commands/run_ruby_cmd.rb +1 -1
- data/lib/gooddata/cli/commands/user_cmd.rb +2 -2
- data/lib/gooddata/cli/hooks.rb +4 -2
- data/lib/gooddata/cli/shared.rb +1 -1
- data/lib/gooddata/cli/terminal.rb +1 -1
- data/lib/gooddata/commands/api.rb +1 -1
- data/lib/gooddata/commands/auth.rb +4 -28
- data/lib/gooddata/commands/domain.rb +9 -4
- data/lib/gooddata/commands/process.rb +26 -23
- data/lib/gooddata/commands/project.rb +74 -50
- data/lib/gooddata/commands/projects.rb +3 -2
- data/lib/gooddata/commands/role.rb +9 -3
- data/lib/gooddata/commands/user.rb +6 -4
- data/lib/gooddata/connection.rb +11 -45
- data/lib/gooddata/core/logging.rb +0 -1
- data/lib/gooddata/core/project.rb +22 -22
- data/lib/gooddata/core/rest.rb +9 -8
- data/lib/gooddata/core/user.rb +0 -11
- data/lib/gooddata/exceptions/project_not_found.rb +1 -0
- data/lib/gooddata/extensions/enumerable.rb +10 -0
- data/lib/gooddata/extensions/hash.rb +25 -0
- data/lib/gooddata/goodzilla/goodzilla.rb +4 -4
- data/lib/gooddata/helper/class_helper.rb +1 -0
- data/lib/gooddata/helper/helpers.rb +8 -0
- data/lib/gooddata/helpers/auth_helpers.rb +41 -0
- data/lib/gooddata/mixins/author.rb +1 -1
- data/lib/gooddata/mixins/contributor.rb +1 -1
- data/lib/gooddata/mixins/data_property_reader.rb +2 -0
- data/lib/gooddata/mixins/data_property_writer.rb +2 -0
- data/lib/gooddata/mixins/inspector.rb +49 -0
- data/lib/gooddata/mixins/md_finders.rb +16 -8
- data/lib/gooddata/mixins/md_id_to_uri.rb +12 -4
- data/lib/gooddata/mixins/md_object_indexer.rb +15 -4
- data/lib/gooddata/mixins/md_object_query.rb +42 -20
- data/lib/gooddata/mixins/md_relations.rb +21 -12
- data/lib/gooddata/mixins/meta_getter.rb +2 -0
- data/lib/gooddata/mixins/meta_property_reader.rb +2 -0
- data/lib/gooddata/mixins/meta_property_writer.rb +2 -0
- data/lib/gooddata/mixins/rest_resource.rb +32 -10
- data/lib/gooddata/mixins/root_key_getter.rb +1 -1
- data/lib/gooddata/models/data_result.rb +3 -1
- data/lib/gooddata/models/domain.rb +31 -22
- data/lib/gooddata/models/empty_result.rb +22 -0
- data/lib/gooddata/models/invitation.rb +11 -9
- data/lib/gooddata/models/links.rb +5 -3
- data/lib/gooddata/models/membership.rb +23 -28
- data/lib/gooddata/models/metadata.rb +35 -35
- data/lib/gooddata/models/metadata/attribute.rb +10 -8
- data/lib/gooddata/models/metadata/dashboard.rb +1 -1
- data/lib/gooddata/models/metadata/fact.rb +3 -3
- data/lib/gooddata/models/metadata/label.rb +4 -4
- data/lib/gooddata/models/metadata/metric.rb +76 -38
- data/lib/gooddata/models/metadata/report.rb +52 -17
- data/lib/gooddata/models/metadata/report_definition.rb +178 -28
- data/lib/gooddata/models/model.rb +13 -6
- data/lib/gooddata/models/process.rb +93 -30
- data/lib/gooddata/models/profile.rb +18 -20
- data/lib/gooddata/models/project.rb +344 -127
- data/lib/gooddata/models/project_creator.rb +32 -22
- data/lib/gooddata/models/project_metadata.rb +26 -14
- data/lib/gooddata/models/project_role.rb +15 -17
- data/lib/gooddata/models/report_data_result.rb +4 -0
- data/lib/gooddata/models/schedule.rb +51 -20
- data/lib/gooddata/models/schema_blueprint.rb +9 -3
- data/lib/gooddata/rest/README.md +37 -0
- data/lib/gooddata/rest/client.rb +318 -0
- data/lib/gooddata/rest/connection.rb +235 -0
- data/lib/gooddata/rest/connections/connections.rb +8 -0
- data/lib/gooddata/rest/connections/dummy_connection.rb +52 -0
- data/lib/gooddata/rest/connections/rest_client_connection.rb +177 -0
- data/lib/gooddata/rest/object.rb +32 -0
- data/lib/gooddata/rest/object_factory.rb +67 -0
- data/lib/gooddata/rest/resource.rb +17 -0
- data/lib/gooddata/rest/rest.rb +20 -0
- data/lib/gooddata/version.rb +1 -1
- data/spec/data/cc/data/source/commits.csv +4 -0
- data/spec/data/cc/data/source/devs.csv +4 -0
- data/spec/data/cc/data/source/repos.csv +3 -0
- data/spec/data/cc/devel.prm +0 -0
- data/spec/data/cc/graph/graph.grf +11 -0
- data/spec/data/cc/workspace.prm +19 -0
- data/spec/data/hello_world_process/hello_world.rb +1 -0
- data/spec/data/hello_world_process/hello_world.zip +0 -0
- data/spec/data/users.csv +12 -12
- data/spec/helpers/connection_helper.rb +6 -0
- data/spec/helpers/process_helper.rb +12 -0
- data/spec/helpers/project_helper.rb +2 -2
- data/spec/integration/command_projects_spec.rb +11 -9
- data/spec/integration/create_from_template_spec.rb +6 -2
- data/spec/integration/full_process_schedule_spec.rb +49 -36
- data/spec/integration/full_project_spec.rb +221 -256
- data/spec/integration/partial_md_export_import_spec.rb +18 -17
- data/spec/logging_in_logging_out_spec.rb +17 -8
- data/spec/spec_helper.rb +4 -2
- data/spec/unit/cli/commands/cmd_api_spec.rb +1 -1
- data/spec/unit/cli/commands/cmd_auth_spec.rb +1 -1
- data/spec/unit/cli/commands/cmd_domain_spec.rb +29 -3
- data/spec/unit/cli/commands/cmd_process_spec.rb +1 -1
- data/spec/unit/cli/commands/cmd_project_spec.rb +1 -1
- data/spec/unit/cli/commands/cmd_role_spec.rb +13 -2
- data/spec/unit/cli/commands/cmd_run_ruby_spec.rb +1 -1
- data/spec/unit/cli/commands/cmd_scaffold_spec.rb +1 -1
- data/spec/unit/cli/commands/cmd_user_spec.rb +1 -1
- data/spec/unit/commands/command_api_spec.rb +0 -19
- data/spec/unit/commands/command_auth_spec.rb +20 -13
- data/spec/unit/commands/command_dataset_spec.rb +2 -2
- data/spec/unit/commands/command_process_spec.rb +24 -21
- data/spec/unit/commands/command_projects_spec.rb +2 -2
- data/spec/unit/commands/command_scaffold_spec.rb +2 -2
- data/spec/unit/commands/command_user_spec.rb +3 -3
- data/spec/unit/core/connection_spec.rb +9 -10
- data/spec/unit/core/project_spec.rb +8 -4
- data/spec/unit/core/rest_spec.rb +6 -6
- data/spec/unit/models/domain_spec.rb +14 -7
- data/spec/unit/models/invitation_spec.rb +2 -2
- data/spec/unit/models/membership_spec.rb +5 -5
- data/spec/unit/models/metric_spec.rb +92 -0
- data/spec/unit/models/profile_spec.rb +25 -21
- data/spec/unit/models/project_blueprint_spec.rb +6 -6
- data/spec/unit/models/project_role_spec.rb +3 -5
- data/spec/unit/models/project_spec.rb +43 -37
- data/spec/unit/models/schedule_spec.rb +58 -107
- data/spec/unit/rest/resource_spec.rb +6 -0
- metadata +87 -10
- data/lib/gooddata/cli/commands/role_cmd.rb +0 -28
- data/lib/gooddata/core/connection.rb +0 -392
- data/lib/gooddata/core/threaded.rb +0 -14
- data/lib/gooddata/models/md_object.rb +0 -25
- data/lib/gooddata/models/metadata/folder.rb +0 -24
- data/spec/unit/models/md_object_spec.rb +0 -55
- data/spec/unit/models/metric.rb +0 -92
|
@@ -15,7 +15,7 @@ module GoodData
|
|
|
15
15
|
# @return [String]
|
|
16
16
|
def find_value_uri(value)
|
|
17
17
|
escaped_value = CGI.escape(value)
|
|
18
|
-
results =
|
|
18
|
+
results = client.post("#{uri}/validElements?limit=30&offset=0&order=asc&filter=#{escaped_value}", {})
|
|
19
19
|
items = results['validElements']['items']
|
|
20
20
|
if items.empty?
|
|
21
21
|
fail(AttributeElementNotFound, value)
|
|
@@ -30,7 +30,7 @@ module GoodData
|
|
|
30
30
|
def find_element_value(element_id)
|
|
31
31
|
element_id = element_id.is_a?(String) ? element_id.match(/\?id=(\d+)/)[1] : element_id
|
|
32
32
|
uri = links['elements']
|
|
33
|
-
result =
|
|
33
|
+
result = client.get(uri + "/?id=#{element_id}")
|
|
34
34
|
items = result['attributeElements']['elements']
|
|
35
35
|
if items.empty?
|
|
36
36
|
fail "Element id #{element_id} was not found"
|
|
@@ -55,7 +55,7 @@ module GoodData
|
|
|
55
55
|
# @return [Array]
|
|
56
56
|
def values(options = {})
|
|
57
57
|
limit = options[:limit] || 100
|
|
58
|
-
results =
|
|
58
|
+
results = client.post("#{uri}/validElements?limit=#{limit}&offset=0&order=asc", {})
|
|
59
59
|
results['validElements']['items'].map do |el|
|
|
60
60
|
v = el['element']
|
|
61
61
|
{
|
|
@@ -68,7 +68,7 @@ module GoodData
|
|
|
68
68
|
# Gives an attribute of current label
|
|
69
69
|
# @return [GoodData::Attibute]
|
|
70
70
|
def attribute
|
|
71
|
-
|
|
71
|
+
project.attributes(content['formOf'])
|
|
72
72
|
end
|
|
73
73
|
|
|
74
74
|
# Gives an attribute url of current label. Useful for mass actions when it does not introduce HTTP call.
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
# encoding: UTF-8
|
|
2
2
|
|
|
3
3
|
require_relative '../../goodzilla/goodzilla'
|
|
4
|
+
require_relative '../../mixins/mixins'
|
|
4
5
|
require_relative '../metadata'
|
|
5
6
|
require_relative 'metadata'
|
|
6
7
|
|
|
7
8
|
module GoodData
|
|
8
9
|
# Metric representation
|
|
9
10
|
class Metric < MdObject
|
|
11
|
+
attr_reader :json
|
|
12
|
+
|
|
13
|
+
alias_method :to_hash, :json
|
|
14
|
+
|
|
15
|
+
include GoodData::Mixin::RestResource
|
|
10
16
|
root_key :metric
|
|
11
17
|
|
|
12
18
|
PARSE_MAQL_OBJECT_REGEXP = /\[([^\]]+)\]/
|
|
@@ -17,46 +23,52 @@ module GoodData
|
|
|
17
23
|
# @param options [Hash] the options hash
|
|
18
24
|
# @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
|
|
19
25
|
# @return [Array<GoodData::MdObject> | Array<Hash>] Return the appropriate metadata objects or their representation
|
|
20
|
-
def all(options = {})
|
|
26
|
+
def all(options = { :client => GoodData.connection, :project => GoodData.project })
|
|
21
27
|
query('metrics', Metric, options)
|
|
22
28
|
end
|
|
23
29
|
|
|
24
|
-
def xcreate(options)
|
|
25
|
-
|
|
26
|
-
create(:expression => options, :extended_notation => true)
|
|
27
|
-
else
|
|
28
|
-
create(options.merge(:extended_notation => true))
|
|
29
|
-
end
|
|
30
|
+
def xcreate(metric, options = { :client => GoodData.connection, :project => GoodData.project })
|
|
31
|
+
create(metric, options.merge(:extended_notation => true))
|
|
30
32
|
end
|
|
31
33
|
|
|
32
|
-
def create(options = {})
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
def create(metric, options = { :client => GoodData.connection, :project => GoodData.project })
|
|
35
|
+
client = options[:client]
|
|
36
|
+
fail ArgumentError, 'No :client specified' if client.nil?
|
|
37
|
+
|
|
38
|
+
p = options[:project]
|
|
39
|
+
fail ArgumentError, 'No :project specified' if p.nil?
|
|
40
|
+
|
|
41
|
+
project = GoodData::Project[p, options]
|
|
42
|
+
fail ArgumentError, 'Wrong :project specified' if project.nil?
|
|
43
|
+
|
|
44
|
+
if metric.is_a?(String)
|
|
45
|
+
expression = metric || options[:expression]
|
|
46
|
+
extended_notation = options[:extended_notation] || false
|
|
38
47
|
title = options[:title]
|
|
39
48
|
summary = options[:summary]
|
|
40
|
-
|
|
41
|
-
|
|
49
|
+
else
|
|
50
|
+
title = metric[:title] || options[:title]
|
|
51
|
+
summary = metric[:summary] || options[:summary]
|
|
52
|
+
expression = metric[:expression] || options[:expression] || fail('Metric has to have its expression defined')
|
|
53
|
+
extended_notation = metric[:extended_notation] || options[:extended_notation] || false
|
|
42
54
|
end
|
|
43
55
|
|
|
44
56
|
expression = if extended_notation
|
|
45
57
|
dict = {
|
|
46
|
-
:facts =>
|
|
47
|
-
memo[item
|
|
58
|
+
:facts => project.facts.reduce({}) do |memo, item|
|
|
59
|
+
memo[item.title] = item.uri
|
|
48
60
|
memo
|
|
49
61
|
end,
|
|
50
|
-
:attributes =>
|
|
51
|
-
memo[item
|
|
62
|
+
:attributes => project.attributes.reduce({}) do |memo, item|
|
|
63
|
+
memo[item.title] = item.uri
|
|
52
64
|
memo
|
|
53
65
|
end,
|
|
54
|
-
:metrics =>
|
|
55
|
-
memo[item
|
|
66
|
+
:metrics => project.metrics.reduce({}) do |memo, item|
|
|
67
|
+
memo[item.title] = item.uri
|
|
56
68
|
memo
|
|
57
69
|
end
|
|
58
70
|
}
|
|
59
|
-
interpolated_metric = GoodData::SmallGoodZilla.interpolate_metric(expression, dict)
|
|
71
|
+
interpolated_metric = GoodData::SmallGoodZilla.interpolate_metric(expression, dict, options)
|
|
60
72
|
interpolated_metric
|
|
61
73
|
else
|
|
62
74
|
expression
|
|
@@ -77,37 +89,53 @@ module GoodData
|
|
|
77
89
|
}
|
|
78
90
|
# TODO: add test for explicitly provided identifier
|
|
79
91
|
metric['metric']['meta']['identifier'] = options[:identifier] if options[:identifier]
|
|
80
|
-
|
|
92
|
+
|
|
93
|
+
client.create(Metric, metric, :project => project)
|
|
81
94
|
end
|
|
82
95
|
|
|
83
|
-
def execute(expression, options = {})
|
|
96
|
+
def execute(expression, options = { :client => GoodData.connection })
|
|
97
|
+
# client = options[:client]
|
|
98
|
+
# fail ArgumentError, 'No :client specified' if client.nil?
|
|
99
|
+
|
|
100
|
+
options = expression if expression.is_a?(Hash)
|
|
101
|
+
|
|
84
102
|
m = if expression.is_a?(String)
|
|
85
103
|
tmp = {
|
|
86
104
|
:title => 'Temporary metric to be deleted',
|
|
87
105
|
:expression => expression
|
|
88
106
|
}.merge(options)
|
|
89
107
|
|
|
90
|
-
GoodData::Metric.create(tmp)
|
|
108
|
+
GoodData::Metric.create(tmp, options)
|
|
91
109
|
else
|
|
92
110
|
tmp = {
|
|
93
111
|
:title => 'Temporary metric to be deleted'
|
|
94
112
|
}.merge(expression)
|
|
95
|
-
GoodData::Metric.create(tmp)
|
|
113
|
+
GoodData::Metric.create(tmp, options)
|
|
96
114
|
end
|
|
97
115
|
m.execute
|
|
98
116
|
end
|
|
99
117
|
|
|
100
|
-
def xexecute(expression)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
118
|
+
def xexecute(expression, opts = { :client => GoodData.connection, :project => GoodData.project })
|
|
119
|
+
client = opts[:client]
|
|
120
|
+
fail ArgumentError, 'No :client specified' if client.nil?
|
|
121
|
+
|
|
122
|
+
p = opts[:project]
|
|
123
|
+
fail ArgumentError, 'No :project specified' if p.nil?
|
|
124
|
+
|
|
125
|
+
project = GoodData::Project[p, opts]
|
|
126
|
+
fail ArgumentError, 'Wrong :project specified' if project.nil?
|
|
127
|
+
|
|
128
|
+
execute(expression, opts.merge(:extended_notation => true))
|
|
106
129
|
end
|
|
107
130
|
end
|
|
108
131
|
|
|
109
132
|
def execute
|
|
110
|
-
|
|
133
|
+
opts = {
|
|
134
|
+
:client => client,
|
|
135
|
+
:project => project
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
res = GoodData::ReportDefinition.execute(opts.merge(:left => self))
|
|
111
139
|
res && res[0][0]
|
|
112
140
|
end
|
|
113
141
|
|
|
@@ -120,7 +148,7 @@ module GoodData
|
|
|
120
148
|
end
|
|
121
149
|
|
|
122
150
|
def validate
|
|
123
|
-
fail '
|
|
151
|
+
fail 'Metric needs to have title' if title.nil?
|
|
124
152
|
true
|
|
125
153
|
end
|
|
126
154
|
|
|
@@ -181,16 +209,26 @@ module GoodData
|
|
|
181
209
|
# Looks up the readable values of the objects used inside of MAQL epxpressions. Labels and elements titles are based on the primary label.
|
|
182
210
|
# @return [String] Ther resulting MAQL like expression
|
|
183
211
|
def pretty_expression
|
|
212
|
+
opts = {
|
|
213
|
+
:client => client,
|
|
214
|
+
:project => project
|
|
215
|
+
}
|
|
216
|
+
|
|
184
217
|
temp = expression.dup
|
|
185
|
-
expression.scan(PARSE_MAQL_OBJECT_REGEXP).
|
|
218
|
+
pairs = expression.scan(PARSE_MAQL_OBJECT_REGEXP).pmap do |uri|
|
|
186
219
|
uri = uri.first
|
|
187
220
|
if uri =~ /elements/
|
|
188
|
-
|
|
221
|
+
[uri, Attribute.find_element_value(uri, opts)]
|
|
189
222
|
else
|
|
190
|
-
|
|
191
|
-
temp.sub!(uri, obj.title)
|
|
223
|
+
[uri, GoodData::MdObject[uri, opts].title]
|
|
192
224
|
end
|
|
193
225
|
end
|
|
226
|
+
|
|
227
|
+
pairs.each do |el|
|
|
228
|
+
uri = el[0]
|
|
229
|
+
obj = el[1]
|
|
230
|
+
temp.sub!(uri, obj)
|
|
231
|
+
end
|
|
194
232
|
temp
|
|
195
233
|
end
|
|
196
234
|
end
|
|
@@ -13,14 +13,24 @@ module GoodData
|
|
|
13
13
|
# @param options [Hash] the options hash
|
|
14
14
|
# @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
|
|
15
15
|
# @return [Array<GoodData::MdObject> | Array<Hash>] Return the appropriate metadata objects or their representation
|
|
16
|
-
def all(options = {})
|
|
16
|
+
def all(options = { :client => GoodData.connection, :project => GoodData.project })
|
|
17
17
|
query('reports', Report, options)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
def create(options = {})
|
|
20
|
+
def create(options = { :client => GoodData.connection, :project => GoodData.project })
|
|
21
|
+
client = options[:client]
|
|
22
|
+
fail ArgumentError, 'No :client specified' if client.nil?
|
|
23
|
+
|
|
24
|
+
p = options[:project]
|
|
25
|
+
fail ArgumentError, 'No :project specified' if p.nil?
|
|
26
|
+
|
|
27
|
+
project = GoodData::Project[p, options]
|
|
28
|
+
fail ArgumentError, 'Wrong :project specified' if project.nil?
|
|
29
|
+
|
|
21
30
|
title = options[:title]
|
|
31
|
+
fail 'Report needs a title specified' unless title
|
|
22
32
|
summary = options[:summary] || ''
|
|
23
|
-
rd = options[:rd] || ReportDefinition.create(
|
|
33
|
+
rd = options[:rd] || ReportDefinition.create(options)
|
|
24
34
|
rd.save
|
|
25
35
|
|
|
26
36
|
report = {
|
|
@@ -39,35 +49,49 @@ module GoodData
|
|
|
39
49
|
}
|
|
40
50
|
# TODO: write test for report definitions with explicit identifiers
|
|
41
51
|
report['report']['meta']['identifier'] = options[:identifier] if options[:identifier]
|
|
42
|
-
Report
|
|
52
|
+
client.create(Report, report, :project => project)
|
|
43
53
|
end
|
|
44
54
|
end
|
|
45
55
|
|
|
56
|
+
# Add a report definition to a report. This will show on a UI as a new version.
|
|
57
|
+
#
|
|
58
|
+
# @param report_definition [GoodData::ReportDefinition | String] Report definition to add. Either it can be a URI of a report definition or an actual report definition object.
|
|
59
|
+
# @return [GoodData::Report] Return self
|
|
60
|
+
def add_definition(report_definition)
|
|
61
|
+
rep_def = project.report_definitions(report_definition)
|
|
62
|
+
content['definitions'] = definition_uris << rep_def.uri
|
|
63
|
+
self
|
|
64
|
+
end
|
|
65
|
+
|
|
46
66
|
def results
|
|
47
67
|
content['results']
|
|
48
68
|
end
|
|
49
69
|
|
|
50
70
|
def definitions
|
|
71
|
+
content['definitions'].pmap { |uri| project.report_definitions(uri) }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def definition_uris
|
|
51
75
|
content['definitions']
|
|
52
76
|
end
|
|
53
77
|
|
|
54
78
|
def latest_report_definition_uri
|
|
55
|
-
|
|
79
|
+
definition_uris.last
|
|
56
80
|
end
|
|
57
81
|
|
|
58
82
|
def latest_report_definition
|
|
59
|
-
|
|
83
|
+
project.report_definitions(latest_report_definition_uri)
|
|
60
84
|
end
|
|
61
85
|
|
|
62
86
|
def remove_definition(definition)
|
|
63
87
|
def_uri = is_a?(GoodData::ReportDefinition) ? definition.uri : definition
|
|
64
|
-
content['definitions'] =
|
|
88
|
+
content['definitions'] = definition_uris.reject { |x| x == def_uri }
|
|
65
89
|
self
|
|
66
90
|
end
|
|
67
91
|
|
|
68
92
|
# TODO: Cover with test. You would probably need something that will be able to create a report easily from a definition
|
|
69
93
|
def remove_definition_but_latest
|
|
70
|
-
to_remove =
|
|
94
|
+
to_remove = definition_uris - [latest_report_definition_uri]
|
|
71
95
|
to_remove.each do |uri|
|
|
72
96
|
remove_definition(uri)
|
|
73
97
|
end
|
|
@@ -75,25 +99,29 @@ module GoodData
|
|
|
75
99
|
end
|
|
76
100
|
|
|
77
101
|
def purge_report_of_unused_definitions!
|
|
78
|
-
full_list =
|
|
102
|
+
full_list = definition_uris
|
|
79
103
|
remove_definition_but_latest
|
|
80
|
-
purged_list =
|
|
104
|
+
purged_list = definition_uris
|
|
81
105
|
to_remove = full_list - purged_list
|
|
82
106
|
save
|
|
83
|
-
to_remove.each { |uri|
|
|
107
|
+
to_remove.each { |uri| client.delete(uri) }
|
|
84
108
|
self
|
|
85
109
|
end
|
|
86
110
|
|
|
87
111
|
def execute
|
|
88
112
|
fail 'You have to save the report before executing. If you do not want to do that please use GoodData::ReportDefinition' unless saved?
|
|
89
|
-
result =
|
|
113
|
+
result = client.post '/gdc/xtab2/executor3', 'report_req' => { 'report' => uri }
|
|
90
114
|
data_result_uri = result['execResult']['dataResult']
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
115
|
+
|
|
116
|
+
result = client.poll_on_response(data_result_uri) do |body|
|
|
117
|
+
body && body['taskState'] && body['taskState']['status'] == 'WAIT'
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
if result.empty?
|
|
121
|
+
client.create(EmptyResult, result)
|
|
122
|
+
else
|
|
123
|
+
client.create(ReportDataResult, result)
|
|
95
124
|
end
|
|
96
|
-
ReportDataResult.new(GoodData.get data_result_uri)
|
|
97
125
|
end
|
|
98
126
|
|
|
99
127
|
def exportable?
|
|
@@ -105,5 +133,12 @@ module GoodData
|
|
|
105
133
|
result1 = GoodData.post('/gdc/exporter/executor', :result_req => { :format => format, :result => result })
|
|
106
134
|
GoodData.poll_on_code(result1['uri'], process: false)
|
|
107
135
|
end
|
|
136
|
+
|
|
137
|
+
def replace(what, for_what)
|
|
138
|
+
new_defs = definitions.map do |rep_def|
|
|
139
|
+
rep_def.replace(what, for_what)
|
|
140
|
+
end
|
|
141
|
+
new_defs.pmap(&:save)
|
|
142
|
+
end
|
|
108
143
|
end
|
|
109
144
|
end
|
|
@@ -16,7 +16,7 @@ module GoodData
|
|
|
16
16
|
# @param options [Hash] the options hash
|
|
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
|
-
def all(options = {})
|
|
19
|
+
def all(options = { :client => GoodData.connection, :project => GoodData.project })
|
|
20
20
|
query('reportdefinition', ReportDefinition, options)
|
|
21
21
|
end
|
|
22
22
|
|
|
@@ -60,20 +60,23 @@ module GoodData
|
|
|
60
60
|
parts
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
-
def find(stuff)
|
|
63
|
+
def find(stuff, opts = { :client => GoodData.connection, :project => GoodData.project })
|
|
64
|
+
client = opts[:client]
|
|
65
|
+
fail ArgumentError, 'No :client specified' if client.nil?
|
|
66
|
+
|
|
64
67
|
stuff.map do |item|
|
|
65
68
|
if item.respond_to?(:attribute?) && item.attribute?
|
|
66
69
|
item.display_forms.first
|
|
67
70
|
elsif item.is_a?(String)
|
|
68
|
-
x = GoodData::MdObject.get_by_id(item)
|
|
71
|
+
x = GoodData::MdObject.get_by_id(item, opts)
|
|
69
72
|
fail "Object given by id \"#{item}\" could not be found" if x.nil?
|
|
70
73
|
case x.raw_data.keys.first.to_s
|
|
71
74
|
when 'attribute'
|
|
72
|
-
GoodData::Attribute.new(x.
|
|
75
|
+
GoodData::Attribute.new(x.json).display_forms.first
|
|
73
76
|
when 'attributeDisplayForm'
|
|
74
|
-
GoodData::Label.new(x.
|
|
77
|
+
GoodData::Label.new(x.json)
|
|
75
78
|
when 'metric'
|
|
76
|
-
GoodData::Metric.new(x.
|
|
79
|
+
GoodData::Metric.new(x.json)
|
|
77
80
|
end
|
|
78
81
|
elsif item.is_a?(Hash) && item.keys.include?(:title)
|
|
79
82
|
case item[:type].to_s
|
|
@@ -120,48 +123,71 @@ module GoodData
|
|
|
120
123
|
begin
|
|
121
124
|
unsaved_metrics.each { |m| m.save }
|
|
122
125
|
rd = GoodData::ReportDefinition.create(options)
|
|
123
|
-
data_result(execute_inline(rd))
|
|
126
|
+
data_result(execute_inline(rd, options), options)
|
|
124
127
|
ensure
|
|
125
128
|
unsaved_metrics.each { |m| m.delete if m && m.saved? }
|
|
126
129
|
end
|
|
127
130
|
end
|
|
128
131
|
|
|
129
|
-
def execute_inline(rd)
|
|
130
|
-
|
|
132
|
+
def execute_inline(rd, opts = { :client => GoodData.connection, :project => GoodData.project })
|
|
133
|
+
client = opts[:client]
|
|
134
|
+
fail ArgumentError, 'No :client specified' if client.nil?
|
|
135
|
+
|
|
136
|
+
p = opts[:project]
|
|
137
|
+
fail ArgumentError, 'No :project specified' if p.nil?
|
|
138
|
+
|
|
139
|
+
project = GoodData::Project[p, opts]
|
|
140
|
+
fail ArgumentError, 'Wrong :project specified' if project.nil?
|
|
141
|
+
|
|
142
|
+
rd = rd.respond_to?(:json) ? rd.json : rd
|
|
131
143
|
data = {
|
|
132
144
|
report_req: {
|
|
133
145
|
definitionContent: {
|
|
134
146
|
content: rd,
|
|
135
|
-
projectMetadata:
|
|
147
|
+
projectMetadata: project.links['metadata']
|
|
136
148
|
}
|
|
137
149
|
}
|
|
138
150
|
}
|
|
139
|
-
uri = "/gdc/app/projects/#{
|
|
140
|
-
|
|
151
|
+
uri = "/gdc/app/projects/#{project.pid}/execute"
|
|
152
|
+
|
|
153
|
+
client.post(uri, data)
|
|
141
154
|
end
|
|
142
155
|
|
|
143
156
|
# TODO: refactor the method. It should be instance method
|
|
144
157
|
# Method used for getting a data_result from a wire representation of
|
|
145
158
|
# @param result [Hash, Object] Wire data from JSON
|
|
146
159
|
# @return [GoodData::ReportDataResult]
|
|
147
|
-
def data_result(result)
|
|
160
|
+
def data_result(result, options = { :client => GoodData.connection })
|
|
161
|
+
client = options[:client]
|
|
162
|
+
fail ArgumentError, 'No :client specified' if client.nil?
|
|
163
|
+
|
|
148
164
|
data_result_uri = result['execResult']['dataResult']
|
|
149
|
-
result =
|
|
165
|
+
result = client.poll_on_response(data_result_uri) do |body|
|
|
166
|
+
body && body['taskState'] && body['taskState']['status'] == 'WAIT'
|
|
167
|
+
end
|
|
150
168
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
169
|
+
if result.empty?
|
|
170
|
+
client.create(EmptyResult, result)
|
|
171
|
+
else
|
|
172
|
+
client.create(ReportDataResult, result)
|
|
154
173
|
end
|
|
155
|
-
return nil unless result
|
|
156
|
-
ReportDataResult.new(GoodData.get data_result_uri)
|
|
157
174
|
end
|
|
158
175
|
|
|
159
|
-
def create(options = {})
|
|
176
|
+
def create(options = { :client => GoodData.connection, :project => GoodData.project })
|
|
177
|
+
client = options[:client]
|
|
178
|
+
fail ArgumentError, 'No :client specified' if client.nil?
|
|
179
|
+
|
|
180
|
+
p = options[:project]
|
|
181
|
+
fail ArgumentError, 'No :project specified' if p.nil?
|
|
182
|
+
|
|
183
|
+
project = GoodData::Project[p, options]
|
|
184
|
+
fail ArgumentError, 'Wrong :project specified' if project.nil?
|
|
185
|
+
|
|
160
186
|
left = Array(options[:left])
|
|
161
187
|
top = Array(options[:top])
|
|
162
188
|
|
|
163
|
-
left = ReportDefinition.find(left)
|
|
164
|
-
top = ReportDefinition.find(top)
|
|
189
|
+
left = ReportDefinition.find(left, options)
|
|
190
|
+
top = ReportDefinition.find(top, options)
|
|
165
191
|
|
|
166
192
|
# TODO: Put somewhere for i18n
|
|
167
193
|
fail_msg = 'All metrics in report definition must be saved'
|
|
@@ -193,24 +219,148 @@ module GoodData
|
|
|
193
219
|
# TODO: write test for report definitions with explicit identifiers
|
|
194
220
|
pars['reportDefinition']['meta']['identifier'] = options[:identifier] if options[:identifier]
|
|
195
221
|
|
|
196
|
-
|
|
222
|
+
client.create(ReportDefinition, pars, :project => project)
|
|
197
223
|
end
|
|
198
224
|
end
|
|
199
225
|
|
|
226
|
+
def attribute_parts
|
|
227
|
+
cols = content['grid']['columns'] || []
|
|
228
|
+
rows = content['grid']['rows'] || []
|
|
229
|
+
items = cols + rows
|
|
230
|
+
items.select { |item| item.is_a?(Hash) && item.keys.first == 'attribute' }
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def attributes
|
|
234
|
+
labels.map { |label| label.attribute }
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def labels
|
|
238
|
+
attribute_parts.map { |part| project.labels(part['attribute']['uri']) }
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def metric_parts
|
|
242
|
+
content['grid']['metrics']
|
|
243
|
+
end
|
|
244
|
+
|
|
200
245
|
def metrics
|
|
201
|
-
|
|
246
|
+
metric_parts.map { |i| project.metrics(i['uri']) }
|
|
202
247
|
end
|
|
203
248
|
|
|
204
|
-
def execute
|
|
249
|
+
def execute(opts = { :client => GoodData.connection, :project => GoodData.project })
|
|
250
|
+
opts = {
|
|
251
|
+
:client => client,
|
|
252
|
+
:project => project
|
|
253
|
+
}
|
|
254
|
+
|
|
205
255
|
result = if saved?
|
|
206
256
|
pars = {
|
|
207
257
|
'report_req' => { 'reportDefinition' => uri }
|
|
208
258
|
}
|
|
209
|
-
|
|
259
|
+
client.post '/gdc/xtab2/executor', pars
|
|
210
260
|
else
|
|
211
|
-
ReportDefinition.execute_inline(self)
|
|
261
|
+
ReportDefinition.execute_inline(self, opts)
|
|
212
262
|
end
|
|
213
|
-
ReportDefinition.data_result(result)
|
|
263
|
+
ReportDefinition.data_result(result, opts)
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def filters
|
|
267
|
+
content['filters'].map { |f| f['expression'] }
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Replace certain object in report definition. Returns new definition which is not saved.
|
|
271
|
+
#
|
|
272
|
+
# @param what [GoodData::MdObject | String] Object which responds to uri or a string that should be replaced
|
|
273
|
+
# @option for_what [GoodData::MdObject | String] Object which responds to uri or a string that should used as replacement
|
|
274
|
+
# @return [Array<GoodData::MdObject> | Array<Hash>] Return the appropriate metadata objects or their representation
|
|
275
|
+
def replace(what, for_what = nil)
|
|
276
|
+
pairs = if what.is_a?(Hash)
|
|
277
|
+
whats = what.keys
|
|
278
|
+
to_whats = what.values
|
|
279
|
+
whats.zip(to_whats)
|
|
280
|
+
else
|
|
281
|
+
[[what, for_what]]
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
pairs.each do |pair|
|
|
285
|
+
what = pair[0]
|
|
286
|
+
for_what = pair[1]
|
|
287
|
+
|
|
288
|
+
uri_what = what.respond_to?(:uri) ? what.uri : what
|
|
289
|
+
uri_for_what = for_what.respond_to?(:uri) ? for_what.uri : for_what
|
|
290
|
+
|
|
291
|
+
content['grid']['metrics'] = metric_parts.map do |item|
|
|
292
|
+
item.deep_dup.tap do |i|
|
|
293
|
+
i['uri'].gsub!(uri_what, uri_for_what)
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
cols = content['grid']['columns'] || []
|
|
298
|
+
content['grid']['columns'] = cols.map do |item|
|
|
299
|
+
if item.is_a?(Hash)
|
|
300
|
+
item.deep_dup.tap do |i|
|
|
301
|
+
i['attribute']['uri'].gsub!(uri_what, uri_for_what)
|
|
302
|
+
end
|
|
303
|
+
else
|
|
304
|
+
item
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
rows = content['grid']['rows'] || []
|
|
309
|
+
content['grid']['rows'] = rows.map do |item|
|
|
310
|
+
if item.is_a?(Hash)
|
|
311
|
+
item.deep_dup.tap do |i|
|
|
312
|
+
i['attribute']['uri'].gsub!(uri_what, uri_for_what)
|
|
313
|
+
end
|
|
314
|
+
else
|
|
315
|
+
item
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
widths = content['grid']['columnWidths'] || []
|
|
320
|
+
content['grid']['columnWidths'] = widths.map do |item|
|
|
321
|
+
if item.is_a?(Hash)
|
|
322
|
+
item.deep_dup.tap do |i|
|
|
323
|
+
if i['locator'][0].key?('attributeHeaderLocator')
|
|
324
|
+
i['locator'][0]['attributeHeaderLocator']['uri'].gsub!(uri_what, uri_for_what)
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
else
|
|
328
|
+
item
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
sort = content['grid']['sort']['columns'] || []
|
|
333
|
+
content['grid']['sort']['columns'] = sort.map do |item|
|
|
334
|
+
if item.is_a?(Hash)
|
|
335
|
+
item.deep_dup.tap do |i|
|
|
336
|
+
next unless i.key?('metricSort')
|
|
337
|
+
next unless i['metricSort'].key?('locators')
|
|
338
|
+
next unless i['metricSort']['locators'][0].key?('attributeLocator2')
|
|
339
|
+
i['metricSort']['locators'][0]['attributeLocator2']['uri'].gsub!(uri_what, uri_for_what)
|
|
340
|
+
i['metricSort']['locators'][0]['attributeLocator2']['element'].gsub!(uri_what, uri_for_what)
|
|
341
|
+
end
|
|
342
|
+
else
|
|
343
|
+
item
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
if content.key?('chart')
|
|
348
|
+
content['chart']['buckets'] = content['chart']['buckets'].reduce({}) do |a, e|
|
|
349
|
+
key = e[0]
|
|
350
|
+
val = e[1]
|
|
351
|
+
# binding.pry
|
|
352
|
+
a[key] = val.map do |item|
|
|
353
|
+
item.deep_dup.tap do |i|
|
|
354
|
+
i['uri'].gsub!(uri_what, uri_for_what)
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
a
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
content['filters'] = filters.map { |filter_expression| { 'expression' => filter_expression.gsub(uri_what, uri_for_what) } }
|
|
362
|
+
end
|
|
363
|
+
self
|
|
214
364
|
end
|
|
215
365
|
end
|
|
216
366
|
end
|