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
@@ -0,0 +1,318 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rest-client'
4
+
5
+ require_relative '../helpers/auth_helpers'
6
+
7
+ require_relative 'connections/connections'
8
+ require_relative 'object_factory'
9
+
10
+ require_relative '../mixins/inspector'
11
+
12
+ module GoodData
13
+ module Rest
14
+ # User's interface to GoodData Platform.
15
+ #
16
+ # MUST provide way to use - DELETE, GET, POST, PUT
17
+ # SHOULD provide way to use - HEAD, Bulk GET ...
18
+ class Client
19
+ #################################
20
+ # Constants
21
+ #################################
22
+ DEFAULT_CONNECTION_IMPLEMENTATION = Connections::RestClientConnection
23
+
24
+ #################################
25
+ # Class variables
26
+ #################################
27
+ @@instance = nil # rubocop:disable ClassVars
28
+
29
+ #################################
30
+ # Getters/Setters
31
+ #################################
32
+
33
+ # Decide if we need provide direct access to connection
34
+ attr_reader :connection
35
+
36
+ # TODO: Decide if we need provide direct access to factory
37
+ attr_reader :factory
38
+
39
+ include Mixin::Inspector
40
+ inspector :object_id
41
+
42
+ #################################
43
+ # Class methods
44
+ #################################
45
+ class << self
46
+ # Globally available way to connect (and create client and set global instance)
47
+ #
48
+ # ## HACK
49
+ # To make transition from old implementation to new one following HACK IS TEMPORARILY ENGAGED!
50
+ #
51
+ # 1. First call of #connect sets the GoodData::Rest::Client.instance (static, singleton instance)
52
+ # 2. There are METHOD functions with same signature as their CLASS counterparts using singleton instance
53
+ #
54
+ # ## Example
55
+ #
56
+ # client = GoodData.connect('jon.smith@goodddata.com', 's3cr3tp4sw0rd')
57
+ #
58
+ # @param username [String] Username to be used for authentication
59
+ # @param password [String] Password to be used for authentication
60
+ # @return [GoodData::Rest::Client] Client
61
+ def connect(username, password, opts = {})
62
+ new_opts = opts.dup
63
+ if username.is_a?(Hash) && username.key?(:sst_token)
64
+ new_opts[:sst_token] = username[:sst_token]
65
+ elsif username.is_a? Hash
66
+ new_opts[:username] = username[:login] || username[:user] || username[:username]
67
+ new_opts[:password] = username[:password]
68
+ elsif username.nil? && password.nil? && (opts.nil? || opts.empty?)
69
+ new_opts = Helpers::AuthHelper.read_credentials
70
+ else
71
+ new_opts[:username] = username
72
+ new_opts[:password] = password
73
+ end
74
+ unless new_opts[:sst_token]
75
+ fail ArgumentError, 'No username specified' if new_opts[:username].nil?
76
+ fail ArgumentError, 'No password specified' if new_opts[:password].nil?
77
+ end
78
+
79
+ client = Client.new(new_opts)
80
+
81
+ if client
82
+ at_exit do
83
+ # puts client.connection.stats_table if client && client.connection
84
+ end
85
+ end
86
+
87
+ # HACK: This line assigns class instance # if not done yet
88
+ @@instance = client # rubocop:disable ClassVars
89
+ client
90
+ end
91
+
92
+ def disconnect
93
+ if @@instance # rubocop:disable ClassVars
94
+ @@instance.disconnect # rubocop:disable ClassVars
95
+ @@instance = nil # rubocop:disable ClassVars
96
+ end
97
+ end
98
+
99
+ def connection
100
+ @@instance # rubocop:disable ClassVars
101
+ end
102
+
103
+ alias_method :client, :connection
104
+ end
105
+
106
+ # Constructor of client
107
+ # @param opts [Hash] Client options
108
+ # @option opts [String] :username Username used for authentication
109
+ # @option opts [String] :password Password used for authentication
110
+ # @option opts :connection_factory Object able to create new instances of GoodData::Rest::Connection
111
+ # @option opts [GoodData::Rest::Connection] :connection Existing GoodData::Rest::Connection
112
+ def initialize(opts)
113
+ # TODO: Decide if we want to pass the options directly or not
114
+ @opts = opts
115
+
116
+ @connection_factory = @opts[:connection_factory] || DEFAULT_CONNECTION_IMPLEMENTATION
117
+
118
+ # TODO: See previous TODO
119
+ # Create connection
120
+ @connection = opts[:connection] || @connection_factory.new(opts)
121
+
122
+ # Connect
123
+ connect
124
+
125
+ # Create factory bound to previously created connection
126
+ @factory = ObjectFactory.new(self)
127
+ end
128
+
129
+ def domain(domain_name)
130
+ GoodData::Domain[domain_name, :client => self]
131
+ end
132
+
133
+ def projects(id = :all)
134
+ GoodData::Project[id, client: self]
135
+ end
136
+
137
+ def processes(id = :all)
138
+ GoodData::Process[id, client: self]
139
+ end
140
+
141
+ def connect
142
+ username = @opts[:username]
143
+ password = @opts[:password]
144
+
145
+ @connection.connect(username, password, @opts)
146
+ end
147
+
148
+ def disconnect
149
+ @connection.disconnect
150
+ end
151
+
152
+ #######################
153
+ # Factory stuff
154
+ ######################
155
+ def create(klass, data = {}, opts = {})
156
+ @factory.create(klass, data, opts)
157
+ end
158
+
159
+ def find(klass, opts = {})
160
+ @factory.find(klass, opts)
161
+ end
162
+
163
+ # Gets resource by name
164
+ def resource(res_name)
165
+ puts "Getting resource '#{res_name}'"
166
+ nil
167
+ end
168
+
169
+ def user
170
+ create(GoodData::Profile, @connection.user)
171
+ end
172
+
173
+ #######################
174
+ # Rest
175
+ #######################
176
+ # HTTP DELETE
177
+ #
178
+ # @param uri [String] Target URI
179
+ def delete(uri, opts = {})
180
+ @connection.delete uri, opts
181
+ end
182
+
183
+ # HTTP GET
184
+ #
185
+ # @param uri [String] Target URI
186
+ def get(uri, opts = {})
187
+ @connection.get uri, opts
188
+ end
189
+
190
+ # FIXME: Invstigate _file argument
191
+ def get_project_webdav_path(_file, opts = { :project => GoodData.project })
192
+ p = opts[:project]
193
+ fail ArgumentError, 'No :project specified' if p.nil?
194
+
195
+ project = GoodData::Project[p, opts]
196
+ fail ArgumentError, 'Wrong :project specified' if project.nil?
197
+
198
+ u = URI(project.links['uploads'])
199
+ URI.join(u.to_s.chomp(u.path.to_s), '/project-uploads/', "#{project.pid}/")
200
+ end
201
+
202
+ # FIXME: Invstigate _file argument
203
+ def get_user_webdav_path(_file, opts = { :project => GoodData.project })
204
+ p = opts[:project]
205
+ fail ArgumentError, 'No :project specified' if p.nil?
206
+
207
+ project = GoodData::Project[p, opts]
208
+ fail ArgumentError, 'Wrong :project specified' if project.nil?
209
+
210
+ u = URI(project.links['uploads'])
211
+ URI.join(u.to_s.chomp(u.path.to_s), '/uploads/')
212
+ end
213
+
214
+ # Generalizaton of poller. Since we have quite a variation of how async proceses are handled
215
+ # this is a helper that should help you with resources where the information about "Are we done"
216
+ # is the http code of response. By default we repeat as long as the code == 202. You can
217
+ # change the code if necessary. It expects the URI as an input where it can poll. It returns the
218
+ # value of last poll. In majority of cases these are the data that you need.
219
+ #
220
+ # @param link [String] Link for polling
221
+ # @param options [Hash] Options
222
+ # @return [Hash] Result of polling
223
+ def poll_on_code(link, options = {})
224
+ code = options[:code] || 202
225
+ sleep_interval = options[:sleep_interval] || DEFAULT_SLEEP_INTERVAL
226
+ response = get(link, :process => false)
227
+
228
+ while response.code == code
229
+ sleep sleep_interval
230
+ retryable(:tries => 3, :on => RestClient::InternalServerError) do
231
+ sleep sleep_interval
232
+ response = get(link, :process => false)
233
+ end
234
+ end
235
+ if options[:process] == false
236
+ response
237
+ else
238
+ get(link)
239
+ end
240
+ end
241
+
242
+ # Generalizaton of poller. Since we have quite a variation of how async proceses are handled
243
+ # this is a helper that should help you with resources where the information about "Are we done"
244
+ # is inside the response. It expects the URI as an input where it can poll and a block that should
245
+ # return either true -> 'meaning we are done' or false -> meaning sleep and repeat. It returns the
246
+ # value of last poll. In majority of cases these are the data that you need
247
+ #
248
+ # @param link [String] Link for polling
249
+ # @param options [Hash] Options
250
+ # @return [Hash] Result of polling
251
+ def poll_on_response(link, options = {}, &bl)
252
+ sleep_interval = options[:sleep_interval] || DEFAULT_SLEEP_INTERVAL
253
+ response = get(link)
254
+ while bl.call(response)
255
+ sleep sleep_interval
256
+ retryable(:tries => 3, :on => RestClient::InternalServerError) do
257
+ sleep sleep_interval
258
+ response = get(link)
259
+ end
260
+ end
261
+ response
262
+ end
263
+
264
+ # HTTP PUT
265
+ #
266
+ # @param uri [String] Target URI
267
+ def put(uri, data, opts = {})
268
+ @connection.put uri, data, opts
269
+ end
270
+
271
+ # HTTP POST
272
+ #
273
+ # @param uri [String] Target URI
274
+ def post(uri, data, opts = {})
275
+ @connection.post uri, data, opts
276
+ end
277
+
278
+ # Retry blok if exception thrown
279
+ def retryable(options = {}, &block)
280
+ opts = { :tries => 1, :on => Exception }.merge(options)
281
+
282
+ retry_exception, retries = opts[:on], opts[:tries]
283
+
284
+ begin
285
+ return yield
286
+ rescue retry_exception
287
+ retry if (retries -= 1) > 0
288
+ end
289
+
290
+ yield
291
+ end
292
+
293
+ # Uploads file
294
+ def upload(file, options = {})
295
+ @connection.upload file, options
296
+ end
297
+
298
+ def upload_to_user_webdav(file, options = {})
299
+ p = options[:project]
300
+ fail ArgumentError, 'No :project specified' if p.nil?
301
+
302
+ project = GoodData::Project[p, options]
303
+ fail ArgumentError, 'Wrong :project specified' if project.nil?
304
+
305
+ u = URI(project.links['uploads'])
306
+ url = URI.join(u.to_s.chomp(u.path.to_s), '/uploads/')
307
+ upload(file, options.merge(
308
+ :directory => options[:directory],
309
+ :staging_url => url
310
+ ))
311
+ end
312
+
313
+ def with_project(pid, &block)
314
+ GoodData.with_project(pid, client: self, &block)
315
+ end
316
+ end
317
+ end
318
+ end
@@ -0,0 +1,235 @@
1
+ # encoding: utf-8
2
+
3
+ require 'terminal-table'
4
+
5
+ require_relative '../version'
6
+ require_relative '../exceptions/exceptions'
7
+
8
+ module GoodData
9
+ module Rest
10
+ # Wrapper of low-level HTTP/REST client/library
11
+ class Connection
12
+ DEFAULT_URL = 'https://secure.gooddata.com'
13
+ LOGIN_PATH = '/gdc/account/login'
14
+ TOKEN_PATH = '/gdc/account/token'
15
+
16
+ DEFAULT_HEADERS = {
17
+ :content_type => :json,
18
+ :accept => [:json, :zip],
19
+ :user_agent => GoodData.gem_version_string
20
+ }
21
+
22
+ DEFAULT_LOGIN_PAYLOAD = {
23
+ :headers => DEFAULT_HEADERS
24
+ }
25
+
26
+ class << self
27
+ def construct_login_payload(username, password)
28
+ res = {
29
+ 'postUserLogin' => {
30
+ 'login' => username,
31
+ 'password' => password,
32
+ 'remember' => 1
33
+ }
34
+ }
35
+ res
36
+ end
37
+ end
38
+
39
+ attr_reader :cookies
40
+ attr_reader :stats
41
+ attr_reader :user
42
+
43
+ def initialize(opts)
44
+ @user = nil
45
+
46
+ @stats = {}
47
+ @opts = opts
48
+
49
+ # Initialize cookies
50
+ reset_cookies!
51
+ end
52
+
53
+ # Connect using username and password
54
+ def connect(username, password, options = {})
55
+ # Reset old cookies first
56
+ if options[:sst_token]
57
+ merge_cookies!('GDCAuthSST' => options[:sst_token])
58
+ @user = get(get('/gdc/app/account/bootstrap')['bootstrapResource']['accountSetting']['links']['self'])
59
+ @auth = {}
60
+ refresh_token :dont_reauth => true
61
+ else
62
+ credentials = Connection.construct_login_payload(username, password)
63
+ @auth = post(LOGIN_PATH, credentials, :dont_reauth => true)['userLogin']
64
+
65
+ @user = get(@auth['profile'])
66
+ refresh_token :dont_reauth => true
67
+ end
68
+ end
69
+
70
+ # Disconnect
71
+ def disconnect
72
+ # TODO: Wrap somehow
73
+ url = @auth['state']
74
+ delete url if url
75
+
76
+ @auth = nil
77
+ @server = nil
78
+ @user = nil
79
+
80
+ reset_cookies!
81
+ end
82
+
83
+ def refresh_token(options = {})
84
+ begin # rubocop:disable RedundantBegin
85
+ get TOKEN_PATH, :dont_reauth => true # avoid infinite loop GET fails with 401
86
+ rescue Exception => e # rubocop:disable RescueException
87
+ puts e.message
88
+ raise e
89
+ end
90
+ end
91
+
92
+ # Returns server URI
93
+ #
94
+ # @return [String] server uri
95
+ def server_url
96
+ @server && @server.url
97
+ end
98
+
99
+ # HTTP DELETE
100
+ #
101
+ # @param uri [String] Target URI
102
+ def delete(uri, options = {})
103
+ fail NotImplementedError "DELETE #{uri}"
104
+ end
105
+
106
+ # HTTP GET
107
+ #
108
+ # @param uri [String] Target URI
109
+ def get(uri, options = {})
110
+ fail NotImplementedError "GET #{uri}"
111
+ end
112
+
113
+ # HTTP PUT
114
+ #
115
+ # @param uri [String] Target URI
116
+ def put(uri, data, options = {})
117
+ fail NotImplementedError "PUT #{uri}"
118
+ end
119
+
120
+ # HTTP POST
121
+ #
122
+ # @param uri [String] Target URI
123
+ def post(uri, data, options = {})
124
+ fail NotImplementedError "POST #{uri}"
125
+ end
126
+
127
+ # Reader method for SST token
128
+ #
129
+ # @return uri [String] SST token
130
+ def sst_token
131
+ cookies[:cookies]['GDCAuthSST']
132
+ end
133
+
134
+ def stats_table(values = stats)
135
+ sorted = values.sort_by { |k, v| v[:avg] }
136
+ Terminal::Table.new :headings => %w(title avg min max total calls) do |t|
137
+ sorted.each do |l|
138
+ row = [
139
+ l[0],
140
+ sprintf('%.3f', l[1][:avg]),
141
+ sprintf('%.3f', l[1][:min]),
142
+ sprintf('%.3f', l[1][:max]),
143
+ sprintf('%.3f', l[1][:total]),
144
+ l[1][:calls]
145
+ ]
146
+ t.add_row row
147
+ end
148
+ end
149
+ end
150
+
151
+ # Reader method for TT token
152
+ #
153
+ # @return uri [String] TT token
154
+ def tt_token
155
+ cookies[:cookies]['GDCAuthTT']
156
+ end
157
+
158
+ private
159
+
160
+ def merge_cookies!(cookies)
161
+ @cookies[:cookies].merge! cookies
162
+ end
163
+
164
+ def profile(title, &block)
165
+ t1 = Time.now
166
+ res = block.call
167
+ t2 = Time.now
168
+ delta = t2 - t1
169
+
170
+ update_stats title, delta
171
+ res
172
+ end
173
+
174
+ def reset_cookies!
175
+ @cookies = { :cookies => {} }
176
+ end
177
+
178
+ # TODO: Store PH_MAP for wildcarding of URLs in reports in separate file
179
+ PH_MAP = [
180
+ ['/gdc/projects/{id}/users/{id}/permissions', %r{/gdc/projects/[\w]+/users/[\w]+/permissions}],
181
+ ['/gdc/projects/{id}/roles/{id}', %r{/gdc/projects/[\w]+/roles/[\d]+}],
182
+ ['/gdc/projects/{id}/model/diff/{id}', %r{/gdc/projects/[\w]+/model/diff/[\w]+}],
183
+ ['/gdc/projects/{id}/', %r{/gdc/projects/[\w]+/}],
184
+ ['/gdc/projects/{id}', %r{/gdc/projects/[\w]+}],
185
+ ['/gdc/md/{id}/using2/{id}/{id}', %r{/gdc/md/[\w]+/using2/[\d]+/[\d]+}],
186
+ ['/gdc/md/{id}/usedby2/{id}/{id}', %r{/gdc/md/[\w]+/usedby2/[\d]+/[\d]+}],
187
+ ['/gdc/md/{id}/tasks/{id}/status', %r{/gdc/md/[\w]+/tasks/[\w]+/status}],
188
+ ['/gdc/md/{id}/obj/{id}/validElements', %r{/gdc/md/[\w]+/obj/[\d]+/validElements(/)?(\?.*)?}],
189
+ ['/gdc/md/{id}/obj/{id}/elements', %r{/gdc/md/[\w]+/obj/[\d]+/elements(/)?(\?.*)?}],
190
+ ['/gdc/md/{id}/obj/{id}', %r{/gdc/md/[\w]+/obj/[\d]+}],
191
+ ['/gdc/md/{id}/etl/task/{id}', %r{/gdc/md/[\w]+/etl/task/[\d]+}],
192
+ ['/gdc/md/{id}/dataResult/{id}', %r{/gdc/md/[\w]+/dataResult/[\d]+}],
193
+ ['/gdc/md/{id}', %r{/gdc/md/[\w]+}],
194
+ ['/gdc/app/projects/{id}/execute', %r{/gdc/app/projects/[\w]+/execute}],
195
+ ['/gdc/account/profile/{id}', %r{/gdc/account/profile/[\w]+}],
196
+ ['/gdc/account/login/{id}', %r{/gdc/account/login/[\w]+}],
197
+ ['/gdc/account/domains/{id}', %r{/gdc/account/domains/[\w\d-]+}]
198
+ ]
199
+
200
+ def update_stats(title, delta)
201
+ orig_title = title
202
+
203
+ placeholders = true
204
+
205
+ if placeholders
206
+ PH_MAP.each do |pm|
207
+ break if title.gsub!(pm[1], pm[0])
208
+ end
209
+ end
210
+
211
+ stat = stats[title]
212
+ if stat.nil?
213
+ stat = {
214
+ :min => delta,
215
+ :max => delta,
216
+ :total => 0,
217
+ :avg => 0,
218
+ :calls => 0,
219
+ :entries => []
220
+ }
221
+ end
222
+
223
+ stat[:min] = delta if delta < stat[:min]
224
+ stat[:max] = delta if delta > stat[:max]
225
+ stat[:total] += delta
226
+ stat[:calls] += 1
227
+ stat[:avg] = stat[:total] / stat[:calls]
228
+
229
+ stat[:entries] << orig_title if placeholders
230
+
231
+ stats[title] = stat
232
+ end
233
+ end
234
+ end
235
+ end