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
@@ -1,6 +1,5 @@
1
1
  # encoding: UTF-8
2
2
 
3
- require_relative '../core/connection'
4
3
  require_relative '../core/rest'
5
4
 
6
5
  require_relative 'metadata/metadata'
@@ -61,7 +60,15 @@ module GoodData
61
60
  end
62
61
 
63
62
  # Load given file into a data set described by the given schema
64
- def upload_data(path, project_blueprint, dataset, options = {})
63
+ def upload_data(path, project_blueprint, dataset, options = { :client => GoodData.connection, :project => GoodData.project })
64
+ client = options[:client]
65
+ fail ArgumentError, 'No :client specified' if client.nil?
66
+
67
+ p = options[:project]
68
+ fail ArgumentError, 'No :project specified' if p.nil?
69
+
70
+ project = GoodData::Project[p, options]
71
+ fail ArgumentError, 'Wrong :project specified' if project.nil?
65
72
  # path = if path =~ URI.regexp
66
73
  # Tempfile.open('remote_file') do |temp|
67
74
  # temp << open(path).read
@@ -99,7 +106,7 @@ module GoodData
99
106
  end
100
107
 
101
108
  # upload it
102
- GoodData.upload_to_user_webdav("#{dir}/upload.zip", :directory => File.basename(dir))
109
+ client.upload_to_user_webdav("#{dir}/upload.zip", :directory => File.basename(dir), :client => options[:client], :project => options[:project])
103
110
  ensure
104
111
  FileUtils.rm_rf dir
105
112
  end
@@ -107,15 +114,15 @@ module GoodData
107
114
  # kick the load
108
115
  pull = { 'pullIntegration' => File.basename(dir) }
109
116
  link = project.md.links('etl')['pull']
110
- task = GoodData.post link, pull
117
+ task = client.post link, pull
111
118
 
112
- res = GoodData.poll_on_response(task['pullTask']['uri']) do |body|
119
+ res = client.poll_on_response(task['pullTask']['uri']) do |body|
113
120
  body['taskStatus'] == 'RUNNING' || body['taskStatus'] == 'PREPARED'
114
121
  end
115
122
 
116
123
  if res['taskStatus'] == 'ERROR'
117
124
  s = StringIO.new
118
- GoodData.download_from_user_webdav(File.basename(dir) + '/upload_status.json', s)
125
+ client.download_from_user_webdav(File.basename(dir) + '/upload_status.json', s)
119
126
  js = MultiJson.load(s.string)
120
127
  fail "Load Failed with error #{JSON.pretty_generate(js)}"
121
128
  end
@@ -2,8 +2,10 @@
2
2
 
3
3
  require 'pry'
4
4
 
5
+ require_relative '../rest/resource'
6
+
5
7
  module GoodData
6
- class Process
8
+ class Process < GoodData::Rest::Object
7
9
  attr_reader :data
8
10
 
9
11
  alias_method :raw_data, :data
@@ -12,15 +14,24 @@ module GoodData
12
14
 
13
15
  class << self
14
16
  def [](id, options = {})
15
- if id == :all
16
- uri = "/gdc/projects/#{GoodData.project.pid}/dataload/processes"
17
- data = GoodData.get(uri)
17
+ project = options[:project]
18
+ c = client(options)
19
+
20
+ if id == :all && project
21
+ uri = "/gdc/projects/#{project.pid}/dataload/processes"
22
+ data = c.get(uri)
18
23
  data['processes']['items'].map do |process_data|
19
24
  Process.new(process_data)
20
25
  end
26
+ elsif id == :all
27
+ uri = "/gdc/account/profile/#{c.user.obj_id}/dataload/processes"
28
+ data = c.get(uri)
29
+ data['processes']['items'].map do |process_data|
30
+ c.create(Process, process_data)
31
+ end
21
32
  else
22
- uri = "/gdc/projects/#{GoodData.project.pid}/dataload/processes/#{id}"
23
- new(GoodData.get(uri))
33
+ uri = "/gdc/projects/#{project.pid}/dataload/processes/#{id}"
34
+ c.create(Process, c.get(uri), project: project)
24
35
  end
25
36
  end
26
37
 
@@ -30,8 +41,16 @@ module GoodData
30
41
 
31
42
  # TODO: Check the params.
32
43
  def with_deploy(dir, options = {}, &block)
33
- # verbose = options[:verbose] || false
34
- GoodData.with_project(options[:project_id] || options[:project]) do |project|
44
+ client = options[:client]
45
+ fail ArgumentError, 'No :client specified' if client.nil?
46
+
47
+ p = options[:project]
48
+ fail ArgumentError, 'No :project specified' if p.nil?
49
+
50
+ project = GoodData::Project[p, options]
51
+ fail ArgumentError, 'Wrong :project specified' if project.nil?
52
+
53
+ GoodData.with_project(project) do
35
54
  params = options[:params].nil? ? [] : [options[:params]]
36
55
  if block
37
56
  begin
@@ -46,9 +65,37 @@ module GoodData
46
65
  end
47
66
  end
48
67
 
49
- def upload_package(path, files_to_exclude)
50
- if !path.directory?
51
- GoodData.upload_to_user_webdav(path)
68
+ def upload_package(path, files_to_exclude, opts = { :client => GoodData.connection })
69
+ client = opts[:client]
70
+ fail ArgumentError, 'No :client specified' if client.nil?
71
+
72
+ p = opts[:project]
73
+ fail ArgumentError, 'No :project specified' if p.nil?
74
+
75
+ project = GoodData::Project[p, opts]
76
+ fail ArgumentError, 'Wrong :project specified' if project.nil?
77
+
78
+ if !path.directory? && (path.extname == '.grf' || path.extname == '.rb')
79
+ puts 'Creating package for upload'
80
+ Tempfile.open('deploy-graph-archive') do |temp|
81
+ Zip::OutputStream.open(temp.path) do |zio|
82
+ FileUtils.cd(path.parent) do
83
+ files_to_pack = [path.basename]
84
+ files_to_pack.each do |item|
85
+ puts "including #{item}"
86
+ unless File.directory?(item)
87
+ zio.put_next_entry(item)
88
+ zio.print IO.read(item)
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+ client.upload_to_user_webdav(temp.path, opts)
95
+ temp.path
96
+ end
97
+ elsif !path.directory?
98
+ client.upload_to_user_webdav(path, opts)
52
99
  path
53
100
  else
54
101
  Tempfile.open('deploy-graph-archive') do |temp|
@@ -57,7 +104,7 @@ module GoodData
57
104
 
58
105
  files_to_pack = Dir.glob('./**/*').reject { |f| files_to_exclude.include?(Pathname(path) + f) }
59
106
  files_to_pack.each do |item|
60
- # puts "including #{item}" if verbose
107
+ puts "including #{item}"
61
108
  unless File.directory?(item)
62
109
  zio.put_next_entry(item)
63
110
  zio.print IO.read(item)
@@ -65,7 +112,8 @@ module GoodData
65
112
  end
66
113
  end
67
114
  end
68
- GoodData.upload_to_user_webdav(temp.path)
115
+
116
+ client.upload_to_user_webdav(temp.path, opts)
69
117
  temp.path
70
118
  end
71
119
  end
@@ -80,15 +128,27 @@ module GoodData
80
128
  # @option options [String] :process_id ID of a process to be redeployed (do not set if you want to create a new process)
81
129
  # @option options [Boolean] :verbose (false) Switch on verbose mode for detailed logging
82
130
  def deploy(path, options = {})
131
+ client = options[:client]
132
+ fail ArgumentError, 'No :client specified' if client.nil?
133
+
134
+ p = options[:project]
135
+ fail ArgumentError, 'No :project specified' if p.nil?
136
+
137
+ project = GoodData::Project[p, options]
138
+ fail ArgumentError, 'No :project specified' if project.nil?
139
+
83
140
  path = Pathname(path) || fail('Path is not specified')
84
- files_to_exclude = options[:files_to_exclude].nil? ? [] : options[:files_to_exclude].map { |p| Pathname(p) }
141
+ files_to_exclude = options[:files_to_exclude].nil? ? [] : options[:files_to_exclude].map { |pname| Pathname(pname) }
85
142
  process_id = options[:process_id]
86
143
 
87
144
  type = options[:type] || 'GRAPH'
88
145
  deploy_name = options[:name]
146
+ fail ArgumentError, 'options[:name] can not be nil or empty!' if deploy_name.nil? || deploy_name.empty?
147
+
89
148
  verbose = options[:verbose] || false
90
149
  puts HighLine.color("Deploying #{path}", HighLine::BOLD) if verbose
91
- deployed_path = Process.upload_package(path, files_to_exclude)
150
+
151
+ deployed_path = Process.upload_package(path, files_to_exclude, :client => client, :project => project)
92
152
  data = {
93
153
  :process => {
94
154
  :name => deploy_name,
@@ -96,12 +156,14 @@ module GoodData
96
156
  :type => type
97
157
  }
98
158
  }
159
+
99
160
  res = if process_id.nil?
100
- GoodData.post("/gdc/projects/#{GoodData.project.pid}/dataload/processes", data)
161
+ client.post("/gdc/projects/#{project.pid}/dataload/processes", data)
101
162
  else
102
- GoodData.put("/gdc/projects/#{GoodData.project.pid}/dataload/processes/#{process_id}", data)
163
+ client.put("/gdc/projects/#{project.pid}/dataload/processes/#{process_id}", data)
103
164
  end
104
- process = Process.new(res)
165
+
166
+ process = client.create(Process, res, project: p)
105
167
  puts HighLine.color("Deploy DONE #{path}", HighLine::GREEN) if verbose
106
168
  process
107
169
  end
@@ -112,7 +174,7 @@ module GoodData
112
174
  end
113
175
 
114
176
  def delete
115
- GoodData.delete(uri)
177
+ client.delete(uri)
116
178
  end
117
179
 
118
180
  # Redeploy existing process.
@@ -128,7 +190,7 @@ module GoodData
128
190
  end
129
191
 
130
192
  def process
131
- json['process']
193
+ data['process']
132
194
  end
133
195
 
134
196
  def name
@@ -146,6 +208,7 @@ module GoodData
146
208
  def link
147
209
  links['self']
148
210
  end
211
+
149
212
  alias_method :uri, :link
150
213
 
151
214
  def obj_id
@@ -167,28 +230,28 @@ module GoodData
167
230
  end
168
231
 
169
232
  def schedules
170
- GoodData::Schedule[:all].select { |schedule| schedule.process_id == obj_id }
233
+ project.schedules.select { |schedule| schedule.process_id == obj_id }
171
234
  end
172
235
 
173
236
  def create_schedule(cron, executable, options = {})
174
- GoodData::Schedule.create(process_id, cron, executable, options)
237
+ project.create_schedule(process_id, cron, executable, options.merge(client: client, project: project))
175
238
  end
176
239
 
177
240
  def execute(executable, options = {})
178
241
  params = options[:params] || {}
179
242
  hidden_params = options[:hidden_params] || {}
180
- result = GoodData.post(executions_link,
181
- :execution => {
182
- :graph => executable.to_s,
183
- :params => params,
184
- :hiddenParams => hidden_params
185
- })
243
+ result = client.post(executions_link,
244
+ :execution => {
245
+ :graph => executable.to_s,
246
+ :params => params,
247
+ :hiddenParams => hidden_params
248
+ })
186
249
  begin
187
- GoodData.poll_on_code(result['executionTask']['links']['poll'])
250
+ client.poll_on_code(result['executionTask']['links']['poll'])
188
251
  rescue RestClient::RequestFailed => e
189
252
  raise(e)
190
253
  ensure
191
- result = GoodData.get(result['executionTask']['links']['detail'])
254
+ result = client.get(result['executionTask']['links']['detail'])
192
255
  if result['executionDetail']['status'] == 'ERROR'
193
256
  fail "Runing process failed. You can look at a log here #{result["executionDetail"]["logFileName"]}"
194
257
  end
@@ -1,11 +1,14 @@
1
1
  # encoding: UTF-8
2
2
 
3
+ require 'pmap'
4
+
5
+ require_relative '../rest/object'
6
+
3
7
  require_relative 'project'
4
8
 
5
9
  module GoodData
6
- # Account settings representation with some added sugar
7
- class Profile
8
- attr_reader :json
10
+ class Profile < GoodData::Rest::Object
11
+ attr_reader :user, :json
9
12
 
10
13
  EMPTY_OBJECT = {
11
14
  'accountSetting' => {
@@ -73,8 +76,7 @@ module GoodData
73
76
  # Gets user currently logged in
74
77
  # @return [GoodData::Profile] User currently logged-in
75
78
  def current
76
- json = GoodData.get GoodData.connection.user['profile']
77
- GoodData::Profile.new(json)
79
+ GoodData.connection.user
78
80
  end
79
81
 
80
82
  # Gets hash representing diff of profiles
@@ -207,7 +209,7 @@ module GoodData
207
209
 
208
210
  # Deletes this account settings
209
211
  def delete
210
- GoodData.delete uri
212
+ client.delete uri
211
213
  end
212
214
 
213
215
  # Gets hash representing diff of profiles
@@ -278,13 +280,6 @@ module GoodData
278
280
  @json['accountSetting']['login'] = val
279
281
  end
280
282
 
281
- # Get full name
282
- #
283
- # @return [String] Full name
284
- def name
285
- "#{first_name} #{last_name}"
286
- end
287
-
288
283
  # Gets the resource identifier
289
284
  #
290
285
  # @return [String] Resource identifier
@@ -328,14 +323,10 @@ module GoodData
328
323
  #
329
324
  # @return [Array<GoodData::Project>] Array of project where account settings belongs to
330
325
  def projects
331
- res = []
332
-
333
- projects = GoodData.get @json['accountSetting']['links']['projects']
334
- projects['projects'].each do |project|
335
- res << GoodData::Project.new(project)
326
+ projects = client.get @json['accountSetting']['links']['projects']
327
+ projects['projects'].map do |project|
328
+ client.create(GoodData::Project, project)
336
329
  end
337
-
338
- res
339
330
  end
340
331
 
341
332
  # Saves object if dirty, clears dirty flag
@@ -380,5 +371,12 @@ module GoodData
380
371
  def uri
381
372
  @json['accountSetting']['links']['self']
382
373
  end
374
+
375
+ private
376
+
377
+ def initialize(json)
378
+ @json = json
379
+ @user = @json['accountSetting']['firstName'] + ' ' + @json['accountSetting']['lastName']
380
+ end
383
381
  end
384
382
  end
@@ -3,16 +3,22 @@
3
3
  require 'csv'
4
4
  require 'zip'
5
5
  require 'fileutils'
6
+ require 'multi_json'
7
+ require 'pmap'
8
+ require 'zip'
6
9
 
7
- require_relative 'process'
8
10
  require_relative '../exceptions/no_project_error'
9
11
 
10
- require_relative '../mixins/mixins'
12
+ require_relative '../rest/resource'
13
+ require_relative '../mixins/author'
14
+ require_relative '../mixins/contributor'
15
+ require_relative '../mixins/rest_resource'
11
16
 
17
+ require_relative 'process'
12
18
  require_relative 'project_role'
13
19
 
14
20
  module GoodData
15
- class Project
21
+ class Project < GoodData::Rest::Resource
16
22
  USERSPROJECTS_PATH = '/gdc/account/profile/%s/projects'
17
23
  PROJECTS_PATH = '/gdc/projects'
18
24
  PROJECT_PATH = '/gdc/projects/%s'
@@ -24,20 +30,19 @@ module GoodData
24
30
  alias_method :to_json, :json
25
31
  alias_method :raw_data, :json
26
32
 
27
- include GoodData::Mixin::MetaGetter
28
- include GoodData::Mixin::DataGetter
29
-
30
- class << self
31
- include GoodData::Mixin::RootKeySetter
33
+ include GoodData::Mixin::RestResource
32
34
 
33
- include GoodData::Mixin::MetaPropertyReader
35
+ Project.root_key :project
34
36
 
35
- include GoodData::Mixin::MetaPropertyWriter
37
+ include GoodData::Mixin::Author
38
+ include GoodData::Mixin::Contributor
36
39
 
40
+ class << self
37
41
  # Returns an array of all projects accessible by
38
42
  # current user
39
- def all
40
- GoodData.profile.projects
43
+ def all(opts = {})
44
+ c = client(opts)
45
+ c.user.projects
41
46
  end
42
47
 
43
48
  # Returns a Project object identified by given string
@@ -46,10 +51,11 @@ module GoodData
46
51
  # - /gdc/projects/<id>
47
52
  # - <id>
48
53
  #
49
- def [](id, options = {})
50
- return id if id.respond_to?(:project?) && id.project?
54
+ def [](id, opts = {})
55
+ return id if id.instance_of?(GoodData::Project) || id.respond_to?(:project?) && id.project?
56
+
51
57
  if id == :all
52
- Project.all
58
+ Project.all(opts)
53
59
  else
54
60
  if id.to_s !~ %r{^(\/gdc\/(projects|md)\/)?[a-zA-Z\d]+$}
55
61
  fail(ArgumentError, 'wrong type of argument. Should be either project ID or path')
@@ -57,48 +63,46 @@ module GoodData
57
63
 
58
64
  id = id.match(/[a-zA-Z\d]+$/)[0] if id =~ /\//
59
65
 
60
- response = GoodData.get PROJECT_PATH % id
61
- Project.new response
66
+ c = client(opts)
67
+ fail ArgumentError, 'No :client specified' if c.nil?
68
+
69
+ response = c.get(PROJECT_PATH % id)
70
+ c.factory.create(Project, response)
62
71
  end
63
72
  end
64
73
 
65
- Project.root_key :project
66
-
67
- include GoodData::Mixin::RootKeyGetter
68
- include GoodData::Mixin::DataGetter
69
- include GoodData::Mixin::Author
70
- include GoodData::Mixin::Contributor
71
- include GoodData::Mixin::Timestamps
72
-
73
74
  # Create a project from a given attributes
74
75
  # Expected keys:
75
76
  # - :title (mandatory)
76
77
  # - :summary
77
78
  # - :template (default /projects/blank)
78
79
  #
79
- def create(attributes, &block)
80
- GoodData.logger.info "Creating project #{attributes[:title]}"
80
+ def create(opts = { :client => GoodData.connection }, &block)
81
+ GoodData.logger.info "Creating project #{opts[:title]}"
81
82
 
82
- auth_token = attributes[:auth_token] || GoodData.connection.auth_token
83
+ c = client(opts)
84
+ fail ArgumentError, 'No :client specified' if c.nil?
85
+
86
+ auth_token = opts[:auth_token] || GoodData.connection.auth_token
83
87
  fail 'You have to provide your token for creating projects as :auth_token parameter' if auth_token.nil? || auth_token.empty?
84
88
 
85
89
  json = {
86
90
  'project' =>
87
91
  {
88
92
  'meta' => {
89
- 'title' => attributes[:title],
90
- 'summary' => attributes[:summary] || 'No summary'
93
+ 'title' => opts[:title],
94
+ 'summary' => opts[:summary] || 'No summary'
91
95
  },
92
96
  'content' => {
93
- 'guidedNavigation' => attributes[:guided_navigation] || 1,
97
+ 'guidedNavigation' => opts[:guided_navigation] || 1,
94
98
  'authorizationToken' => auth_token,
95
- 'driver' => attributes[:driver] || 'Pg'
99
+ 'driver' => opts[:driver] || 'Pg'
96
100
  }
97
101
  }
98
102
  }
99
103
 
100
- json['project']['meta']['projectTemplate'] = attributes[:template] if attributes[:template] && !attributes[:template].empty?
101
- project = Project.new json
104
+ json['project']['meta']['projectTemplate'] = opts[:template] if opts[:template] && !opts[:template].empty?
105
+ project = c.create(Project, json)
102
106
  project.save
103
107
  # until it is enabled or deleted, recur. This should still end if there is a exception thrown out from RESTClient. This sometimes happens from WebApp when request is too long
104
108
  while project.state.to_s != 'enabled'
@@ -119,6 +123,13 @@ module GoodData
119
123
  project
120
124
  end
121
125
 
126
+ def find(opts = {}, client = GoodData::Rest::Client.client)
127
+ user = client.user
128
+ user.projects['projects'].map do |project|
129
+ client.create(GoodData::Project, project)
130
+ end
131
+ end
132
+
122
133
  def create_from_blueprint(blueprint, options = {})
123
134
  GoodData::Model::ProjectCreator.migrate(:spec => blueprint, :token => options[:auth_token])
124
135
  end
@@ -141,15 +152,18 @@ module GoodData
141
152
  end
142
153
  end
143
154
 
144
- def add_metric(options = {})
145
- options[:expression] || fail('Metric has to have its expression defined')
146
- m1 = GoodData::Metric.xcreate(options)
147
- m1.save
155
+ def add_metric(metric, options = {})
156
+ default = { client: client, project: self }
157
+ if metric.is_a?(String)
158
+ GoodData::Metric.xcreate(metric, options.merge(default))
159
+ else
160
+ GoodData::Metric.xcreate(metric.merge(default))
161
+ end
148
162
  end
149
163
  alias_method :create_metric, :add_metric
150
164
 
151
165
  def add_report(options = {})
152
- rep = GoodData::Report.create(options)
166
+ rep = GoodData::Report.create(options.merge(client: client, project: self))
153
167
  rep.save
154
168
  end
155
169
  alias_method :create_report, :add_report
@@ -161,13 +175,29 @@ module GoodData
161
175
  user_has_role?(GoodData.user, 'admin')
162
176
  end
163
177
 
178
+ # Helper for getting attributes of a project
179
+ #
180
+ # @param [String | Number | Object] Anything that you can pass to GoodData::Attribute[id]
181
+ # @return [GoodData::Attribute | Array<GoodData::Attribute>] fact instance or list
182
+ def attributes(id = :all)
183
+ GoodData::Attribute[id, project: self, client: client]
184
+ end
185
+
186
+ def attribute_by_title(title)
187
+ GoodData::Attribute.find_first_by_title(title, project: self, client: client)
188
+ end
189
+
190
+ def attributes_by_title(title)
191
+ GoodData::Attribute.find_by_title(title, project: self, client: client)
192
+ end
193
+
164
194
  # Gets project blueprint from the server
165
195
  #
166
196
  # @return [GoodData::ProjectRole] Project role if found
167
197
  def blueprint
168
- result = GoodData.get("/gdc/projects/#{pid}/model/view")
198
+ result = client.get("/gdc/projects/#{pid}/model/view")
169
199
  polling_url = result['asyncTask']['link']['poll']
170
- model = GoodData.poll_on_code(polling_url)
200
+ model = client.poll_on_code(polling_url)
171
201
  GoodData::Model::FromWire.from_wire(model)
172
202
  end
173
203
 
@@ -177,9 +207,9 @@ module GoodData
177
207
  def browser_uri(options = {})
178
208
  grey = options[:grey]
179
209
  if grey
180
- GoodData.connection.url + uri
210
+ client.connection.url + uri
181
211
  else
182
- GoodData.connection.url + '#s=' + uri
212
+ client.connection.url + '#s=' + uri
183
213
  end
184
214
  end
185
215
 
@@ -188,12 +218,12 @@ module GoodData
188
218
  # @return [GoodData::Project] Newly created project
189
219
  def clone(options = {})
190
220
  # TODO: Refactor so if export or import fails the new_project will be cleaned
191
- with_data = options[:data] || true
192
- with_users = options[:users] || false
221
+ with_data = options[:data].nil? ? true : options[:data]
222
+ with_users = options[:users].nil? ? false : options[:users]
193
223
  a_title = options[:title] || "Clone of #{title}"
194
224
 
195
225
  # Create the project first so we know that it is passing. What most likely is wrong is the tokena and the export actaully takes majoiryt of the time
196
- new_project = GoodData::Project.create(options.merge(:title => a_title))
226
+ new_project = GoodData::Project.create(options.merge(:title => a_title, :client => client))
197
227
 
198
228
  export = {
199
229
  :exportProject => {
@@ -202,11 +232,11 @@ module GoodData
202
232
  }
203
233
  }
204
234
 
205
- result = GoodData.post("/gdc/md/#{obj_id}/maintenance/export", export)
235
+ result = client.post("/gdc/md/#{obj_id}/maintenance/export", export)
206
236
  export_token = result['exportArtifact']['token']
207
237
 
208
238
  status_url = result['exportArtifact']['status']['uri']
209
- GoodData.poll_on_response(status_url) do |body|
239
+ client.poll_on_response(status_url) do |body|
210
240
  body['taskState']['status'] == 'RUNNING'
211
241
  end
212
242
 
@@ -216,35 +246,46 @@ module GoodData
216
246
  }
217
247
  }
218
248
 
219
- result = GoodData.post("/gdc/md/#{new_project.obj_id}/maintenance/import", import)
249
+ result = client.post("/gdc/md/#{new_project.obj_id}/maintenance/import", import)
220
250
  status_url = result['uri']
221
- GoodData.poll_on_response(status_url) do |body|
251
+ client.poll_on_response(status_url) do |body|
222
252
  body['taskState']['status'] == 'RUNNING'
223
253
  end
224
254
 
225
255
  new_project
226
256
  end
227
257
 
228
- def datasets
229
- blueprint.datasets
230
- datasets_uri = "#{md['data']}/sets"
231
- response = GoodData.get datasets_uri
232
- response['dataSetsInfo']['sets'].map do |ds|
233
- DataSet[ds['meta']['uri']]
234
- end
258
+ def create_schedule(process, date, executable, options = {})
259
+ GoodData::Schedule.create(process, date, executable, options.merge(:client => client, :project => self))
235
260
  end
236
261
 
237
- # Gets processes for the project
262
+ # Helper for getting dashboards of a project
238
263
  #
239
- # @return [Array<GoodData::Process>] Processes for the current project
240
- def processes
241
- GoodData::Process.all
264
+ # @param [String | Number | Object] Anything that you can pass to GoodData::Dashboard[id]
265
+ # @return [GoodData::Dashboard | Array<GoodData::Dashboard>] dashboard instance or list
266
+ def dashboards(id = :all)
267
+ GoodData::Dashboard[id, project: self, client: client]
268
+ end
269
+
270
+ def datasets
271
+ blueprint.datasets
242
272
  end
243
273
 
244
274
  # Deletes project
245
275
  def delete
246
276
  fail "Project '#{title}' with id #{uri} is already deleted" if state == :deleted
247
- GoodData.delete(uri)
277
+ client.delete(uri)
278
+ end
279
+
280
+ # Helper for getting rid of all data in the project
281
+ #
282
+ # @option options [Boolean] :force has to be added otherwise the operation is not performed
283
+ # @return [Array] Result of executing MAQLs
284
+ def delete_all_data(options = {})
285
+ return false unless options[:force]
286
+ datasets.pmap do |dataset|
287
+ execute_maql("SYNCHRONIZE {#{dataset.identifier}}")
288
+ end
248
289
  end
249
290
 
250
291
  # Deletes dashboards for project
@@ -252,30 +293,58 @@ module GoodData
252
293
  Dashboard.all.map { |data| Dashboard[data['link']] }.each { |d| d.delete }
253
294
  end
254
295
 
296
+ def deploy_process(path, options = {})
297
+ GoodData::Process.deploy(path, options.merge(client: client, project: self))
298
+ end
299
+
255
300
  # Executes DML expression. See (https://developer.gooddata.com/article/deleting-records-from-datasets)
256
301
  # for some examples and explanations
257
302
  #
258
303
  # @param dml [String] DML expression
304
+ # @return [Hash] Result of executing DML
259
305
  def execute_dml(dml)
260
306
  uri = "/gdc/md/#{pid}/dml/manage"
261
- result = GoodData.post(uri,
262
- manage: {
263
- maql: dml
264
- })
307
+ result = client.post(uri, manage: { maql: dml })
265
308
  polling_uri = result['uri']
266
- result = GoodData.get(polling_uri)
267
- while result['taskState'] && result['taskState']['status'] == 'WAIT'
268
- sleep 10
269
- result = GoodData.get polling_uri
309
+
310
+ client.poll_on_response(polling_uri) do |body|
311
+ body && body['taskState'] && body['taskState']['status'] == 'WAIT'
312
+ end
313
+ end
314
+
315
+ # Executes MAQL expression and waits for it to be finished.
316
+ #
317
+ # @param maql [String] MAQL expression
318
+ # @return [Hash] Result of executing MAQL
319
+ def execute_maql(maql)
320
+ ldm_links = client.get(md[GoodData::Model::LDM_CTG])
321
+ ldm_uri = Links.new(ldm_links)[GoodData::Model::LDM_MANAGE_CTG]
322
+ response = client.post(ldm_uri, manage: { maql: maql })
323
+ polling_uri = response['entries'].first['link']
324
+
325
+ client.poll_on_response(polling_uri) do |body|
326
+ body && body['wTaskStatus'] && body['wTaskStatus']['status'] == 'RUNNING'
270
327
  end
271
328
  end
272
329
 
273
330
  # Helper for getting facts of a project
274
331
  #
275
332
  # @param [String | Number | Object] Anything that you can pass to GoodData::Fact[id]
276
- # @return [GoodData::Fact] fact instance or list
277
- def fact(id)
278
- GoodData::Fact[id, project: self]
333
+ # @return [GoodData::Fact | Array<GoodData::Fact>] fact instance or list
334
+ def facts(id = :all)
335
+ GoodData::Fact[id, project: self, client: client]
336
+ end
337
+
338
+ def fact_by_title(title)
339
+ GoodData::Fact.find_first_by_title(title, project: self, client: client)
340
+ end
341
+
342
+ def facts_by_title(title)
343
+ GoodData::Fact.find_by_title(title, project: self, client: client)
344
+ end
345
+
346
+ def find_attribute_element_value(uri)
347
+ GoodData::Attribute.find_element_value(uri, client: client, project: self)
279
348
  end
280
349
 
281
350
  # Gets project role by its identifier
@@ -348,6 +417,107 @@ module GoodData
348
417
  nil
349
418
  end
350
419
 
420
+ # Exports project users to file
421
+ def import_users(path, opts = { :header => true }, &block)
422
+ opts[:path] = path
423
+
424
+ ##########################
425
+ # Caching/Cached objects
426
+ ##########################
427
+ domains = {}
428
+ current_users = users
429
+ role_list = roles
430
+
431
+ ##########################
432
+ # Load users from CSV
433
+ ##########################
434
+ new_users = GoodData::Helpers.csv_read(opts) do |row|
435
+ json = {}
436
+ if block_given?
437
+ json = yield row
438
+ else
439
+ json = {
440
+ 'user' => {
441
+ 'content' => {
442
+ 'email' => row[0],
443
+ 'login' => row[1],
444
+ 'firstname' => row[2],
445
+ 'lastname' => row[3]
446
+ },
447
+ 'meta' => {}
448
+ }
449
+ }
450
+ end
451
+
452
+ GoodData::User.new(json)
453
+ end
454
+
455
+ ##########################
456
+ # Diff users
457
+ ##########################
458
+ diff = GoodData::User.diff_list(current_users, new_users)
459
+
460
+ ##########################
461
+ # Create new users
462
+ ##########################
463
+ diff[:added].map do |user|
464
+ # TODO: Add user here
465
+ domain_name = user.json['user']['content']['domain']
466
+
467
+ # Lookup for domain in cache'
468
+ domain = domains[domain_name]
469
+
470
+ # Get domain info from REST, add to cache
471
+ if domain.nil?
472
+ domain = {
473
+ :domain => GoodData::Domain[domain_name],
474
+ :users => GoodData::Domain[domain_name].users
475
+ }
476
+
477
+ domain[:users_map] = Hash[domain[:users].map { |u| [u.email, u] }]
478
+ domains[domain_name] = domain
479
+ end
480
+
481
+ # Check if user exists in domain
482
+ domain_user = domain[:users_map][user.email]
483
+
484
+ # Create domain user if needed
485
+ unless domain_user
486
+ password = user.json['user']['content']['password']
487
+
488
+ # Fill necessary user data
489
+ user_data = {
490
+ :login => user.login,
491
+ :firstName => user.first_name,
492
+ :lastName => user.last_name,
493
+ :password => password,
494
+ :verifyPassword => password,
495
+ :email => user.login
496
+ }
497
+
498
+ # Add created user to cache
499
+ domain_user = domain[:domain].add_user(user_data)
500
+ domain[:users] << domain_user
501
+ domain[:users_map][user.email] = domain_user
502
+ end
503
+
504
+ # Lookup for role
505
+ role_name = user.json['user']['content']['role'] || 'readOnlyUser'
506
+ role = get_role_by_identifier(role_name, role_list)
507
+ next if role.nil?
508
+
509
+ # Assign user project role
510
+ add_user(domain_user, [role.uri])
511
+ end
512
+
513
+ ##########################
514
+ # Remove old users
515
+ ##########################
516
+ # diff[:removed].map do |user|
517
+ # user.disable(self)
518
+ # end
519
+ end
520
+
351
521
  # Checks whether user has particular role in given proejct
352
522
  #
353
523
  # @param user [GoodData::Profile | GoodData::Membership | String] User in question. Can be passed by login (String), profile or membershi objects
@@ -365,6 +535,7 @@ module GoodData
365
535
  #
366
536
  # @param json Json used for initialization
367
537
  def initialize(json)
538
+ super
368
539
  @json = json
369
540
  end
370
541
 
@@ -403,21 +574,17 @@ module GoodData
403
574
  }
404
575
 
405
576
  url = "/gdc/projects/#{pid}/invitations"
406
- GoodData.post(url, data)
577
+ client.post(url, data)
407
578
  end
408
579
 
409
580
  # Returns invitations to project
410
581
  #
411
582
  # @return [Array<GoodData::Invitation>] List of invitations
412
583
  def invitations
413
- res = []
414
-
415
- tmp = GoodData.get @json['project']['links']['invitations']
416
- tmp['invitations'].each do |invitation|
417
- res << GoodData::Invitation.new(invitation)
584
+ invitations = client.get @json['project']['links']['invitations']
585
+ invitations['invitations'].pmap do |invitation|
586
+ client.create GoodData::Invitation, invitation
418
587
  end
419
-
420
- res
421
588
  end
422
589
 
423
590
  # Returns project related links
@@ -427,12 +594,21 @@ module GoodData
427
594
  data['links']
428
595
  end
429
596
 
430
- def lint
431
- blueprint.lint
597
+ # Helper for getting labels of a project
598
+ #
599
+ # @param [String | Number | Object] Anything that you can pass to
600
+ # GoodData::Label[id] + it supports :all as welll
601
+ # @return [GoodData::Fact | Array<GoodData::Fact>] fact instance or list
602
+ def labels(id = :all, opts = {})
603
+ if id == :all
604
+ attributes.pmapcat { |a| a.labels }.uniq
605
+ else
606
+ GoodData::Label[id, opts.merge(project: self, client: client)]
607
+ end
432
608
  end
433
609
 
434
610
  def md
435
- @md ||= Links.new GoodData.get(data['links']['metadata'])
611
+ @md ||= client.create(Links, client.get(data['links']['metadata']))
436
612
  end
437
613
 
438
614
  # Gets membership for profile specified
@@ -449,6 +625,21 @@ module GoodData
449
625
  list.find { |m| m.login == profile.login }
450
626
  end
451
627
 
628
+ # Helper for getting metrics of a project
629
+ #
630
+ # @return [Array<GoodData::Metric>] matric instance or list
631
+ def metrics(id = :all, opts = { :full => true })
632
+ GoodData::Metric[id, opts.merge(project: self, client: client)]
633
+ end
634
+
635
+ def metric_by_title(title)
636
+ GoodData::Metric.find_first_by_title(title, project: self, client: client)
637
+ end
638
+
639
+ def metrics_by_title(title)
640
+ GoodData::Metric.find_by_title(title, project: self, client: client)
641
+ end
642
+
452
643
  # Checks if the profile is member of project
453
644
  #
454
645
  # @param [GoodData::Profile] profile - Profile to be checked
@@ -467,26 +658,32 @@ module GoodData
467
658
 
468
659
  alias_method :pid, :obj_id
469
660
 
470
- def partial_md_export(objects, options = {})
471
- # TODO: refactor polling to md_polling in client
661
+ # Helper for getting objects of a project
662
+ #
663
+ # @return [Array<GoodData::MdObject>] object instance or list
664
+ def objects(id, opts = {})
665
+ GoodData::MdObject[id, opts.merge(project: self, client: client)]
666
+ end
472
667
 
473
- fail 'Nothing to migrate. You have to pass list of objects, ids or uris that you would like to migrate' if objects.nil? || objects.empty?
474
- fail 'The objects to migrate has to be provided as an array' unless objects.is_a?(Array)
668
+ def partial_md_export(objs, options = {})
669
+ fail 'Nothing to migrate. You have to pass list of objects, ids or uris that you would like to migrate' if objs.nil?
670
+ objs = [objs] unless objs.is_a?(Array)
671
+ fail 'Nothing to migrate. The list you provided is empty' if objs.empty?
475
672
 
476
673
  target_project = options[:project]
477
674
  fail 'You have to provide a project instance or project pid to migrate to' if target_project.nil?
478
- target_project = GoodData::Project[target_project]
479
- objects = objects.map { |obj| GoodData::MdObject[obj] }
675
+ target_project = client.projects(target_project)
676
+ objs = objs.pmap { |obj| objects(obj) }
480
677
  export_payload = {
481
678
  :partialMDExport => {
482
- :uris => objects.map { |obj| obj.uri }
679
+ :uris => objs.map { |obj| obj.uri }
483
680
  }
484
681
  }
485
- result = GoodData.post("#{GoodData.project.md['maintenance']}/partialmdexport", export_payload)
682
+ result = client.post("#{md['maintenance']}/partialmdexport", export_payload)
486
683
  polling_url = result['partialMDArtifact']['status']['uri']
487
684
  token = result['partialMDArtifact']['token']
488
685
 
489
- polling_result = GoodData.poll_on_response(polling_url) do |body|
686
+ polling_result = client.poll_on_response(polling_url) do |body|
490
687
  body['wTaskStatus'] && body['wTaskStatus']['status'] == 'RUNNING'
491
688
  end
492
689
 
@@ -500,10 +697,10 @@ module GoodData
500
697
  }
501
698
  }
502
699
 
503
- result = GoodData.post("#{target_project.md['maintenance']}/partialmdimport", import_payload)
700
+ result = client.post("#{target_project.md['maintenance']}/partialmdimport", import_payload)
504
701
  polling_url = result['uri']
505
702
 
506
- GoodData.poll_on_response(polling_url) do |body|
703
+ client.poll_on_response(polling_url) do |body|
507
704
  body['wTaskStatus'] && body['wTaskStatus']['status'] == 'RUNNING'
508
705
  end
509
706
 
@@ -512,6 +709,14 @@ module GoodData
512
709
 
513
710
  alias_method :transfer_objects, :partial_md_export
514
711
 
712
+ # Helper for getting processes of a project
713
+ #
714
+ # @param [String | Number | Object] Anything that you can pass to GoodData::Report[id]
715
+ # @return [GoodData::Report | Array<GoodData::Report>] report instance or list
716
+ def processes(id = :all)
717
+ GoodData::Process[id, project: self, client: client]
718
+ end
719
+
515
720
  # Checks if this object instance is project
516
721
  #
517
722
  # @return [Boolean] Return true for all instances
@@ -520,8 +725,8 @@ module GoodData
520
725
  end
521
726
 
522
727
  def info
523
- results = blueprint.datasets.map do |ds|
524
- [ds, ds.count]
728
+ results = blueprint.datasets.pmap do |ds|
729
+ [ds, ds.count(self)]
525
730
  end
526
731
  puts title
527
732
  puts GoodData::Helpers.underline(title)
@@ -544,21 +749,38 @@ module GoodData
544
749
  # Forces project to reload
545
750
  def reload!
546
751
  if saved?
547
- response = GoodData.get(uri)
752
+ response = client.get(uri)
548
753
  @json = response
549
754
  end
550
755
  self
551
756
  end
552
757
 
758
+ # Helper for getting reports of a project
759
+ #
760
+ # @param [String | Number | Object] Anything that you can pass to GoodData::Report[id]
761
+ # @return [GoodData::Report | Array<GoodData::Report>] report instance or list
762
+ def reports(id = :all)
763
+ GoodData::Report[id, project: self, client: client]
764
+ end
765
+
766
+ # Helper for getting report definitions of a project
767
+ #
768
+ # @param [String | Number | Object] Anything that you can pass to GoodData::ReportDefinition[id]
769
+ # @return [GoodData::ReportDefinition | Array<GoodData::ReportDefinition>] report definition instance or list
770
+ def report_definitions(id = :all, options = {})
771
+ GoodData::ReportDefinition[id, options.merge(project: self, client: client)]
772
+ end
773
+
553
774
  # Gets the list or project roles
554
775
  #
555
776
  # @return [Array<GoodData::ProjectRole>] List of roles
556
777
  def roles
557
778
  url = "/gdc/projects/#{pid}/roles"
558
- tmp = GoodData.get(url)
559
- tmp['projectRoles']['roles'].map do |role_url|
560
- json = GoodData.get role_url
561
- GoodData::ProjectRole.new(json)
779
+
780
+ tmp = client.get(url)
781
+ tmp['projectRoles']['roles'].pmap do |role_url|
782
+ json = client.get role_url
783
+ client.create(GoodData::ProjectRole, json)
562
784
  end
563
785
  end
564
786
 
@@ -569,11 +791,11 @@ module GoodData
569
791
  data_to_send['project']['content'].delete('isPublic')
570
792
  data_to_send['project']['content'].delete('state')
571
793
  response = if uri
572
- GoodData.post(PROJECT_PATH % pid, data_to_send)
573
- GoodData.get uri
794
+ client.post(PROJECT_PATH % pid, data_to_send)
795
+ client.get uri
574
796
  else
575
- result = GoodData.post(PROJECTS_PATH, data_to_send)
576
- GoodData.get result['uri']
797
+ result = client.post(PROJECTS_PATH, data_to_send)
798
+ client.get result['uri']
577
799
  end
578
800
  @json = response
579
801
  self
@@ -587,12 +809,10 @@ module GoodData
587
809
  !res
588
810
  end
589
811
 
590
- # Gets project schedules
591
- #
592
- # @return [Array<GoodData::Schedule>] List of schedules
593
- def schedules
594
- tmp = GoodData.get @json['project']['links']['schedules']
595
- tmp['schedules']['items'].map { |schedule| GoodData::Schedule.new(schedule) }
812
+ # @param [String | Number | Object] Anything that you can pass to GoodData::Schedule[id]
813
+ # @return [GoodData::Schedule | Array<GoodData::Schedule>] schedule instance or list
814
+ def schedules(id = :all)
815
+ GoodData::Schedule[id, project: self, client: client]
596
816
  end
597
817
 
598
818
  # Gets SLIs data
@@ -602,7 +822,7 @@ module GoodData
602
822
  link = "#{data['links']['metadata']}#{SLIS_PATH}"
603
823
 
604
824
  # FIXME: Review what to do with passed extra argument
605
- Metadata.new GoodData.get(link)
825
+ Metadata.new client.get(link)
606
826
  end
607
827
 
608
828
  # Gets project state
@@ -637,14 +857,10 @@ module GoodData
637
857
  #
638
858
  # @return [Array<GoodData::User>] List of users
639
859
  def users
640
- res = []
641
-
642
- tmp = GoodData.get @json['project']['links']['users']
860
+ tmp = client.get @json['project']['links']['users']
643
861
  tmp['users'].map do |user|
644
- res << GoodData::Membership.new(user)
862
+ client.factory.create(GoodData::Membership, user)
645
863
  end
646
-
647
- res
648
864
  end
649
865
 
650
866
  alias_method :members, :users
@@ -660,9 +876,10 @@ module GoodData
660
876
 
661
877
  # Get domain info from REST, add to cache
662
878
  if domain.nil?
879
+ d = GoodData::Domain[domain_name, { :client => client }]
663
880
  domain = {
664
- :domain => GoodData::Domain[domain_name],
665
- :users => GoodData::Domain[domain_name].users
881
+ :domain => d,
882
+ :users => d.users(:client => client)
666
883
  }
667
884
 
668
885
  domain[:users_map] = Hash[domain[:users].map { |u| [u.email, u] }]
@@ -735,7 +952,7 @@ module GoodData
735
952
  #
736
953
  # @param list List of users to be disabled
737
954
  def users_remove(list)
738
- list.map do |user|
955
+ list.pmap do |user|
739
956
  user.disable
740
957
  end
741
958
  end
@@ -772,7 +989,7 @@ module GoodData
772
989
  }
773
990
  }
774
991
 
775
- GoodData.post url, payload
992
+ client.post url, payload
776
993
  end
777
994
 
778
995
  alias_method :add_user, :set_user_roles
@@ -782,12 +999,12 @@ module GoodData
782
999
  # @param list List of users to be updated
783
1000
  # @param role_list Optional list of cached roles to prevent unnecessary server round-trips
784
1001
  def set_users_roles(list, role_list = roles)
785
- list.map do |user_hash|
1002
+ list.pmap do |user_hash|
786
1003
  user = user_hash[:user]
787
1004
  roles = user_hash[:role] || user_hash[:roles]
788
1005
  {
789
1006
  :user => user,
790
- :result => set_user_roles(user, roles, role_list)
1007
+ :result => set_user_roles(user, roles)
791
1008
  }
792
1009
  end
793
1010
  end
@@ -800,9 +1017,9 @@ module GoodData
800
1017
  # invalid_objects - Checks metadata for invalid/corrupted objects.
801
1018
  # asyncTask response
802
1019
  def validate(filters = %w(ldm pdm metric_filter invalid_objects))
803
- response = GoodData.post "#{GoodData.project.md['validate-project']}", 'validateProject' => filters
1020
+ response = client.post "#{md['validate-project']}", 'validateProject' => filters
804
1021
  polling_link = response['asyncTask']['link']['poll']
805
- GoodData.poll_on_response(polling_link) do |body|
1022
+ client.poll_on_response(polling_link) do |body|
806
1023
  body['wTaskStatus'] && body['wTaskStatus']['status'] == 'RUNNING'
807
1024
  end
808
1025
  end