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,28 +0,0 @@
1
- # encoding: UTF-8
2
-
3
- require 'pp'
4
-
5
- require_relative '../shared'
6
- require_relative '../../commands/role'
7
-
8
- GoodData::CLI.module_eval do
9
- desc 'Basic Role Management'
10
- arg_name 'list'
11
- command :role do |c|
12
- c.desc 'List roles'
13
- c.command :list do |list|
14
- list.action do |global_options, options, args|
15
- opts = options.merge(global_options)
16
- GoodData.connect(opts)
17
-
18
- pid = global_options[:project_id]
19
- fail 'Project ID has to be provided' if pid.nil? || pid.empty?
20
-
21
- role_list = GoodData::Command::Role.list(pid)
22
- role_list.each do |k, v|
23
- puts [k, v[:uri]].join(',')
24
- end
25
- end
26
- end
27
- end
28
- end
@@ -1,392 +0,0 @@
1
- # encoding: UTF-8
2
-
3
- require 'multi_json'
4
- require 'rest-client'
5
-
6
- require_relative '../version'
7
-
8
- module GoodData
9
- # # GoodData HTTP wrapper
10
- #
11
- # Provides a convenient HTTP wrapper for talking with the GoodData API.
12
- #
13
- # Remember that the connection is shared amongst the entire application.
14
- # Therefore you can't be logged in to more than _one_ GoodData account.
15
- # per session. Simultaneous connections to multiple GoodData accounts is not
16
- # supported at this time.
17
- #
18
- # The GoodData API is a RESTful API that communicates using JSON. This wrapper
19
- # makes sure that the session is stored between requests and that the JSON is
20
- # parsed both when sending and receiving.
21
- #
22
- # ## Usage
23
- #
24
- # Before a connection can be made to the GoodData API, you have to supply the user credentials like this:
25
- #
26
- # Connection.new(username, password)
27
- #
28
- # To send a HTTP request use either the get, post or delete methods documented below.
29
- #
30
- class Connection
31
- DEFAULT_URL = 'https://secure.gooddata.com'
32
- LOGIN_PATH = '/gdc/account/login'
33
- TOKEN_PATH = '/gdc/account/token'
34
-
35
- attr_reader(:auth_token, :url)
36
- attr_accessor :status, :options
37
-
38
- # Options:
39
- # * :tries - Number of retries to perform. Defaults to 1.
40
- # * :on - The Exception on which a retry will be performed. Defaults to Exception, which retries on any Exception.
41
- #
42
- # ### Example
43
- #
44
- # retryable(:tries => 1, :on => OpenURI::HTTPError) do
45
- # # your code here
46
- # end
47
- #
48
- def retryable(options = {}, &block)
49
- opts = { :tries => 1, :on => Exception }.merge(options)
50
-
51
- retry_exception, retries = opts[:on], opts[:tries]
52
-
53
- begin
54
- return yield
55
- rescue retry_exception
56
- retry if (retries -= 1) > 0
57
- end
58
-
59
- yield
60
- end
61
-
62
- # Set the GoodData account credentials.
63
- #
64
- # This have to be performed before any calls to the API.
65
- #
66
- # @param username The GoodData account username
67
- # @param password The GoodData account password
68
- #
69
- def initialize(username, password, options = {})
70
- @status = :not_connected
71
- @username = username
72
- @password = password
73
- @url = options[:server] || DEFAULT_URL
74
- @auth_token = options[:gdc_temporary_token]
75
- @options = options
76
-
77
- @headers = options[:headers] || {}
78
-
79
- default_headers = {
80
- :content_type => :json,
81
- :accept => [:json, :zip],
82
- :user_agent => GoodData.gem_version_string
83
- }
84
- default_headers.merge! @headers
85
-
86
- @server = RestClient::Resource.new @url,
87
- :timeout => @options[:timeout],
88
- :headers => default_headers
89
-
90
- @server = create_server_connection(@url, @options)
91
- end
92
-
93
- # Returns the user JSON object of the currently logged in GoodData user account.
94
- def user
95
- ensure_connection
96
- @user
97
- end
98
-
99
- # Performs a HTTP GET request.
100
- #
101
- # Retuns the JSON response formatted as a Hash object.
102
- #
103
- # @param path The HTTP path on the GoodData server (must be prefixed with a forward slash)
104
- #
105
- # ### Examples
106
- #
107
- # Connection.new(username, password).get '/gdc/projects'
108
- #
109
- def get(path, options = {})
110
- GoodData.logger.debug "GET #{@server}#{path}"
111
- ensure_connection
112
- b = proc { @server[path].get cookies }
113
- process_response(options, &b)
114
- end
115
-
116
- # Performs a HTTP POST request.
117
- #
118
- # Retuns the JSON response formatted as a Hash object.
119
- #
120
- # @param path The HTTP path on the GoodData server (must be prefixed with a forward slash)
121
- # @param data The payload data in the format of a Hash object
122
- #
123
- # ### Examples
124
- #
125
- # Connection.new(username, password).post '/gdc/projects', { ... }
126
- #
127
- def post(path, data, options = {})
128
- GoodData.logger.debug("POST #{@server}#{path}, payload: #{scrub_params(data, [:password, :login, :authorizationToken])}")
129
- ensure_connection
130
- payload = data.is_a?(Hash) ? data.to_json : data
131
- b = proc { @server[path].post payload, cookies }
132
- process_response(options, &b)
133
- end
134
-
135
- # Performs a HTTP PUT request.
136
- #
137
- # Retuns the JSON response formatted as a Hash object.
138
- #
139
- # @param path The HTTP path on the GoodData server (must be prefixed with a forward slash)
140
- # @param data The payload data in the format of a Hash object
141
- #
142
- # ### Examples
143
- #
144
- # Connection.new(username, password).put '/gdc/projects', { ... }
145
- #
146
- def put(path, data, options = {})
147
- payload = data.is_a?(Hash) ? data.to_json : data
148
- GoodData.logger.debug "PUT #{@server}#{path}, payload: #{payload}"
149
- ensure_connection
150
- b = proc { @server[path].put payload, cookies }
151
- process_response(options, &b)
152
- end
153
-
154
- # Performs a HTTP DELETE request.
155
- #
156
- # Retuns the JSON response formatted as a Hash object.
157
- #
158
- # @param path The HTTP path on the GoodData server (must be prefixed with a forward slash)
159
- #
160
- # ### Examples
161
- #
162
- # Connection.new(username, password).delete '/gdc/project/1'
163
- #
164
- def delete(path, options = {})
165
- GoodData.logger.debug "DELETE #{@server}#{path}"
166
- ensure_connection
167
- b = proc { @server[path].delete cookies }
168
- process_response(options, &b)
169
- end
170
-
171
- # Get the cookies associated with the current connection.
172
- def cookies
173
- @cookies ||= { :cookies => {} }
174
- end
175
-
176
- # Set the cookies used when communicating with the GoodData API.
177
- def merge_cookies!(cookies)
178
- self.cookies
179
- @cookies[:cookies].merge! cookies
180
- end
181
-
182
- # Returns true if a connection have been established to the GoodData API
183
- # and the login was successful.
184
- def logged_in?
185
- @status == :logged_in
186
- end
187
-
188
- def url=(url = nil)
189
- @url = url || DEFAULT_URL
190
- @server = create_server_connection(@url, @options)
191
- end
192
-
193
- # The connection will automatically be established once it's needed, which it
194
- # usually is when either the user, get, post or delete method is called. If you
195
- # want to force a connection (or a re-connect) you can use this method.
196
- def connect!
197
- connect
198
- end
199
-
200
- # Uploads a file to GoodData server
201
- # /uploads/ resources are special in that they use a different
202
- # host and a basic authentication.
203
- def upload(file, options = {})
204
- ensure_connection
205
-
206
- dir = options[:directory] || ''
207
- staging_uri = options[:staging_url].to_s
208
- url = dir.empty? ? staging_uri : URI.join(staging_uri, "#{dir}/").to_s
209
-
210
- # Make a directory, if needed
211
- unless dir.empty?
212
- method = :get
213
- GoodData.logger.debug "#{method}: #{url}"
214
- begin
215
- # first check if it does exits
216
- RestClient::Request.execute({
217
- :method => method,
218
- :url => url,
219
- :timeout => @options[:timeout],
220
- :headers => {
221
- :user_agent => GoodData.gem_version_string
222
- }
223
- }.merge(cookies)
224
- )
225
- rescue RestClient::Exception => e
226
- if e.http_code == 404
227
- method = :mkcol
228
- GoodData.logger.debug "#{method}: #{url}"
229
- RestClient::Request.execute({
230
- :method => method,
231
- :url => url,
232
- :timeout => @options[:timeout],
233
- :headers => {
234
- :user_agent => GoodData.gem_version_string
235
- }
236
- }.merge(cookies))
237
- end
238
- end
239
- end
240
-
241
- payload = options[:stream] ? 'file' : File.read(file)
242
- filename = options[:filename] || options[:stream] ? 'randome-filename.txt' : File.basename(file)
243
-
244
- # Upload the file
245
- # puts "uploading the file #{URI.join(url, filename).to_s}"
246
- req = RestClient::Request.new(
247
- :method => :put,
248
- :url => URI.join(url, filename).to_s,
249
- :timeout => @options[:timeout],
250
- :headers => {
251
- :user_agent => GoodData.gem_version_string
252
- },
253
- :payload => payload,
254
- :raw_response => true,
255
- :user => @username,
256
- :password => @password
257
- )
258
- # .merge(cookies))
259
- req.execute
260
- true
261
- end
262
-
263
- def download(what, where, options = {})
264
- staging_uri = options[:staging_url].to_s
265
- url = staging_uri + what
266
- req = RestClient::Request.new(
267
- :method => 'GET',
268
- :url => url,
269
- :user => @username,
270
- :password => @password)
271
-
272
- if where.is_a?(String)
273
- File.open(where, 'w') do |f|
274
- req.execute do |chunk, x, y|
275
- f.write chunk
276
- end
277
- end
278
- else
279
- # Assume it is a IO stream
280
- req.execute do |chunk, x, y|
281
- where.write chunk
282
- end
283
- end
284
- end
285
-
286
- def connected?
287
- @status == :logged_in
288
- end
289
-
290
- def disconnect
291
- if connected? && GoodData.connection.user['state']
292
- GoodData.delete(GoodData.connection.user['state'])
293
- @status = :not_connected
294
- end
295
- end
296
-
297
- private
298
-
299
- def create_server_connection(url, options)
300
- RestClient::Resource.new url,
301
- :timeout => options[:timeout],
302
- :headers => {
303
- :content_type => :json,
304
- :accept => [:json, :zip],
305
- :user_agent => GoodData.gem_version_string
306
- }
307
- end
308
-
309
- def ensure_connection
310
- connect if @status == :not_connected
311
- end
312
-
313
- def connect
314
- GoodData.logger.info 'Connecting to GoodData...'
315
- @status = :connecting
316
- authenticate
317
- end
318
-
319
- def authenticate
320
- credentials = {
321
- 'postUserLogin' => {
322
- 'login' => @username,
323
- 'password' => @password,
324
- 'remember' => 1
325
- }
326
- }
327
- GoodData.logger.debug 'Logging in...'
328
- @user = post(LOGIN_PATH, credentials, :dont_reauth => true)['userLogin']
329
- refresh_token :dont_reauth => true # avoid infinite loop if refresh_token fails with 401
330
-
331
- @status = :logged_in
332
- end
333
-
334
- def process_response(options = {}, &block)
335
- begin
336
- response = block.call
337
- rescue RestClient::Unauthorized
338
- raise $ERROR_INFO if options[:dont_reauth]
339
- refresh_token
340
- response = block.call
341
- end
342
- merge_cookies! response.cookies
343
- content_type = response.headers[:content_type]
344
- return response if options[:process] == false
345
-
346
- if content_type == 'application/json' || content_type == 'application/json;charset=UTF-8'
347
- result = response.to_str == '""' ? {} : MultiJson.load(response.to_str)
348
- GoodData.logger.debug "Response: #{result.inspect}"
349
- elsif ['text/plain;charset=UTF-8', 'text/plain; charset=UTF-8', 'text/plain'].include?(content_type)
350
- result = response
351
- GoodData.logger.debug 'Response: plain text'
352
- elsif content_type == 'application/zip'
353
- result = response
354
- GoodData.logger.debug 'Response: a zipped stream'
355
- elsif response.headers[:content_length].to_s == '0'
356
- result = nil
357
- GoodData.logger.debug 'Response: Empty response possibly 204'
358
- elsif response.code == 204
359
- result = nil
360
- GoodData.logger.debug 'Response: 204 no content'
361
- else
362
- fail "Unsupported response content type '%s':\n%s" % [content_type, response.to_str[0..127]]
363
- end
364
- result
365
- rescue RestClient::Exception => e
366
- GoodData.logger.debug "Response: #{e.response}"
367
- raise $ERROR_INFO
368
- end
369
-
370
- def refresh_token(options = {})
371
- GoodData.logger.debug 'Getting authentication token...'
372
- begin
373
- get TOKEN_PATH, :dont_reauth => true # avoid infinite loop GET fails with 401
374
- rescue RestClient::Unauthorized
375
- raise $ERROR_INFO if options[:dont_reauth]
376
- authenticate
377
- end
378
- end
379
-
380
- def scrub_params(params, keys)
381
- keys = keys.reduce([]) { |a, e| a.concat([e.to_s, e.to_sym]) }
382
-
383
- new_params = params.deep_dup
384
- GoodData::Helpers.hash_dfs(new_params) do |k, key|
385
- keys.each do |key_to_scrub|
386
- k[key_to_scrub] = ('*' * k[key_to_scrub].length) if k && k.key?(key_to_scrub) && k[key_to_scrub]
387
- end
388
- end
389
- new_params
390
- end
391
- end
392
- end
@@ -1,14 +0,0 @@
1
- # encoding: UTF-8
2
-
3
- module GoodData
4
- module Threaded
5
- # Used internally for thread safety
6
- def threaded
7
- Thread.current[:goooddata] ||= {}
8
- end
9
- end
10
-
11
- class << self
12
- include Threaded
13
- end
14
- end
@@ -1,25 +0,0 @@
1
- # encoding: UTF-8
2
-
3
- module GoodData
4
- module Model
5
- class MdObject
6
- attr_accessor :name, :title
7
-
8
- def visual
9
- "TITLE \"#{title_esc}\""
10
- end
11
-
12
- def title_esc
13
- title.gsub(/"/, "\\\"")
14
- end
15
-
16
- ##
17
- # Generates an identifier from the object name by transliterating
18
- # non-Latin character and then dropping non-alphanumerical characters.
19
- #
20
- def identifier
21
- @identifier ||= "#{type_prefix}.#{name}"
22
- end
23
- end
24
- end
25
- end