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.
Files changed (145) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -1
  3. data/README.md +10 -2
  4. data/TODO.md +32 -0
  5. data/gooddata.gemspec +5 -0
  6. data/lib/gooddata.rb +4 -0
  7. data/lib/gooddata/app/app.rb +12 -0
  8. data/lib/gooddata/bricks/middleware/gooddata_middleware.rb +4 -3
  9. data/lib/gooddata/bricks/middleware/restforce_middleware.rb +2 -1
  10. data/lib/gooddata/cli/commands/console_cmd.rb +23 -5
  11. data/lib/gooddata/cli/commands/domain_cmd.rb +9 -10
  12. data/lib/gooddata/cli/commands/process_cmd.rb +11 -9
  13. data/lib/gooddata/cli/commands/project_cmd.rb +25 -27
  14. data/lib/gooddata/cli/commands/projects_cmd.rb +2 -2
  15. data/lib/gooddata/cli/commands/run_ruby_cmd.rb +1 -1
  16. data/lib/gooddata/cli/commands/user_cmd.rb +2 -2
  17. data/lib/gooddata/cli/hooks.rb +4 -2
  18. data/lib/gooddata/cli/shared.rb +1 -1
  19. data/lib/gooddata/cli/terminal.rb +1 -1
  20. data/lib/gooddata/commands/api.rb +1 -1
  21. data/lib/gooddata/commands/auth.rb +4 -28
  22. data/lib/gooddata/commands/domain.rb +9 -4
  23. data/lib/gooddata/commands/process.rb +26 -23
  24. data/lib/gooddata/commands/project.rb +74 -50
  25. data/lib/gooddata/commands/projects.rb +3 -2
  26. data/lib/gooddata/commands/role.rb +9 -3
  27. data/lib/gooddata/commands/user.rb +6 -4
  28. data/lib/gooddata/connection.rb +11 -45
  29. data/lib/gooddata/core/logging.rb +0 -1
  30. data/lib/gooddata/core/project.rb +22 -22
  31. data/lib/gooddata/core/rest.rb +9 -8
  32. data/lib/gooddata/core/user.rb +0 -11
  33. data/lib/gooddata/exceptions/project_not_found.rb +1 -0
  34. data/lib/gooddata/extensions/enumerable.rb +10 -0
  35. data/lib/gooddata/extensions/hash.rb +25 -0
  36. data/lib/gooddata/goodzilla/goodzilla.rb +4 -4
  37. data/lib/gooddata/helper/class_helper.rb +1 -0
  38. data/lib/gooddata/helper/helpers.rb +8 -0
  39. data/lib/gooddata/helpers/auth_helpers.rb +41 -0
  40. data/lib/gooddata/mixins/author.rb +1 -1
  41. data/lib/gooddata/mixins/contributor.rb +1 -1
  42. data/lib/gooddata/mixins/data_property_reader.rb +2 -0
  43. data/lib/gooddata/mixins/data_property_writer.rb +2 -0
  44. data/lib/gooddata/mixins/inspector.rb +49 -0
  45. data/lib/gooddata/mixins/md_finders.rb +16 -8
  46. data/lib/gooddata/mixins/md_id_to_uri.rb +12 -4
  47. data/lib/gooddata/mixins/md_object_indexer.rb +15 -4
  48. data/lib/gooddata/mixins/md_object_query.rb +42 -20
  49. data/lib/gooddata/mixins/md_relations.rb +21 -12
  50. data/lib/gooddata/mixins/meta_getter.rb +2 -0
  51. data/lib/gooddata/mixins/meta_property_reader.rb +2 -0
  52. data/lib/gooddata/mixins/meta_property_writer.rb +2 -0
  53. data/lib/gooddata/mixins/rest_resource.rb +32 -10
  54. data/lib/gooddata/mixins/root_key_getter.rb +1 -1
  55. data/lib/gooddata/models/data_result.rb +3 -1
  56. data/lib/gooddata/models/domain.rb +31 -22
  57. data/lib/gooddata/models/empty_result.rb +22 -0
  58. data/lib/gooddata/models/invitation.rb +11 -9
  59. data/lib/gooddata/models/links.rb +5 -3
  60. data/lib/gooddata/models/membership.rb +23 -28
  61. data/lib/gooddata/models/metadata.rb +35 -35
  62. data/lib/gooddata/models/metadata/attribute.rb +10 -8
  63. data/lib/gooddata/models/metadata/dashboard.rb +1 -1
  64. data/lib/gooddata/models/metadata/fact.rb +3 -3
  65. data/lib/gooddata/models/metadata/label.rb +4 -4
  66. data/lib/gooddata/models/metadata/metric.rb +76 -38
  67. data/lib/gooddata/models/metadata/report.rb +52 -17
  68. data/lib/gooddata/models/metadata/report_definition.rb +178 -28
  69. data/lib/gooddata/models/model.rb +13 -6
  70. data/lib/gooddata/models/process.rb +93 -30
  71. data/lib/gooddata/models/profile.rb +18 -20
  72. data/lib/gooddata/models/project.rb +344 -127
  73. data/lib/gooddata/models/project_creator.rb +32 -22
  74. data/lib/gooddata/models/project_metadata.rb +26 -14
  75. data/lib/gooddata/models/project_role.rb +15 -17
  76. data/lib/gooddata/models/report_data_result.rb +4 -0
  77. data/lib/gooddata/models/schedule.rb +51 -20
  78. data/lib/gooddata/models/schema_blueprint.rb +9 -3
  79. data/lib/gooddata/rest/README.md +37 -0
  80. data/lib/gooddata/rest/client.rb +318 -0
  81. data/lib/gooddata/rest/connection.rb +235 -0
  82. data/lib/gooddata/rest/connections/connections.rb +8 -0
  83. data/lib/gooddata/rest/connections/dummy_connection.rb +52 -0
  84. data/lib/gooddata/rest/connections/rest_client_connection.rb +177 -0
  85. data/lib/gooddata/rest/object.rb +32 -0
  86. data/lib/gooddata/rest/object_factory.rb +67 -0
  87. data/lib/gooddata/rest/resource.rb +17 -0
  88. data/lib/gooddata/rest/rest.rb +20 -0
  89. data/lib/gooddata/version.rb +1 -1
  90. data/spec/data/cc/data/source/commits.csv +4 -0
  91. data/spec/data/cc/data/source/devs.csv +4 -0
  92. data/spec/data/cc/data/source/repos.csv +3 -0
  93. data/spec/data/cc/devel.prm +0 -0
  94. data/spec/data/cc/graph/graph.grf +11 -0
  95. data/spec/data/cc/workspace.prm +19 -0
  96. data/spec/data/hello_world_process/hello_world.rb +1 -0
  97. data/spec/data/hello_world_process/hello_world.zip +0 -0
  98. data/spec/data/users.csv +12 -12
  99. data/spec/helpers/connection_helper.rb +6 -0
  100. data/spec/helpers/process_helper.rb +12 -0
  101. data/spec/helpers/project_helper.rb +2 -2
  102. data/spec/integration/command_projects_spec.rb +11 -9
  103. data/spec/integration/create_from_template_spec.rb +6 -2
  104. data/spec/integration/full_process_schedule_spec.rb +49 -36
  105. data/spec/integration/full_project_spec.rb +221 -256
  106. data/spec/integration/partial_md_export_import_spec.rb +18 -17
  107. data/spec/logging_in_logging_out_spec.rb +17 -8
  108. data/spec/spec_helper.rb +4 -2
  109. data/spec/unit/cli/commands/cmd_api_spec.rb +1 -1
  110. data/spec/unit/cli/commands/cmd_auth_spec.rb +1 -1
  111. data/spec/unit/cli/commands/cmd_domain_spec.rb +29 -3
  112. data/spec/unit/cli/commands/cmd_process_spec.rb +1 -1
  113. data/spec/unit/cli/commands/cmd_project_spec.rb +1 -1
  114. data/spec/unit/cli/commands/cmd_role_spec.rb +13 -2
  115. data/spec/unit/cli/commands/cmd_run_ruby_spec.rb +1 -1
  116. data/spec/unit/cli/commands/cmd_scaffold_spec.rb +1 -1
  117. data/spec/unit/cli/commands/cmd_user_spec.rb +1 -1
  118. data/spec/unit/commands/command_api_spec.rb +0 -19
  119. data/spec/unit/commands/command_auth_spec.rb +20 -13
  120. data/spec/unit/commands/command_dataset_spec.rb +2 -2
  121. data/spec/unit/commands/command_process_spec.rb +24 -21
  122. data/spec/unit/commands/command_projects_spec.rb +2 -2
  123. data/spec/unit/commands/command_scaffold_spec.rb +2 -2
  124. data/spec/unit/commands/command_user_spec.rb +3 -3
  125. data/spec/unit/core/connection_spec.rb +9 -10
  126. data/spec/unit/core/project_spec.rb +8 -4
  127. data/spec/unit/core/rest_spec.rb +6 -6
  128. data/spec/unit/models/domain_spec.rb +14 -7
  129. data/spec/unit/models/invitation_spec.rb +2 -2
  130. data/spec/unit/models/membership_spec.rb +5 -5
  131. data/spec/unit/models/metric_spec.rb +92 -0
  132. data/spec/unit/models/profile_spec.rb +25 -21
  133. data/spec/unit/models/project_blueprint_spec.rb +6 -6
  134. data/spec/unit/models/project_role_spec.rb +3 -5
  135. data/spec/unit/models/project_spec.rb +43 -37
  136. data/spec/unit/models/schedule_spec.rb +58 -107
  137. data/spec/unit/rest/resource_spec.rb +6 -0
  138. metadata +87 -10
  139. data/lib/gooddata/cli/commands/role_cmd.rb +0 -28
  140. data/lib/gooddata/core/connection.rb +0 -392
  141. data/lib/gooddata/core/threaded.rb +0 -14
  142. data/lib/gooddata/models/md_object.rb +0 -25
  143. data/lib/gooddata/models/metadata/folder.rb +0 -24
  144. data/spec/unit/models/md_object_spec.rb +0 -55
  145. 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 = GoodData.post("#{uri}/validElements?limit=30&offset=0&order=asc&filter=#{escaped_value}", {})
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 = GoodData.get(uri + "/?id=#{element_id}")
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 = GoodData.post("#{uri}/validElements?limit=#{limit}&offset=0&order=asc", {})
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
- GoodData::Attribute[content['formOf']]
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
- if options.is_a?(String)
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
- if options.is_a?(String)
34
- expression = options
35
- extended_notation = false
36
- title = nil
37
- else
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
- expression = options[:expression] || fail('Metric has to have its expression defined')
41
- extended_notation = options[:extended_notation] || false
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 => GoodData::Fact[:all].reduce({}) do |memo, item|
47
- memo[item['title']] = item['link']
58
+ :facts => project.facts.reduce({}) do |memo, item|
59
+ memo[item.title] = item.uri
48
60
  memo
49
61
  end,
50
- :attributes => GoodData::Attribute[:all].reduce({}) do |memo, item|
51
- memo[item['title']] = item['link']
62
+ :attributes => project.attributes.reduce({}) do |memo, item|
63
+ memo[item.title] = item.uri
52
64
  memo
53
65
  end,
54
- :metrics => GoodData::Metric[:all].reduce({}) do |memo, item|
55
- memo[item['title']] = item['link']
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
- Metric.new(metric)
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
- if expression.is_a?(String)
102
- execute(:expression => expression, :extended_notation => true)
103
- else
104
- execute(expression.merge(:extended_notation => true))
105
- end
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
- res = GoodData::ReportDefinition.execute(:left => self)
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 'Meric needs to have title' if title.nil?
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).each do |uri|
218
+ pairs = expression.scan(PARSE_MAQL_OBJECT_REGEXP).pmap do |uri|
186
219
  uri = uri.first
187
220
  if uri =~ /elements/
188
- temp.sub!(uri, Attribute.find_element_value(uri))
221
+ [uri, Attribute.find_element_value(uri, opts)]
189
222
  else
190
- obj = GoodData::MdObject[uri]
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(:top => options[:top], :left => options[:left])
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.new 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
- definitions.last
79
+ definition_uris.last
56
80
  end
57
81
 
58
82
  def latest_report_definition
59
- GoodData::MdObject[latest_report_definition_uri]
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'] = definitions.reject { |x| x == def_uri }
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 = definitions - [latest_report_definition_uri]
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 = definitions
102
+ full_list = definition_uris
79
103
  remove_definition_but_latest
80
- purged_list = definitions
104
+ purged_list = definition_uris
81
105
  to_remove = full_list - purged_list
82
106
  save
83
- to_remove.each { |uri| GoodData.delete(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 = GoodData.post '/gdc/xtab2/executor3', 'report_req' => { 'report' => uri }
113
+ result = client.post '/gdc/xtab2/executor3', 'report_req' => { 'report' => uri }
90
114
  data_result_uri = result['execResult']['dataResult']
91
- result = GoodData.get data_result_uri
92
- while result['taskState'] && result['taskState']['status'] == 'WAIT'
93
- sleep 10
94
- result = GoodData.get data_result_uri
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.raw_data).display_forms.first
75
+ GoodData::Attribute.new(x.json).display_forms.first
73
76
  when 'attributeDisplayForm'
74
- GoodData::Label.new(x.raw_data)
77
+ GoodData::Label.new(x.json)
75
78
  when 'metric'
76
- GoodData::Metric.new(x.raw_data)
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
- rd = rd.respond_to?(:raw_data) ? rd.raw_data : rd
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: GoodData.project.links['metadata']
147
+ projectMetadata: project.links['metadata']
136
148
  }
137
149
  }
138
150
  }
139
- uri = "/gdc/app/projects/#{GoodData.project.pid}/execute"
140
- GoodData.post(uri, data)
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 = GoodData.get data_result_uri
165
+ result = client.poll_on_response(data_result_uri) do |body|
166
+ body && body['taskState'] && body['taskState']['status'] == 'WAIT'
167
+ end
150
168
 
151
- while result && result['taskState'] && result['taskState']['status'] == 'WAIT'
152
- sleep 10
153
- result = GoodData.get data_result_uri
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
- ReportDefinition.new(pars)
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
- content['grid']['metrics'].map { |i| GoodData::Metric[i['uri']] }
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
- GoodData.post '/gdc/xtab2/executor', pars
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