gooddata 0.6.7 → 0.6.8

Sign up to get free protection for your applications and to get access to all the features.
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