gooddata 0.6.3 → 0.6.4

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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -1
  3. data/CHANGELOG.markdown +6 -0
  4. data/README.md +1 -0
  5. data/gooddata.gemspec +2 -1
  6. data/lib/gooddata.rb +4 -1
  7. data/lib/gooddata/bricks/base_downloader.rb +33 -19
  8. data/lib/gooddata/bricks/middleware/bulk_salesforce_middleware.rb +49 -25
  9. data/lib/gooddata/bricks/middleware/restforce_middleware.rb +36 -33
  10. data/lib/gooddata/cli/commands/project_cmd.rb +6 -4
  11. data/lib/gooddata/client.rb +1 -1
  12. data/lib/gooddata/commands/api.rb +1 -1
  13. data/lib/gooddata/commands/auth.rb +1 -1
  14. data/lib/gooddata/connection.rb +13 -10
  15. data/lib/gooddata/core/connection.rb +1 -1
  16. data/lib/gooddata/core/user.rb +11 -3
  17. data/lib/gooddata/exceptions/validation_error.rb +12 -0
  18. data/lib/gooddata/extensions/extensions.rb +6 -0
  19. data/lib/gooddata/goodzilla/goodzilla.rb +2 -2
  20. data/lib/gooddata/helpers/csv_helper.rb +57 -0
  21. data/lib/gooddata/{helpers.rb → helpers/global_helpers.rb} +0 -0
  22. data/lib/gooddata/helpers/helpers.rb +6 -0
  23. data/lib/gooddata/models/domain.rb +134 -24
  24. data/lib/gooddata/models/membership.rb +402 -0
  25. data/lib/gooddata/models/metadata.rb +64 -7
  26. data/lib/gooddata/models/metadata/attribute.rb +27 -12
  27. data/lib/gooddata/models/metadata/column.rb +1 -1
  28. data/lib/gooddata/models/metadata/dashboard.rb +7 -6
  29. data/lib/gooddata/models/metadata/display_form.rb +17 -2
  30. data/lib/gooddata/models/metadata/fact.rb +13 -7
  31. data/lib/gooddata/models/metadata/metric.rb +9 -9
  32. data/lib/gooddata/models/metadata/report.rb +7 -8
  33. data/lib/gooddata/models/metadata/report_definition.rb +10 -11
  34. data/lib/gooddata/models/metadata/schema.rb +1 -1
  35. data/lib/gooddata/models/model.rb +1 -1
  36. data/lib/gooddata/models/process.rb +44 -25
  37. data/lib/gooddata/models/profile.rb +365 -13
  38. data/lib/gooddata/models/project.rb +245 -35
  39. data/lib/gooddata/models/project_blueprint.rb +42 -18
  40. data/lib/gooddata/models/project_creator.rb +4 -1
  41. data/lib/gooddata/models/project_role.rb +7 -7
  42. data/lib/gooddata/models/schedule.rb +17 -1
  43. data/lib/gooddata/models/schema_blueprint.rb +19 -2
  44. data/lib/gooddata/version.rb +1 -1
  45. data/out.txt +0 -0
  46. data/spec/data/users.csv +12 -0
  47. data/spec/helpers/connection_helper.rb +1 -0
  48. data/spec/helpers/csv_helper.rb +12 -0
  49. data/spec/helpers/project_helper.rb +1 -1
  50. data/spec/integration/full_project_spec.rb +136 -3
  51. data/spec/spec_helper.rb +9 -0
  52. data/spec/unit/commands/command_user_spec.rb +1 -1
  53. data/spec/unit/extensions/hash_spec.rb +19 -0
  54. data/spec/unit/godzilla/goodzilla_spec.rb +15 -0
  55. data/spec/unit/helpers/csv_helper_spec.rb +18 -0
  56. data/spec/unit/models/domain_spec.rb +47 -4
  57. data/spec/unit/models/md_object_spec.rb +8 -0
  58. data/spec/unit/models/membership_spec.rb +128 -0
  59. data/spec/unit/models/metadata_spec.rb +38 -0
  60. data/spec/unit/models/profile_spec.rb +212 -0
  61. data/spec/unit/models/project_blueprint_spec.rb +35 -8
  62. data/spec/unit/models/project_role_spec.rb +6 -6
  63. data/spec/unit/models/project_spec.rb +226 -13
  64. data/spec/unit/models/schedule_spec.rb +58 -0
  65. data/tmp/.gitkeepme +0 -0
  66. metadata +36 -11
  67. data/lib/gooddata/models/account_settings.rb +0 -124
  68. data/lib/gooddata/models/user.rb +0 -165
  69. data/spec/unit/models/account_settings_spec.rb +0 -28
  70. data/spec/unit/models/user_spec.rb +0 -16
@@ -7,7 +7,11 @@ module GoodData
7
7
 
8
8
  def self.from_json(spec)
9
9
  if spec.is_a?(String)
10
- ProjectBlueprint.new(MultiJson.load(File.read(spec), :symbolize_keys => true))
10
+ if File.file?(spec)
11
+ ProjectBlueprint.new(MultiJson.load(File.read(spec), :symbolize_keys => true))
12
+ else
13
+ ProjectBlueprint.new(MultiJson.load(spec, :symbolize_keys => true))
14
+ end
11
15
  else
12
16
  ProjectBlueprint.new(spec)
13
17
  end
@@ -21,7 +25,8 @@ module GoodData
21
25
  end
22
26
 
23
27
  def datasets
24
- data[:datasets].map { |d| DatasetBlueprint.new(d) }
28
+ sets = data[:datasets] || []
29
+ sets.map { |d| DatasetBlueprint.new(d) }
25
30
  end
26
31
 
27
32
  def add_dataset(a_dataset, index = nil)
@@ -38,6 +43,13 @@ module GoodData
38
43
  data[:datasets].delete_at(index)
39
44
  end
40
45
 
46
+ # Is this a project blueprint?
47
+ #
48
+ # @return [Boolean] if it is
49
+ def project_blueprint?
50
+ true
51
+ end
52
+
41
53
  def date_dimensions
42
54
  data[:date_dimensions]
43
55
  end
@@ -53,10 +65,24 @@ module GoodData
53
65
  DatasetBlueprint.new(ds)
54
66
  end
55
67
 
68
+ # Constructor
69
+ #
70
+ # @param init_data [ProjectBlueprint | Hash] Blueprint or a blueprint definition. If passed a hash it is used as data for new instance. If there is a ProjectBlueprint passed it is duplicated and a new instance is created.
71
+ # @return [ProjectBlueprint] A new project blueprint instance
56
72
  def initialize(init_data)
57
- @data = init_data
58
- end
59
-
73
+ some_data = if init_data.respond_to?(:project_blueprint?) && init_data.project_blueprint?
74
+ init_data.to_hash
75
+ elsif init_data.respond_to?(:to_blueprint)
76
+ init_data.to_blueprint.to_hash
77
+ else
78
+ init_data
79
+ end
80
+ @data = some_data.deep_dup
81
+ end
82
+
83
+ # Validate the blueprint in particular if all references reference existing datasets and valid fields inside them.
84
+ #
85
+ # @return [Array] array of errors
60
86
  def validate_references
61
87
  if datasets.count == 1
62
88
  []
@@ -71,21 +97,20 @@ module GoodData
71
97
  end
72
98
  end
73
99
 
74
- def validate_labels_references
75
- datasets.reduce([]) { |a, e| a.concat(e.validate_label_references) }
76
- end
77
-
78
- def validate_model
100
+ # Validate the blueprint and all its datasets return array of errors that are found.
101
+ #
102
+ # @return [Array] array of errors
103
+ def validate
79
104
  refs_errors = validate_references
80
- labels_errors = validate_labels_references
105
+ labels_errors = datasets.reduce([]) { |a, e| a.concat(e.validate) }
81
106
  refs_errors.concat(labels_errors)
82
107
  end
83
108
 
84
- def model_valid?
85
- refs_errors = validate_references
86
- labels_errors = validate_labels_references
87
- errors = refs_errors.concat(labels_errors)
88
- errors.empty? ? true : false
109
+ # Validate the blueprint and all its datasets and return true if model is valid. False otherwise.
110
+ #
111
+ # @return [Boolean] is model valid?
112
+ def valid?
113
+ validate.empty?
89
114
  end
90
115
 
91
116
  def referenced_by(dataset)
@@ -185,8 +210,7 @@ module GoodData
185
210
  end
186
211
 
187
212
  def dup
188
- deep_copy = Marshal.load(Marshal.dump(data))
189
- ProjectBlueprint.new(deep_copy)
213
+ ProjectBlueprint.new(data.deep_dup)
190
214
  end
191
215
 
192
216
  def title
@@ -11,7 +11,10 @@ module GoodData
11
11
  class << self
12
12
  def migrate(options = {})
13
13
  spec = options[:spec] || fail('You need to provide spec for migration')
14
- spec = spec.to_hash
14
+ bp = ProjectBlueprint.new(spec)
15
+ spec = bp.to_hash
16
+
17
+ fail GoodData::ValidationError, "Blueprint is invalid #{bp.validate.inspect}" unless bp.valid?
15
18
 
16
19
  token = options[:token]
17
20
  project = options[:project] || GoodData::Project.create(:title => spec[:title], :auth_token => token)
@@ -1,6 +1,6 @@
1
1
  # encoding: UTF-8
2
2
 
3
- require_relative 'account_settings'
3
+ require_relative 'profile'
4
4
 
5
5
  module GoodData
6
6
  class ProjectRole
@@ -17,20 +17,20 @@ module GoodData
17
17
 
18
18
  # Gets Project Role Author
19
19
  #
20
- # @return [GoodData::AccountSettings] Project Role author
20
+ # @return [GoodData::Profile] Project Role author
21
21
  def author
22
22
  url = @json['projectRole']['meta']['author']
23
23
  tmp = GoodData.get url
24
- GoodData::AccountSettings.new(tmp)
24
+ GoodData::Profile.new(tmp)
25
25
  end
26
26
 
27
27
  # Gets Project Role Contributor
28
28
  #
29
- # @return [GoodData::AccountSettings] Project Role Contributor
29
+ # @return [GoodData::Profile] Project Role Contributor
30
30
  def contributor
31
31
  url = @json['projectRole']['meta']['contributor']
32
32
  tmp = GoodData.get url
33
- GoodData::AccountSettings.new(tmp)
33
+ GoodData::Profile.new(tmp)
34
34
  end
35
35
 
36
36
  # Gets DateTime time when created
@@ -70,14 +70,14 @@ module GoodData
70
70
 
71
71
  # Gets Users with this Role
72
72
  #
73
- # @return [Array<GoodData::AccountSettings>] List of users
73
+ # @return [Array<GoodData::Profile>] List of users
74
74
  def users
75
75
  res = []
76
76
  url = @json['projectRole']['links']['roleUsers']
77
77
  tmp = GoodData.get url
78
78
  tmp['associatedUsers']['users'].each do |user_url|
79
79
  user = GoodData.get user_url
80
- res << GoodData::AccountSettings.new(user)
80
+ res << GoodData::Profile.new(user)
81
81
  end
82
82
  res
83
83
  end
@@ -48,6 +48,7 @@ module GoodData
48
48
  },
49
49
  :hidden_params => {}
50
50
  }
51
+ default_opts.merge!(:reschedule => options[:reschedule])
51
52
 
52
53
  inject_schema = {
53
54
  :hidden_params => 'hiddenParams'
@@ -183,6 +184,21 @@ module GoodData
183
184
  @dirty = true
184
185
  end
185
186
 
187
+ # Returns reschedule settings
188
+ #
189
+ # @return [Integer] Reschedule settings
190
+ def reschedule
191
+ @json['schedule']['reschedule']
192
+ end
193
+
194
+ # Assigns execution reschedule settings
195
+ #
196
+ # @param new_reschedule [Integer] Reschedule settings to be set
197
+ def reschedule=(new_reschedule)
198
+ @json['schedule']['reschedule'] = new_reschedule
199
+ @dirty = true
200
+ end
201
+
186
202
  # Returns execution process ID
187
203
  #
188
204
  # @return [String] Process ID
@@ -265,8 +281,8 @@ module GoodData
265
281
  'hiddenParams' => @json['schedule']['hiddenParams']
266
282
  }
267
283
  }
284
+ update_json['schedule'].merge!('reschedule' => @json['schedule']['reschedule'])
268
285
  res = GoodData.put uri, update_json
269
-
270
286
  @json = res
271
287
  @dirty = false
272
288
  return true
@@ -120,8 +120,7 @@ module GoodData
120
120
  end
121
121
 
122
122
  def dup
123
- deep_copy = Marshal.load(Marshal.dump(data))
124
- DatasetBlueprint.new(deep_copy)
123
+ DatasetBlueprint.new(data.deep_dup)
125
124
  end
126
125
 
127
126
  def to_wire_model
@@ -148,6 +147,24 @@ module GoodData
148
147
  end
149
148
  end
150
149
 
150
+ # Validate the blueprint return array of errors that are found.
151
+ #
152
+ # @return [Array] array of errors
153
+ def validate
154
+ more_than_one_anchor = find_column_by_type(:anchor, :all).count > 1 ? [{ :anchor => 2 }] : []
155
+ validate_label_references.concat(more_than_one_anchor)
156
+ end
157
+
158
+ # Validate the blueprint and return true if model is valid. False otherwise.
159
+ #
160
+ # @return [Boolean] is model valid?
161
+ def valid?
162
+ validate.empty?
163
+ end
164
+
165
+ # Validate the that any labels are pointing to the existing attribute. If not returns the list of errors. Currently just violating labels.
166
+ #
167
+ # @return [Array] array of errors
151
168
  def validate_label_references
152
169
  labels.select do |label|
153
170
  find_column_by_name(label[:reference]).empty?
@@ -2,7 +2,7 @@
2
2
 
3
3
  # GoodData Module
4
4
  module GoodData
5
- VERSION = '0.6.3'
5
+ VERSION = '0.6.4'
6
6
 
7
7
  class << self
8
8
  # Version
data/out.txt ADDED
File without changes
@@ -0,0 +1,12 @@
1
+ First Name,Last Name,Email,Password,MUF,MUF ID,Role,Role ID,Project ID,Domain
2
+ Gem,Tester,svarovsky+gem_tester@gooddata.com,jindrisska,Edit,,adminRole,5,wgqhml3se0035s8n5byqdq0j0ob5jam4,gooddata-tomas-svarovsky
3
+ Test,User0,test.user.110@example.com,password0,Full,,adminRole,5,wgqhml3se0035s8n5byqdq0j0ob5jam4,gooddata-tomas-svarovsky
4
+ Test,User1,test.user.111@example.com,password1,Full,,connectorsSystemRole,5,wgqhml3se0035s8n5byqdq0j0ob5jam4,gooddata-tomas-svarovsky
5
+ Test,User2,test.user.112@example.com,password2,Full,,editorRole,5,wgqhml3se0035s8n5byqdq0j0ob5jam4,gooddata-tomas-svarovsky
6
+ Test,User3,test.user.113@example.com,password3,Full,,dashboardOnlyRole,5,wgqhml3se0035s8n5byqdq0j0ob5jam4,gooddata-tomas-svarovsky
7
+ Test,User4,test.user.114@example.com,password4,Full,,unverifiedAdminRole,5,wgqhml3se0035s8n5byqdq0j0ob5jam4,gooddata-tomas-svarovsky
8
+ Test,User5,test.user.115@example.com,password5,Full,,readOnlyUserRole,5,wgqhml3se0035s8n5byqdq0j0ob5jam4,gooddata-tomas-svarovsky
9
+ Test,User6,test.user.116@example.com,password6,Full,,adminRole,5,wgqhml3se0035s8n5byqdq0j0ob5jam4,gooddata-tomas-svarovsky
10
+ Test,User7,test.user.117@example.com,password7,Full,,connectorsSystemRole,5,wgqhml3se0035s8n5byqdq0j0ob5jam4,gooddata-tomas-svarovsky
11
+ Test,User8,test.user.118@example.com,password8,Full,,editorRole,5,wgqhml3se0035s8n5byqdq0j0ob5jam4,gooddata-tomas-svarovsky
12
+ Test,User9,test.user.119@example.com,password9,Full,,dashboardOnlyRole,5,wgqhml3se0035s8n5byqdq0j0ob5jam4,gooddata-tomas-svarovsky
@@ -6,6 +6,7 @@ module ConnectionHelper
6
6
  DEFAULT_USERNAME = "svarovsky+gem_tester@gooddata.com"
7
7
  DEFAULT_PASSWORD = "jindrisska"
8
8
  DEFAULT_DOMAIN = 'gooddata-tomas-svarovsky'
9
+ DEFAULT_USER_URL = '/gdc/account/profile/3cea1102d5584813506352a2a2a00d95'
9
10
 
10
11
  # Creates connection using default credentials or supplied one
11
12
  #
@@ -0,0 +1,12 @@
1
+ # encoding: UTF-8
2
+
3
+ # Global requires
4
+ require 'multi_json'
5
+
6
+ # Local requires
7
+ require 'gooddata/models/models'
8
+
9
+ module CsvHelper
10
+ CSV_PATH_EXPORT = 'out.txt'
11
+ CSV_PATH_IMPORT = File.join(File.dirname(__FILE__), '..', 'data', 'users.csv')
12
+ end
@@ -7,7 +7,7 @@ require 'multi_json'
7
7
  require 'gooddata/models/models'
8
8
 
9
9
  module ProjectHelper
10
- PROJECT_ID = 'wgqhml3se0035s8n5byqdq0j0ob5jam4'
10
+ PROJECT_ID = 'we1vvh4il93r0927r809i3agif50d7iz'
11
11
  PROJECT_URL = "/gdc/projects/#{PROJECT_ID}"
12
12
 
13
13
  def self.get_default_project
@@ -1,8 +1,9 @@
1
1
  require 'gooddata'
2
2
 
3
- describe "Ful project implementation", :constraint => 'slow' do
3
+ describe "Full project implementation", :constraint => 'slow' do
4
4
  before(:all) do
5
5
  @spec = JSON.parse(File.read("./spec/data/test_project_model_spec.json"), :symbolize_names => true)
6
+ @invalid_spec = JSON.parse(File.read("./spec/data/blueprint_invalid.json"), :symbolize_names => true)
6
7
  ConnectionHelper::create_default_connection
7
8
  @project = GoodData::Model::ProjectCreator.migrate({:spec => @spec, :token => ConnectionHelper::GD_PROJECT_TOKEN})
8
9
  end
@@ -11,6 +12,12 @@ describe "Ful project implementation", :constraint => 'slow' do
11
12
  @project.delete unless @project.nil?
12
13
  end
13
14
 
15
+ it "should not build an invalid model" do
16
+ expect {
17
+ GoodData::Model::ProjectCreator.migrate({:spec => @invalid_spec, :token => ConnectionHelper::GD_PROJECT_TOKEN})
18
+ }.to raise_error(GoodData::ValidationError)
19
+ end
20
+
14
21
  it "should contain datasets" do
15
22
  GoodData.with_project(@project) do |p|
16
23
  p.datasets.count.should == 4
@@ -79,10 +86,62 @@ describe "Ful project implementation", :constraint => 'slow' do
79
86
  end
80
87
  end
81
88
 
89
+ it "should throw an exception if trying to access object without explicitely specifying a project" do
90
+ expect do
91
+ GoodData::Metric[:all]
92
+ end.to raise_exception(GoodData::NoProjectError)
93
+ end
94
+
95
+ it "should be possible to get all metrics" do
96
+ GoodData.with_project(@project) do |p|
97
+ metrics1 = GoodData::Metric[:all]
98
+ metrics2 = GoodData::Metric.all
99
+ metrics1.should == metrics2
100
+ end
101
+ end
102
+
103
+ it "should be possible to get all metrics with full objects" do
104
+ GoodData.with_project(@project) do |p|
105
+ metrics1 = GoodData::Metric[:all, :full => true]
106
+ metrics2 = GoodData::Metric.all :full => true
107
+ metrics1.should == metrics2
108
+ end
109
+ end
110
+
111
+ it "should be able to get a metric by identifier" do
112
+ GoodData.with_project(@project) do |p|
113
+ metrics = GoodData::Metric.all :full => true
114
+ metric = GoodData::Metric[metrics.first.identifier]
115
+ metric.identifier == metrics.first.identifier
116
+ metrics.first == metric
117
+ end
118
+ end
119
+
120
+ it "should be able to get a metric by uri" do
121
+ GoodData.with_project(@project) do |p|
122
+ metrics = GoodData::Metric.all :full => true
123
+ metric = GoodData::Metric[metrics.first.uri]
124
+ metric.uri == metrics.first.uri
125
+ metrics.first == metric
126
+ end
127
+ end
128
+
129
+ it "should be able to get a metric by object id" do
130
+ GoodData.with_project(@project) do |p|
131
+ metrics = GoodData::Metric.all :full => true
132
+ metric = GoodData::Metric[metrics.first.obj_id]
133
+ metric.obj_id == metrics.first.obj_id
134
+ metrics.first == metric
135
+ end
136
+ end
137
+
82
138
  it "should exercise the object relations and getting them in various ways" do
83
139
  GoodData.with_project(@project) do |p|
140
+
84
141
  # Find a metric by name
85
142
  metric = GoodData::Metric.find_first_by_title('My metric')
143
+ the_same_metric = GoodData::Metric[metric]
144
+ metric.should == metric
86
145
 
87
146
  # grab fact in several different ways
88
147
  fact1 = GoodData::Fact.find_first_by_title('Lines changed')
@@ -148,6 +207,11 @@ describe "Ful project implementation", :constraint => 'slow' do
148
207
 
149
208
  res = GoodData::Metric.execute("SELECT SUM(![fact.commits.lines_changed])", :extended_notation => true)
150
209
  res.should == 9
210
+
211
+ fact = GoodData::Fact.find_first_by_title('Lines changed')
212
+ fact.fact?.should == true
213
+ res = fact.create_metric(:type => :sum).execute
214
+ res.should == 9
151
215
  end
152
216
  end
153
217
 
@@ -163,7 +227,9 @@ describe "Ful project implementation", :constraint => 'slow' do
163
227
 
164
228
  it "should have more users" do
165
229
  GoodData.with_project(@project) do |p|
166
- GoodData::Attribute['attr.devs.dev_id'].create_metric.execute.should == 4
230
+ attribute = GoodData::Attribute['attr.devs.dev_id']
231
+ attribute.attribute?.should == true
232
+ attribute.create_metric.execute.should == 4
167
233
  end
168
234
  end
169
235
 
@@ -188,6 +254,14 @@ describe "Ful project implementation", :constraint => 'slow' do
188
254
  end
189
255
  end
190
256
 
257
+ it "Should be able to compute count o different datasets" do
258
+ GoodData.with_project(@project) do |p|
259
+ attribute = GoodData::Attribute['attr.devs.dev_id']
260
+ dataset_attribute = GoodData::Attribute['attr.commits.id']
261
+ attribute.create_metric(:attribute => dataset_attribute).execute.should == 3
262
+ end
263
+ end
264
+
191
265
  it "should be able to tell you if a value is contained in a metric" do
192
266
  GoodData.with_project(@project) do |p|
193
267
  attribute = GoodData::Attribute['attr.devs.dev_id']
@@ -213,10 +287,69 @@ describe "Ful project implementation", :constraint => 'slow' do
213
287
  end
214
288
  end
215
289
 
216
- it "should be able to lookup the attributes by regexp and return a collectio" do
290
+ it "should be able to lookup the attributes by regexp and return a collection" do
217
291
  GoodData.with_project(@project) do |p|
218
292
  attrs = GoodData::Attribute.find_by_title(/Date/i)
219
293
  attrs.count.should == 1
220
294
  end
221
295
  end
296
+
297
+ it "should be able to give you values of the label as an array of hashes" do
298
+ GoodData.with_project(@project) do |p|
299
+ attribute = GoodData::Attribute['attr.devs.dev_id']
300
+ label = attribute.primary_label
301
+ label.values.map {|v| v[:value]}.should == [
302
+ 'jirka@gooddata.com',
303
+ 'josh@gooddata.com',
304
+ 'petr@gooddata.com',
305
+ 'tomas@gooddata.com'
306
+ ]
307
+ end
308
+ end
309
+
310
+ it "should be able to give you values for" do
311
+ GoodData.with_project(@project) do |p|
312
+ attribute = GoodData::Attribute['attr.devs.dev_id']
313
+ attribute.values_for(2).should == ["tomas@gooddata.com", "1"]
314
+ end
315
+ end
316
+
317
+ it "should be able to find specific element and give you the primary label value" do
318
+ GoodData.with_project(@project) do |p|
319
+ attribute = GoodData::Attribute['attr.devs.dev_id']
320
+ GoodData::Attribute.find_element_value("#{attribute.uri}/elements?id=2").should == 'tomas@gooddata.com'
321
+ end
322
+ end
323
+
324
+ it "should be able to give you label by name" do
325
+ GoodData.with_project(@project) do |p|
326
+ attribute = GoodData::Attribute['attr.devs.dev_id']
327
+ label = attribute.label_by_name('email')
328
+ label.label?.should == true
329
+ label.title.should == 'Email'
330
+ label.identifier.should == "label.devs.dev_id.email"
331
+ label.attribute_uri.should == attribute.uri
332
+ label.attribute.should == attribute
333
+ end
334
+ end
335
+
336
+ it "should be able to return values of the attribute for inspection" do
337
+ GoodData.with_project(@project) do |p|
338
+ attribute = GoodData::Attribute['attr.devs.dev_id']
339
+ vals = attribute.values
340
+ vals.count.should == 4
341
+ vals.first.count.should == 2
342
+ vals.first.first[:value].should == "jirka@gooddata.com"
343
+ end
344
+ end
345
+
346
+ it "should be able to save_as a metric" do
347
+ GoodData.with_project(@project) do |p|
348
+ m = GoodData::Metric.find_first_by_title("My test metric")
349
+ cloned = m.save_as
350
+ m_cloned = GoodData::Metric.find_first_by_title("Clone of My test metric")
351
+ m_cloned.should == cloned
352
+ m_cloned.execute.should == cloned.execute
353
+ end
354
+ end
222
355
  end