gooddata 0.6.4 → 0.6.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.markdown +4 -0
  3. data/README.md +1 -0
  4. data/lib/gooddata/cli/commands/project_cmd.rb +2 -7
  5. data/lib/gooddata/client.rb +0 -2
  6. data/lib/gooddata/commands/project.rb +10 -0
  7. data/lib/gooddata/core/rest.rb +12 -2
  8. data/lib/gooddata/exceptions/attr_element_not_found.rb +12 -0
  9. data/lib/gooddata/extensions/enumerable.rb +12 -0
  10. data/lib/gooddata/helpers/global_helpers.rb +20 -0
  11. data/lib/gooddata/mixins/author.rb +16 -0
  12. data/lib/gooddata/mixins/content_getter.rb +11 -0
  13. data/lib/gooddata/mixins/content_property_reader.rb +13 -0
  14. data/lib/gooddata/mixins/content_property_writer.rb +13 -0
  15. data/lib/gooddata/mixins/contributor.rb +16 -0
  16. data/lib/gooddata/mixins/data_getter.rb +11 -0
  17. data/lib/gooddata/mixins/data_property_reader.rb +13 -0
  18. data/lib/gooddata/mixins/data_property_writer.rb +13 -0
  19. data/lib/gooddata/mixins/is_attribute.rb +13 -0
  20. data/lib/gooddata/mixins/is_fact.rb +13 -0
  21. data/lib/gooddata/mixins/is_label.rb +15 -0
  22. data/lib/gooddata/mixins/links.rb +11 -0
  23. data/lib/gooddata/mixins/md_finders.rb +36 -0
  24. data/lib/gooddata/mixins/md_id_to_uri.rb +29 -0
  25. data/lib/gooddata/mixins/md_json.rb +11 -0
  26. data/lib/gooddata/mixins/md_object_id.rb +11 -0
  27. data/lib/gooddata/mixins/md_object_indexer.rb +36 -0
  28. data/lib/gooddata/mixins/md_object_query.rb +83 -0
  29. data/lib/gooddata/mixins/md_relations.rb +39 -0
  30. data/lib/gooddata/mixins/meta_getter.rb +11 -0
  31. data/lib/gooddata/mixins/meta_property_reader.rb +13 -0
  32. data/lib/gooddata/mixins/meta_property_writer.rb +13 -0
  33. data/lib/gooddata/mixins/mixins.rb +15 -0
  34. data/lib/gooddata/mixins/not_attribute.rb +13 -0
  35. data/lib/gooddata/mixins/not_exportable.rb +11 -0
  36. data/lib/gooddata/mixins/not_fact.rb +13 -0
  37. data/lib/gooddata/mixins/not_label.rb +15 -0
  38. data/lib/gooddata/mixins/not_metric.rb +13 -0
  39. data/lib/gooddata/mixins/obj_id.rb +11 -0
  40. data/lib/gooddata/mixins/rest_getters.rb +13 -0
  41. data/lib/gooddata/mixins/rest_resource.rb +19 -0
  42. data/lib/gooddata/mixins/root_key_getter.rb +11 -0
  43. data/lib/gooddata/mixins/root_key_setter.rb +11 -0
  44. data/lib/gooddata/mixins/timestamps.rb +15 -0
  45. data/lib/gooddata/models/from_wire.rb +153 -0
  46. data/lib/gooddata/models/metadata.rb +28 -230
  47. data/lib/gooddata/models/metadata/attribute.rb +4 -6
  48. data/lib/gooddata/models/metadata/fact.rb +4 -6
  49. data/lib/gooddata/models/metadata/{display_form.rb → label.rb} +17 -11
  50. data/lib/gooddata/models/metadata/metric.rb +1 -1
  51. data/lib/gooddata/models/metadata/report_definition.rb +2 -2
  52. data/lib/gooddata/models/model.rb +55 -23
  53. data/lib/gooddata/models/models.rb +0 -2
  54. data/lib/gooddata/models/module_constants.rb +0 -2
  55. data/lib/gooddata/models/process.rb +1 -1
  56. data/lib/gooddata/models/project.rb +117 -76
  57. data/lib/gooddata/models/project_blueprint.rb +322 -42
  58. data/lib/gooddata/models/project_creator.rb +5 -4
  59. data/lib/gooddata/models/project_role.rb +20 -55
  60. data/lib/gooddata/models/schema_blueprint.rb +287 -84
  61. data/lib/gooddata/models/schema_builder.rb +0 -4
  62. data/lib/gooddata/models/to_manifest.rb +160 -0
  63. data/lib/gooddata/models/to_wire.rb +150 -0
  64. data/lib/gooddata/version.rb +1 -1
  65. data/spec/data/blueprint_invalid.json +3 -1
  66. data/spec/data/gd_gse_data_blueprint.json +1370 -0
  67. data/spec/data/gd_gse_data_manifest.json +1424 -0
  68. data/spec/data/gd_gse_data_model.json +1772 -0
  69. data/spec/data/manifest_test_project.json +116 -0
  70. data/spec/data/model_view.json +1772 -0
  71. data/spec/data/superfluous_titles_view.json +81 -0
  72. data/spec/data/test_project_model_spec.json +7 -4
  73. data/spec/data/wire_test_project.json +143 -0
  74. data/spec/helpers/crypto_helper.rb +9 -0
  75. data/spec/helpers/project_helper.rb +2 -0
  76. data/spec/integration/command_projects_spec.rb +4 -2
  77. data/spec/integration/full_project_spec.rb +51 -18
  78. data/spec/integration/partial_md_export_import_spec.rb +1 -1
  79. data/spec/spec_helper.rb +2 -1
  80. data/spec/unit/models/attribute_column_spec.rb +7 -7
  81. data/spec/unit/models/domain_spec.rb +2 -2
  82. data/spec/unit/models/from_wire_spec.rb +119 -0
  83. data/spec/unit/models/metadata_spec.rb +4 -2
  84. data/spec/unit/models/project_blueprint_spec.rb +32 -16
  85. data/spec/unit/models/project_role_spec.rb +6 -4
  86. data/spec/unit/models/project_spec.rb +26 -3
  87. data/spec/unit/models/schema_builder_spec.rb +5 -6
  88. data/spec/unit/models/to_manifest_spec.rb +24 -0
  89. data/spec/unit/models/to_wire_spec.rb +63 -0
  90. metadata +53 -29
  91. data/lib/gooddata/models/attributes/anchor.rb +0 -37
  92. data/lib/gooddata/models/attributes/attributes.rb +0 -8
  93. data/lib/gooddata/models/attributes/date_attribute.rb +0 -25
  94. data/lib/gooddata/models/attributes/time_attribute.rb +0 -24
  95. data/lib/gooddata/models/columns/attribute.rb +0 -71
  96. data/lib/gooddata/models/columns/columns.rb +0 -8
  97. data/lib/gooddata/models/columns/date_column.rb +0 -63
  98. data/lib/gooddata/models/columns/fact_model.rb +0 -54
  99. data/lib/gooddata/models/columns/label.rb +0 -55
  100. data/lib/gooddata/models/columns/reference.rb +0 -57
  101. data/lib/gooddata/models/facts/facts.rb +0 -8
  102. data/lib/gooddata/models/facts/time_fact.rb +0 -20
  103. data/lib/gooddata/models/folders/attribute_folder.rb +0 -20
  104. data/lib/gooddata/models/folders/fact_folder.rb +0 -20
  105. data/lib/gooddata/models/folders/folders.rb +0 -8
  106. data/lib/gooddata/models/metadata/column.rb +0 -61
  107. data/lib/gooddata/models/metadata/data_set.rb +0 -32
  108. data/lib/gooddata/models/metadata/date_dimension.rb +0 -26
  109. data/lib/gooddata/models/metadata/schema.rb +0 -227
  110. data/lib/gooddata/models/references/date_reference.rb +0 -44
  111. data/lib/gooddata/models/references/references.rb +0 -8
  112. data/lib/gooddata/models/references/time_reference.rb +0 -13
  113. data/spec/helpers/schema_helper.rb +0 -16
  114. data/spec/unit/models/anchor_spec.rb +0 -32
  115. data/spec/unit/models/tools_spec.rb +0 -95
  116. data/test/test_upload.rb +0 -79
@@ -3,144 +3,46 @@
3
3
  require_relative '../core/connection'
4
4
  require_relative '../core/project'
5
5
 
6
+ require_relative '../mixins/mixins'
7
+
6
8
  module GoodData
7
9
  class MdObject
8
- MD_OBJ_CTG = 'obj'
9
10
  IDENTIFIERS_CFG = 'instance-identifiers'
10
11
 
11
12
  attr_reader :json
12
13
 
13
14
  alias_method :raw_data, :json
14
15
  alias_method :to_hash, :json
15
- alias_method :data, :json
16
-
17
- class << self
18
- def root_key(a_key)
19
- define_method :root_key, proc { a_key.to_s }
20
- end
21
-
22
- def metadata_property_reader(*props)
23
- props.each do |prop|
24
- define_method prop, proc { meta[prop.to_s] }
25
- end
26
- end
27
-
28
- def metadata_property_writer(*props)
29
- props.each do |prop|
30
- define_method "#{prop}=", proc { |val| meta[prop.to_s] = val }
31
- end
32
- end
33
16
 
34
- # Returns either list of objects or a specific object. This method is reimplemented in subclasses to leverage specific implementation for specific type of objects. Options is used in subclasses specifically to provide shorthand for getting a full objects after getting a list of hashes from query resource
35
- # @param [Object] id id can be either a number a String (as a URI). Subclasses should also be abel to deal with getting the instance of MdObject already and a :all symbol
36
- # @param [Hash] options the options hash
37
- # @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
38
- # @return [MdObject] if id is a String or number single object is returned
39
- # @return [Array] if :all was provided as an id, list of objects should be returned. Note that this is implemented only in the subclasses. MdObject does not support this since API has no means to return list of all types of objects
40
- def [](id, options = {})
41
- fail "You have to provide an \"id\" to be searched for." unless id
42
- fail(NoProjectError, 'Connect to a project before searching for an object') unless GoodData.project
43
- return all(options) if id == :all
44
- return id if id.is_a?(MdObject)
45
- uri = if id.is_a?(Integer) || id =~ /^\d+$/
46
- "#{GoodData.project.md[MD_OBJ_CTG]}/#{id}"
47
- elsif id !~ /\//
48
- identifier_to_uri id
49
- elsif id =~ /^\//
50
- id
51
- else
52
- fail 'Unexpected object id format: expected numeric ID, identifier with no slashes or an URI starting with a slash'
53
- end
54
- new(GoodData.get uri) unless uri.nil?
55
- end
56
-
57
- # Method intended to get all objects of that type in a specified project
58
- #
59
- # @param options [Hash] the options hash
60
- # @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
61
- # @return [Array<GoodData::MdObject> | Array<Hash>] Return the appropriate metadata objects or their representation
62
- def all(options = {})
63
- fail NotImplementedError, 'Method should be implemented in subclass. Currently there is no way hoe to get all metadata objects on API.'
64
- end
65
-
66
- def find_by_tag(tag)
67
- self[:all].select { |r| r['tags'].split(',').include?(tag) }
68
- end
69
-
70
- def find_first_by_title(title)
71
- all = self[:all]
72
- item = if title.is_a?(Regexp)
73
- all.find { |r| r['title'] =~ title }
74
- else
75
- all.find { |r| r['title'] == title }
76
- end
77
- self[item['link']] unless item.nil?
78
- end
79
-
80
- # Finds a specific type of the object by title. Returns all matches. Returns full object.
81
- #
82
- # @param title [String] title that has to match exactly
83
- # @param title [Regexp] regular expression that has to match
84
- # @return [Array<GoodData::MdObject>] Array of MdObject
85
- def find_by_title(title)
86
- all = self[:all]
87
- items = if title.is_a?(Regexp)
88
- all.select { |r| r['title'] =~ title }
89
- else
90
- all.select { |r| r['title'] == title }
91
- end
92
- items.map { |item| self[item['link']] unless item.nil? }
93
- end
17
+ include GoodData::Mixin::RootKeyGetter
18
+ include GoodData::Mixin::MdJson
19
+ include GoodData::Mixin::DataGetter
20
+ include GoodData::Mixin::MetaGetter
21
+ include GoodData::Mixin::ObjId
22
+ include GoodData::Mixin::ContentGetter
23
+ include GoodData::Mixin::Timestamps
24
+ include GoodData::Mixin::Links
25
+ include GoodData::Mixin::NotAttribute
26
+ include GoodData::Mixin::NotExportable
27
+ include GoodData::Mixin::NotFact
28
+ include GoodData::Mixin::NotMetric
29
+ include GoodData::Mixin::NotLabel
30
+ include GoodData::Mixin::MdRelations
94
31
 
95
- # TODO: Add test
96
- def identifier_to_uri(*ids)
97
- fail(NoProjectError, 'Connect to a project before searching for an object') unless GoodData.project
98
- uri = GoodData.project.md[IDENTIFIERS_CFG]
99
- response = GoodData.post uri, 'identifierToUri' => ids
100
- if response['identifiers'].empty?
101
- nil
102
- else
103
- identifiers = response['identifiers']
104
- ids_lookup = identifiers.reduce({}) do |a, e|
105
- a[e['identifier']] = e['uri']
106
- a
107
- end
108
- uris = ids.map { |x| ids_lookup[x] }
109
- uris.count == 1 ? uris.first : uris
110
- end
111
- end
112
-
113
- alias_method :id_to_uri, :identifier_to_uri
114
-
115
- alias_method :get_by_id, :[]
116
-
117
- private
118
-
119
- # Method intended to be called by individual classes in their all
120
- # implementations. It abstracts the way interacting with query resources.
121
- # It either returns the array of hashes from query. If asked it also
122
- # goes and brings the full objects. Due to performance reasons
123
- # :full => false is the default. This will most likely change
124
- #
125
- # @param query_obj_type [String] string used in URI to distinguish different query resources for different objects
126
- # @param klass [Class] A class used for instantiating the returned data
127
- # @param options [Hash] the options hash
128
- # @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
129
- # @return [Array<GoodData::MdObject> | Array<Hash>] Return the appropriate metadata objects or their representation
130
- def query(query_obj_type, klass, options = {})
131
- fail(NoProjectError, 'Connect to a project before searching for an object') unless GoodData.project
132
- query_result = GoodData.get(GoodData.project.md['query'] + "/#{query_obj_type}/")['query']['entries']
133
- options[:full] ? query_result.map { |item| klass[item['link']] } : query_result
134
- end
32
+ class << self
33
+ include GoodData::Mixin::RootKeySetter
34
+ include GoodData::Mixin::MetaPropertyReader
35
+ include GoodData::Mixin::MetaPropertyWriter
36
+ include GoodData::Mixin::MdObjId
37
+ include GoodData::Mixin::MdObjectQuery
38
+ include GoodData::Mixin::MdObjectIndexer
39
+ include GoodData::Mixin::MdFinders
40
+ include GoodData::Mixin::MdIdToUri
135
41
  end
136
42
 
137
43
  metadata_property_reader :uri, :identifier, :title, :summary, :tags, :deprecated, :category
138
44
  metadata_property_writer :tags, :summary, :title, :identifier
139
45
 
140
- def root_key
141
- raw_data.keys.first
142
- end
143
-
144
46
  def initialize(data)
145
47
  @json = data.to_hash
146
48
  end
@@ -160,26 +62,10 @@ module GoodData
160
62
 
161
63
  alias_method :refresh, :reload!
162
64
 
163
- def obj_id
164
- uri.split('/').last
165
- end
166
-
167
- def links
168
- data['links']
169
- end
170
-
171
65
  def browser_uri
172
66
  GoodData.connection.url + meta['uri']
173
67
  end
174
68
 
175
- def updated
176
- Time.parse(meta['updated'])
177
- end
178
-
179
- def created
180
- Time.parse(meta['created'])
181
- end
182
-
183
69
  def deprecated=(flag)
184
70
  if flag == '1' || flag == 1
185
71
  meta['deprecated'] = '1'
@@ -190,46 +76,10 @@ module GoodData
190
76
  end
191
77
  end
192
78
 
193
- def data
194
- raw_data[root_key]
195
- end
196
-
197
- def meta
198
- data && data['meta']
199
- end
200
-
201
- def content
202
- data && data['content']
203
- end
204
-
205
79
  def project
206
80
  @project ||= Project[uri.gsub(%r{\/obj\/\d+$}, '')]
207
81
  end
208
82
 
209
- def usedby(key = nil)
210
- dependency("#{GoodData.project.md['usedby2']}/#{obj_id}", key)
211
- end
212
-
213
- alias_method :used_by, :usedby
214
-
215
- def using(key = nil)
216
- dependency("#{GoodData.project.md['using2']}/#{obj_id}", key)
217
- end
218
-
219
- def usedby?(obj)
220
- dependency?(:usedby, obj)
221
- end
222
-
223
- alias_method :used_by?, :usedby?
224
-
225
- def using?(obj)
226
- dependency?(:using, obj)
227
- end
228
-
229
- def to_json
230
- @json.to_json
231
- end
232
-
233
83
  def saved?
234
84
  res = uri.nil?
235
85
  !res
@@ -250,7 +100,8 @@ module GoodData
250
100
  result = GoodData.post(GoodData.project.md['obj'], to_json)
251
101
  saved_object = self.class[result['uri']]
252
102
  # TODO: add test for explicitly provided identifier
253
- @json = saved_object.raw_data
103
+
104
+ @json = saved_object.json
254
105
  if explicit_identifier
255
106
  # Object creation API discards the identifier. If an identifier
256
107
  # was explicitely provided in the origina object, we need to set
@@ -274,7 +125,7 @@ module GoodData
274
125
  # @param new_title [String] New title. If not provided one is provided
275
126
  # @return [GoodData::MdObject] MdObject that has been saved as
276
127
  def save_as(new_title = "Clone of #{title}")
277
- dupped = Marshal.load(Marshal.dump(raw_data))
128
+ dupped = Marshal.load(Marshal.dump(json))
278
129
  dupped[root_key]['meta'].delete('uri')
279
130
  dupped[root_key]['meta'].delete('identifier')
280
131
  dupped[root_key]['meta']['title'] = new_title
@@ -289,58 +140,5 @@ module GoodData
289
140
  def validate
290
141
  true
291
142
  end
292
-
293
- def exportable?
294
- false
295
- end
296
-
297
- # Returns true if the object is a fact false otherwise
298
- # @return [Boolean]
299
- def fact?
300
- false
301
- end
302
-
303
- # Returns true if the object is an attribute false otherwise
304
- # @return [Boolean]
305
- def attribute?
306
- false
307
- end
308
-
309
- # Returns true if the object is a metric false otherwise
310
- # @return [Boolean]
311
- def metric?
312
- false
313
- end
314
-
315
- # Returns true if the object is a label false otherwise
316
- # @return [Boolean]
317
- def label?
318
- false
319
- end
320
- alias_method :display_form?, :label?
321
-
322
- private
323
-
324
- def dependency(uri, key = nil)
325
- result = GoodData.get("#{uri}/#{obj_id}")['entries']
326
- if key.nil?
327
- result
328
- elsif key.respond_to?(:category)
329
- result.select { |item| item['category'] == key.category }
330
- else
331
- result.select { |item| item['category'] == key }
332
- end
333
- end
334
-
335
- def dependency?(type, uri)
336
- objs = case type
337
- when :usedby
338
- usedby
339
- when :using
340
- using
341
- end
342
- uri = uri.respond_to?(:uri) ? uri.uri : uri
343
- objs.any? { |obj| obj['link'] == uri }
344
- end
345
143
  end
346
144
  end
@@ -2,10 +2,14 @@
2
2
 
3
3
  require_relative 'metadata'
4
4
 
5
+ require_relative '../../mixins/is_attribute'
6
+
5
7
  module GoodData
6
8
  class Attribute < MdObject
7
9
  root_key :attribute
8
10
 
11
+ include GoodData::Mixin::IsAttribute
12
+
9
13
  ATTRIBUTE_BASE_AGGREGATIONS = [:count]
10
14
 
11
15
  class << self
@@ -42,12 +46,6 @@ module GoodData
42
46
  end
43
47
  alias_method :primary_label, :primary_display_form
44
48
 
45
- # Returns true if the object is an attribute false otherwise
46
- # @return [Boolean]
47
- def attribute?
48
- true
49
- end
50
-
51
49
  # Creates the basic count metric with the attribute used. If you need to compute the attribute on a different dataset you can specify that in params. The metric created is not saved.
52
50
  # @param [Hash] options the options to pass to the value list
53
51
  # @option options [Symbol] :type type of aggregation function.
@@ -2,12 +2,16 @@
2
2
 
3
3
  require_relative '../metadata'
4
4
  require_relative '../../core/rest'
5
+ require_relative '../../mixins/is_fact'
6
+
5
7
  require_relative 'metadata'
6
8
 
7
9
  module GoodData
8
10
  class Fact < GoodData::MdObject
9
11
  root_key :fact
10
12
 
13
+ include GoodData::Mixin::IsFact
14
+
11
15
  # TODO: verify that we have all (which we do not right now)
12
16
  FACT_BASE_AGGREGATIONS = [:sum, :min, :max, :avg, :median]
13
17
 
@@ -22,12 +26,6 @@ module GoodData
22
26
  end
23
27
  end
24
28
 
25
- # Returns true if the object is a fact false otherwise
26
- # @return [Boolean]
27
- def fact?
28
- true
29
- end
30
-
31
29
  # Creates the basic count metric with the fact used. The metric created is not saved.
32
30
  # @param [Hash] options the options to pass to the value list
33
31
  # @option options [Symbol] :type type of aggregation function. Default is :sum
@@ -1,21 +1,24 @@
1
1
  # encoding: UTF-8
2
2
 
3
3
  require_relative '../metadata'
4
+ require_relative '../../mixins/is_label'
4
5
  require_relative 'metadata'
5
6
 
6
7
  module GoodData
7
8
  class Label < GoodData::MdObject
8
9
  root_key :attributeDisplayForm
9
10
 
11
+ include GoodData::Mixin::IsLabel
12
+
10
13
  # Finds an attribute element URI for given value. This URI can be used by find_element_value to find the original value again
11
14
  # @param [String] value value of an label you are looking for
12
15
  # @return [String]
13
16
  def find_value_uri(value)
14
- value = CGI.escape(value)
15
- results = GoodData.post("#{uri}/validElements?limit=30&offset=0&order=asc&filter=#{value}", {})
17
+ escaped_value = CGI.escape(value)
18
+ results = GoodData.post("#{uri}/validElements?limit=30&offset=0&order=asc&filter=#{escaped_value}", {})
16
19
  items = results['validElements']['items']
17
20
  if items.empty?
18
- fail "#{value} not found"
21
+ fail(AttributeElementNotFound, value)
19
22
  else
20
23
  items.first['element']['uri']
21
24
  end
@@ -25,7 +28,7 @@ module GoodData
25
28
  # @param [Object] element_id Element identifier either Number or a uri as a String
26
29
  # @return [String] value of the element if found
27
30
  def find_element_value(element_id)
28
- element_id = element_id.is_a?(String) ? element_id.match(/\?id=(\d)/)[1] : element_id
31
+ element_id = element_id.is_a?(String) ? element_id.match(/\?id=(\d+)/)[1] : element_id
29
32
  uri = links['elements']
30
33
  result = GoodData.get(uri + "/?id=#{element_id}")
31
34
  items = result['attributeElements']['elements']
@@ -36,6 +39,16 @@ module GoodData
36
39
  end
37
40
  end
38
41
 
42
+ # Finds if a label has an attribute element for given value.
43
+ # @param [String] value value of an label you are looking for
44
+ # @return [Boolean]
45
+ def value?(value)
46
+ find_value_uri(value)
47
+ true
48
+ rescue AttributeElementNotFound
49
+ false
50
+ end
51
+
39
52
  # Returns all values for this label. This is for inspection purposes only since obviously there can be huge number of elements.
40
53
  # @param [Hash] options the options to pass to the value list
41
54
  # @option options [Number] :limit limits the number of values to certain number. Default is 100
@@ -58,13 +71,6 @@ module GoodData
58
71
  GoodData::Attribute[content['formOf']]
59
72
  end
60
73
 
61
- # Returns true if the object is an attribute false otherwise
62
- # @return [Boolean]
63
- def label?
64
- true
65
- end
66
- alias_method :display_form?, :label?
67
-
68
74
  # Gives an attribute url of current label. Useful for mass actions when it does not introduce HTTP call.
69
75
  # @return [GoodData::Attibute]
70
76
  def attribute_uri
@@ -108,7 +108,7 @@ module GoodData
108
108
 
109
109
  def execute
110
110
  res = GoodData::ReportDefinition.execute(:left => self)
111
- res[0][0]
111
+ res && res[0][0]
112
112
  end
113
113
 
114
114
  def expression