zuora_connect 1.5.317 → 1.6.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7a4d713eaabaed74c83de4682191b8fbc2cdc132
4
- data.tar.gz: 943062d9fb7e9a2040e1e4c0b34a8d7f3883b07d
3
+ metadata.gz: 0f4381548b341f67bad84f5e83b16c3185113260
4
+ data.tar.gz: 1f8fed40fdbc6291cc89fe1bbec7f55270d4fc78
5
5
  SHA512:
6
- metadata.gz: b64588369fe4c866cc8721923249a0876bee0f235ef002a7022a117d5e68c732dbb5b4e298af7a98279332c71c6853253e45b1fb69313beeb6de257c0b22190d
7
- data.tar.gz: 8584068ff654fa0e5807c7be6a2f5874f9445192197f05b6d5df9d83f89d11af20ace96af1f0514917aae9d5a62242033b0eed44be98ef1bbbb295ccb17dd424
6
+ metadata.gz: 0e2b91b6d3f1058f99904657bf3ba433463c03aaf02a8f4064bdfd5c719c49dee2e37c74bf2c6bea000048b8f2b0a4483bf9e78fddd127523c31457d7bd90ca3
7
+ data.tar.gz: 36dc42c3924cb40a6c1d958d821665f1b76c2eab4ba4836150b18481008085c0e68b4dee5b94d2164b0a4fc223931f58f2e86e672f5f040d394aa941965e03fe
@@ -1,7 +1,8 @@
1
1
  module ZuoraConnect
2
2
  class StaticController < ApplicationController
3
- before_filter :authenticate_connect_app_request, :except => [:health, :session_error, :invalid_app_instance_error]
4
- after_filter :persist_connect_app_session, :except => [:health, :session_error, :invalid_app_instance_error]
3
+ before_filter :authenticate_connect_app_request, :except => [:metrics, :health, :session_error, :invalid_app_instance_error]
4
+ after_filter :persist_connect_app_session, :except => [:metrics, :health, :session_error, :invalid_app_instance_error]
5
+
5
6
  def session_error
6
7
  respond_to do |format|
7
8
  format.html
@@ -16,6 +17,11 @@ module ZuoraConnect
16
17
  end
17
18
  end
18
19
 
20
+ def metrics
21
+ type = params[:type].present? ? params[:type] : "versions"
22
+ render json: ZuoraConnect::AppInstanceBase.get_metrics(type), status: 200
23
+ end
24
+
19
25
  def health
20
26
  render json: {
21
27
  message: "Alive",
@@ -1,10 +1,11 @@
1
1
  module ZuoraConnect
2
+ require "uri"
2
3
  class AppInstanceBase < ActiveRecord::Base
3
4
  default_scope {select(ZuoraConnect::AppInstance.column_names.delete_if {|x| ["catalog_mapping", "catalog"].include?(x) }) }
4
5
  after_initialize :init
5
6
  self.table_name = "zuora_connect_app_instances"
6
7
  attr_accessor :options, :mode, :logins, :task_data, :last_refresh, :username, :password, :s3_client, :api_version
7
-
8
+
8
9
  REFRESH_TIMEOUT = 2.minute #Used to determine how long to wait on current refresh call before executing another
9
10
  INSTANCE_REFRESH_WINDOW = 30.minutes #Used to set how how long till app starts attempting to refresh cached task connect data
10
11
  INSTANCE_REDIS_CACHE_PERIOD = 60.minutes #Used to determine how long to cached task data will live for
@@ -24,8 +25,111 @@ module ZuoraConnect
24
25
  self.apartment_switch(nil, true)
25
26
  end
26
27
 
28
+
29
+ # Methods for writing Telegraf metrics
30
+
31
+ # Returns the process type if any
32
+ def self.get_process_type
33
+ p_type = nil
34
+ if ENV['HOSTNAME'] && ENV['DEIS_APP']
35
+ temp = ENV['HOSTNAME'].split(ENV['DEIS_APP'])[1]
36
+ temp = temp.split(/(-[0-9a-zA-Z]{5})$/)[0] # remove the 5 char hash
37
+ p_type = temp[1, temp.rindex("-")-1]
38
+ end
39
+ return p_type
40
+ end
41
+
42
+ # Write to telegraf
43
+ def self.write_to_telegraf(endpoint_name: nil, method_name: nil, status_code: nil, response_time: nil, db_runtime: nil, view_runtime: nil, content_type: nil, direction: nil, error_type: nil, app_instance: nil, function_name: nil)
44
+
45
+ # To avoid writing metrics from rspec tests
46
+ if ENV['DEIS_APP']
47
+ # Getting the process type
48
+ p_type = ZuoraConnect::AppInstanceBase.get_process_type
49
+
50
+ if direction == "inbound" && ZuoraConnect::Configuration.new.enable_inbound_metrics_flag
51
+ Thread.current[:appinstance].present? ? app_instance = Thread.current[:appinstance].id : app_instance = 0
52
+
53
+ # Separately handling 200 and non 200 as influx does not accept nil as a value
54
+ if db_runtime && view_runtime
55
+ # 200 requests
56
+ begin
57
+ ZuoraConnect.configuration.telegraf_client.write(ZuoraConnect.configuration.influxdb_series_name_inbound,
58
+ tags: {"app_name": "#{ZuoraConnect.configuration.app_name}", "controller_action": endpoint_name, "content-type": content_type, method: method_name, status: status_code, process_type: p_type, "app_instance": app_instance},
59
+ values: {response_time: response_time, db_time: db_runtime, view_time: view_runtime})
60
+ rescue => e
61
+ raise e
62
+ end
63
+ else
64
+ # non 200 requests
65
+ begin
66
+ ZuoraConnect.configuration.telegraf_client.write(ZuoraConnect.configuration.influxdb_series_name_inbound,
67
+ tags: {"app_name": "#{ZuoraConnect.configuration.app_name}", "controller_action": endpoint_name, "content-type": content_type, method: method_name, status: status_code, process_type: p_type, "app_instance": app_instance},
68
+ values: {response_time: response_time})
69
+ rescue=> e
70
+ raise e
71
+ end
72
+ end
73
+
74
+ elsif direction == "outbound" && ZuoraConnect::Configuration.new.enable_outbound_metrics_flag
75
+ # if there is an error
76
+ if error_type
77
+ begin
78
+ ZuoraConnect.configuration.telegraf_client.write(ZuoraConnect.configuration.influxdb_series_name_outbound,
79
+ tags: {"app_name": "#{ZuoraConnect.configuration.app_name}", endpoint: endpoint_name, status: status_code, process_type: p_type, "app_instance": app_instance, "function_name": function_name, method: method_name, "error_type": error_type},
80
+ values: {response_time: response_time})
81
+ rescue => e
82
+ raise e
83
+ end
84
+ else
85
+ begin
86
+ ZuoraConnect.configuration.telegraf_client.write(ZuoraConnect.configuration.influxdb_series_name_outbound,
87
+ tags: {"app_name": "#{ZuoraConnect.configuration.app_name}", endpoint: endpoint_name, status: status_code, process_type: p_type, "app_instance": app_instance, "function_name": function_name, method: method_name},
88
+ values: {response_time: response_time})
89
+ rescue => e
90
+ raise e
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ def self.get_metrics(type)
98
+ namespace = ENV['DEIS_APP'].present? ? "#{ENV['DEIS_APP']}" : "#{Rails.application.class.parent_name}"
99
+
100
+ data = {}
101
+
102
+ if type == "versions"
103
+ data = {
104
+ app_name: namespace,
105
+ Version_Gem: ZuoraConnect::VERSION,
106
+ Version_Zuora: ZuoraAPI::VERSION ,
107
+ Version_Ruby: RUBY_VERSION,
108
+ Version_Rails: Rails.version,
109
+ hold: 1
110
+ }
111
+ elsif type == "stats"
112
+ begin
113
+ Resque.redis.ping
114
+ data = {
115
+ app_name: namespace,
116
+ Resque:{
117
+ Jobs_Finished: Resque.info[:processed] ,
118
+ Jobs_Failed: Resque.info[:failed],
119
+ Jobs_Pending: Resque.info[:pending],
120
+ Workers_Active: Resque.info[:working],
121
+ Workers_Total: Resque.info[:workers]
122
+ }
123
+ }
124
+ rescue
125
+ end
126
+ end
127
+ return data.to_json
128
+ end
129
+
130
+
27
131
  def apartment_switch(method = nil, migrate = false)
28
- begin
132
+ begin
29
133
  Apartment::Tenant.switch!(self.id) if self.persisted?
30
134
  rescue Apartment::TenantNotFound => ex
31
135
  Apartment::Tenant.create(self.id.to_s)
@@ -70,7 +174,7 @@ module ZuoraConnect
70
174
  raise ZuoraConnect::Exceptions::HoldingPattern if holding_pattern && !self.mark_for_refresh
71
175
  self.refresh(session)
72
176
 
73
- elsif (self.id != session["appInstance"].to_i)
177
+ elsif (self.id != session["appInstance"].to_i)
74
178
  Rails.logger.info("[#{self.id}] REFRESHING - AppInstance ID(#{self.id}) does not match session id(#{session["appInstance"].to_i})")
75
179
  raise ZuoraConnect::Exceptions::HoldingPattern if holding_pattern && !self.mark_for_refresh
76
180
  self.refresh(session)
@@ -81,11 +185,11 @@ module ZuoraConnect
81
185
  self.refresh(session)
82
186
 
83
187
  elsif session["#{self.id}::last_refresh"].blank?
84
- Rails.logger.info("[#{self.id}] REFRESHING - No Time on Cookie")
188
+ Rails.logger.info("[#{self.id}] REFRESHING - No Time on Cookie")
85
189
  raise ZuoraConnect::Exceptions::HoldingPattern if holding_pattern && !self.mark_for_refresh
86
190
  self.refresh(session)
87
191
 
88
- # If the cache is expired and we can aquire a refresh lock
192
+ # If the cache is expired and we can aquire a refresh lock
89
193
  elsif (session["#{self.id}::last_refresh"].to_i < INSTANCE_REFRESH_WINDOW.ago.to_i) && self.mark_for_refresh
90
194
  Rails.logger.info("[#{self.id}] REFRESHING - Session Old by #{time_expire.abs} second")
91
195
  self.refresh(session)
@@ -101,7 +205,7 @@ module ZuoraConnect
101
205
  begin
102
206
  I18n.locale = self.locale
103
207
  rescue I18n::InvalidLocale => ex
104
- Rails.logger.error("Invalid Locale: #{ex.message}")
208
+ Rails.logger.debug("Invalid Locale: #{ex.message}")
105
209
  end
106
210
  Time.zone = self.timezone
107
211
  return self
@@ -117,12 +221,12 @@ module ZuoraConnect
117
221
 
118
222
  def refresh(session = nil)
119
223
  refresh_count ||= 0
120
-
224
+ error_type = ""
121
225
  start = Time.now
122
226
  response = HTTParty.get(ZuoraConnect.configuration.url + "/api/#{self.api_version}/tools/tasks/#{self.id}.json",:body => {:access_token => self.access_token})
123
227
  response_time = Time.now - start
124
228
 
125
- Rails.logger.info("[#{self.id}] REFRESH TASK - Connect Task Info Request Time #{response_time.round(2).to_s}")
229
+ Rails.logger.debug("[#{self.id}] REFRESH TASK - Connect Task Info Request Time #{response_time.round(2).to_s}")
126
230
  if response.code == 200
127
231
  build_task(JSON.parse(response.body), session)
128
232
  @last_refresh = Time.now.to_i
@@ -137,6 +241,7 @@ module ZuoraConnect
137
241
  Rails.logger.info("[#{self.id}] REFRESH TASK - #{ex.class} Retrying(#{refresh_count})")
138
242
  retry
139
243
  else
244
+ error_type = "#{ex.class}"
140
245
  Rails.logger.fatal("[#{self.id}] REFRESH TASK - #{ex.class} Failed #{refresh_count}x")
141
246
  raise
142
247
  end
@@ -148,60 +253,96 @@ module ZuoraConnect
148
253
  end
149
254
  retry
150
255
  else
256
+ error_type = "#{ex.class}"
151
257
  Rails.logger.fatal("[#{self.id}] REFRESH TASK - Failed #{refresh_count}x")
152
258
  raise
153
259
  end
260
+ ensure
261
+ # Writing to telegraf
262
+ status_code = response.code if response
263
+ endpoint_name = URI(ZuoraConnect.configuration.url).host
264
+ Thread.current[:appinstance].present? ? app_instance = Thread.current[:appinstance].id : app_instance = 0
265
+ ZuoraConnect::AppInstanceBase.write_to_telegraf("response_time": response_time, "status_code": status_code, "endpoint_name": endpoint_name, "direction": "outbound", "error_type": error_type, "function_name": "#{self.class}##{__method__}", "method_name": "GET", "app_instance": app_instance)
154
266
  end
155
267
 
156
268
  #### START Task Mathods ####
157
269
  def build_task(task_data, session)
158
- @task_data = task_data
159
- @mode = @task_data["mode"]
160
- @task_data.each do |k,v|
161
- if k.match(/^(.*)_login$/)
162
- tmp = ZuoraConnect::Login.new(v)
163
- if !session.nil? && v["tenant_type"] == "Zuora"
164
- if tmp.entities.size > 0
165
- tmp.entities.each do |value|
166
- entity_id = value["id"]
167
- tmp.client(entity_id).current_session = session["#{self.id}::#{k}::#{entity_id}:session"] if !session.nil? && v["tenant_type"] == "Zuora" && session["#{self.id}::#{k}::#{entity_id}:session"]
270
+ begin
271
+ @task_data = task_data
272
+ @mode = @task_data["mode"]
273
+ @task_data.each do |k,v|
274
+ if k.match(/^(.*)_login$/)
275
+ tmp = ZuoraConnect::Login.new(v)
276
+ if !session.nil? && v["tenant_type"] == "Zuora"
277
+ if tmp.entities.size > 0
278
+ tmp.entities.each do |value|
279
+ entity_id = value["id"]
280
+ tmp.client(entity_id).current_session = session["#{self.id}::#{k}::#{entity_id}:session"] if !session.nil? && v["tenant_type"] == "Zuora" && session["#{self.id}::#{k}::#{entity_id}:session"]
281
+ end
282
+ else
283
+ tmp.client.current_session = session["#{self.id}::#{k}:session"] if !session.nil? && v["tenant_type"] == "Zuora" && session["#{self.id}::#{k}:session"]
168
284
  end
169
- else
170
- tmp.client.current_session = session["#{self.id}::#{k}:session"] if !session.nil? && v["tenant_type"] == "Zuora" && session["#{self.id}::#{k}:session"]
171
285
  end
286
+ @logins[k] = tmp
287
+ self.attr_builder(k, @logins[k])
288
+ elsif k == "options"
289
+ v.each do |opt|
290
+ @options[opt["config_name"]] = opt
291
+ end
292
+ elsif k == "user_settings"
293
+ self.timezone = v["timezone"]
294
+ self.locale = v["local"]
172
295
  end
173
- @logins[k] = tmp
174
- self.attr_builder(k, @logins[k])
175
- elsif k == "options"
176
- v.each do |opt|
177
- @options[opt["config_name"]] = opt
178
- end
179
- elsif k == "user_settings"
180
- self.timezone = v["timezone"]
181
- self.locale = v["local"]
182
296
  end
297
+ rescue => ex
298
+ Rails.logger.error("Task Data: #{task_data}")
299
+ Rails.logger.error("Task Session: #{session.to_hash}")
300
+ raise
183
301
  end
184
302
  end
185
303
 
186
304
  def updateOption(optionId, value)
187
- return HTTParty.get(ZuoraConnect.configuration.url + "/api/#{self.api_version}/tools/application_options/#{optionId}/edit?value=#{value}",:body => {:access_token => self.username})
305
+ begin
306
+ start_time = Time.now
307
+ response = HTTParty.get(ZuoraConnect.configuration.url + "/api/#{self.api_version}/tools/application_options/#{optionId}/edit?value=#{value}",:body => {:access_token => self.username})
308
+ rescue => e
309
+ error_type = "#{e.class}"
310
+ ensure
311
+ end_time = Time.now
312
+ response_time = end_time - start_time
313
+ status_code = response.code if response
314
+ endpoint_name = URI(ZuoraConnect.configuration.url).host
315
+ Thread.current[:appinstance].present? ? app_instance = Thread.current[:appinstance].id : app_instance = 0
316
+ ZuoraConnect::AppInstanceBase.write_to_telegraf("response_time": response_time, "status_code": status_code, "endpoint_name": endpoint_name, "direction": "outbound", "error_type": error_type, "function_name": "#{self.class}##{__method__}", "method_name": "GET", "app_instance": app_instance)
317
+ return response
318
+ end
188
319
  end
189
320
 
190
321
  #This can update an existing login, add a new login, change to another existing login
191
322
  #EXAMPLE: {"name": "ftp_login_14","username": "ftplogin7","tenant_type": "Custom","password": "test2","url": "www.ftp.com","custom_data": { "path": "/var/usr/test"}}
192
323
  def update_logins(options)
193
324
  update_login_count ||= 0
194
-
325
+ start_time = Time.now
326
+ error_type = ""
195
327
  response = HTTParty.post(ZuoraConnect.configuration.url + "/api/#{self.api_version}/tools/tasks/#{self.id}/logins",:body => {:access_token => self.username}.merge(options))
328
+ parsed_json = JSON.parse(response.body)
196
329
  if response.code == 200
197
- return JSON.parse(response.body)
330
+ if defined?(Redis.current)
331
+ self.build_task(parsed_json, self.data_lookup)
332
+ self.last_refresh = Time.now.to_i
333
+ self.cache_app_instance
334
+ end
335
+ return parsed_json
336
+ elsif response.code == 400
337
+ raise ZuoraConnect::Exceptions::APIError.new(message: parsed_json['errors'].join(' '), response: response.body, code: response.code)
198
338
  else
199
339
  raise ZuoraConnect::Exceptions::ConnectCommunicationError.new("Error Communicating with Connect", response.body, response.code)
200
340
  end
201
- rescue Net::ReadTimeout, Net::OpenTimeout, Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNREFUSED, SocketError
341
+ rescue Net::ReadTimeout, Net::OpenTimeout, Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNREFUSED, SocketError => ex
202
342
  if (update_login_count += 1) < 3
203
343
  retry
204
344
  else
345
+ error_type = "#{ex.class}"
205
346
  raise
206
347
  end
207
348
  rescue ZuoraConnect::Exceptions::ConnectCommunicationError => ex
@@ -211,8 +352,16 @@ module ZuoraConnect
211
352
  end
212
353
  retry
213
354
  else
355
+ error_type = "#{ex.class}"
214
356
  raise
215
357
  end
358
+ ensure
359
+ end_time = Time.now
360
+ response_time = end_time - start_time
361
+ status_code = response.code if response
362
+ endpoint_name = URI(ZuoraConnect.configuration.url).host
363
+ Thread.current[:appinstance].present? ? app_instance = Thread.current[:appinstance].id : app_instance = 0
364
+ ZuoraConnect::AppInstanceBase.write_to_telegraf("response_time": response_time, "status_code": status_code, "endpoint_name": endpoint_name, "direction": "outbound", "error_type": error_type, "function_name": "#{self.class}##{__method__}", "method_name": "POST", "app_instance": app_instance)
216
365
  end
217
366
  #### END Task Mathods ####
218
367
 
@@ -226,12 +375,12 @@ module ZuoraConnect
226
375
  end
227
376
 
228
377
  def oauth_expired?
229
- (self.oauth_expires_at < Time.now)
378
+ return self.oauth_expires_at.present? ? (self.oauth_expires_at < Time.now) : true
230
379
  end
231
380
 
232
381
  def refresh_oauth
233
382
  refresh_oauth_count ||= 0
234
-
383
+ error_type = ""
235
384
  start = Time.now
236
385
  params = {
237
386
  :grant_type => "refresh_token",
@@ -258,13 +407,14 @@ module ZuoraConnect
258
407
  Rails.logger.info("[#{self.id}] REFRESH OAUTH - #{ex.class} Retrying(#{refresh_oauth_count})")
259
408
  retry
260
409
  else
410
+ error_type = "#{ex.class}"
261
411
  Rails.logger.fatal("[#{self.id}] REFRESH OAUTH - #{ex.class} Failed #{refresh_oauth_count}x")
262
412
  raise
263
413
  end
264
414
  rescue ZuoraConnect::Exceptions::ConnectCommunicationError => ex
265
415
  sleep(5)
266
416
  self.reload_attributes([:refresh_token, :oauth_expires_at, :access_token]) #Reload only the refresh token for retry
267
-
417
+
268
418
  #After reload, if nolonger expired return
269
419
  return if !self.oauth_expired?
270
420
 
@@ -272,9 +422,15 @@ module ZuoraConnect
272
422
  Rails.logger.info("[#{self.id}] REFRESH OAUTH - Failed Retrying(#{refresh_oauth_count})")
273
423
  retry
274
424
  else
425
+ error_type = "#{ex.class}"
275
426
  Rails.logger.fatal("[#{self.id}] REFRESH OAUTH - Failed #{refresh_oauth_count}x")
276
427
  raise
277
428
  end
429
+ ensure
430
+ status_code = response.code if response
431
+ endpoint_name = URI(ZuoraConnect.configuration.url).host
432
+ Thread.current[:appinstance].present? ? app_instance = Thread.current[:appinstance].id : app_instance = 0
433
+ ZuoraConnect::AppInstanceBase.write_to_telegraf("response_time": response_time, "status_code": status_code, "endpoint_name": endpoint_name, "direction": "outbound", "error_type": error_type, "function_name": "#{self.class}##{__method__}", "method_name": "POST", "app_instance": app_instance)
278
434
  end
279
435
  #### END Connect OAUTH methods ####
280
436
 
@@ -297,7 +453,7 @@ module ZuoraConnect
297
453
 
298
454
  def mark_for_refresh
299
455
  return defined?(Redis.current) ? Redis.current.set("AppInstance:#{self.id}:Refreshing", true, {:nx => true, :ex => REFRESH_TIMEOUT.to_i}) : true
300
- end
456
+ end
301
457
 
302
458
  def data_lookup(session: {})
303
459
  if defined?(PaperTrail)
@@ -340,7 +496,17 @@ module ZuoraConnect
340
496
  end
341
497
  end
342
498
  end
499
+
343
500
  session["#{self.id}::task_data"] = self.task_data
501
+
502
+ #Redis is not defined strip out old data
503
+ if !defined?(Redis.current)
504
+ session["#{self.id}::task_data"].delete('applications')
505
+ session["#{self.id}::task_data"].select {|k,v| k.include?('login') && v['tenant_type'] == 'Zuora'}.each do |login_key, login_data|
506
+ session["#{self.id}::task_data"][login_key]['entities'] = (login_data.dig('entities') || []).map {|entity| entity.slice('id', 'tenantId')}
507
+ end
508
+ end
509
+
344
510
  session["#{self.id}::last_refresh"] = self.last_refresh
345
511
  session["appInstance"] = self.id
346
512
  return session
@@ -420,8 +586,8 @@ module ZuoraConnect
420
586
  self.update_column(:catalog_update_attempt_at, Time.now.utc)
421
587
 
422
588
  entity_reference = entity_id.blank? ? 'Default' : entity_id
423
- Rails.logger.info("Fetch Catalog")
424
- Rails.logger.info("Zuora Entity: #{entity_id.blank? ? 'default' : entity_id}")
589
+ Rails.logger.debug("Fetch Catalog")
590
+ Rails.logger.debug("Zuora Entity: #{entity_id.blank? ? 'default' : entity_id}")
425
591
 
426
592
  login = zuora_login.client(entity_reference)
427
593
 
@@ -654,7 +820,7 @@ module ZuoraConnect
654
820
  ### END S3 Helping Methods #####
655
821
 
656
822
  ### START Aggregate Grouping Helping Methods ####
657
- def self.refresh_aggregate_table(aggregate_name: 'all_tasks_processing', table_name: 'tasks', where_clause: "where status in ('Processing', 'Queued')", index_table: true)
823
+ def self.refresh_aggregate_table(aggregate_name: 'all_tasks_processing', table_name: 'tasks', where_clause: "where status in ('Processing', 'Queued')", index_table: true)
658
824
  self.update_functions
659
825
  #Broke function into two parts to ensure transaction size was small enough
660
826
  ActiveRecord::Base.connection.execute('SELECT "shared_extensions".refresh_aggregate_table(\'%s\', \'%s\', %s, \'Table\');' % [aggregate_name, table_name, ActiveRecord::Base.connection.quote(where_clause)])
@@ -666,13 +832,13 @@ module ZuoraConnect
666
832
  end
667
833
  ### END Aggregate Grouping Helping Methods #####
668
834
 
669
- # Overide this method to avoid the new session call for api requests that use the before filter authenticate_app_api_request.
835
+ # Overide this method to avoid the new session call for api requests that use the before filter authenticate_app_api_request.
670
836
  # This can be usefull for apps that dont need connect metadata call, or credentials, to operate for api requests
671
837
  def new_session_for_api_requests(params: {})
672
838
  return true
673
839
  end
674
840
 
675
- # Overide this method to avoid the new session call for ui requests that use the before filter authenticate_connect_app_request.
841
+ # Overide this method to avoid the new session call for ui requests that use the before filter authenticate_connect_app_request.
676
842
  # This can be usefull for apps that dont need connect metadata call, or credentials, to operate for ui requests
677
843
  def new_session_for_ui_requests(params: {})
678
844
  return true
@@ -0,0 +1,41 @@
1
+ if defined? Prometheus
2
+ module Prometheus
3
+ require "zuora_connect/version"
4
+ require "zuora_api/version"
5
+
6
+ app_name = ENV['DEIS_APP'].present? ? "#{ENV['DEIS_APP']}" : "#{Rails.application.class.parent_name}"
7
+ # Create a default Prometheus registry for our metrics.
8
+ prometheus = Prometheus::Client.registry
9
+
10
+ # Create your metrics.
11
+ ZUORA_VERSION = Prometheus::Client::Gauge.new(:zuora_version, 'The current Zuora Gem version.')
12
+ CONNECT_VERSION = Prometheus::Client::Gauge.new(:gem_version, 'The current Connect Gem version.')
13
+ RAILS_VERSION = Prometheus::Client::Gauge.new(:rails_version, 'The current Rails version.')
14
+ RUBY_V = Prometheus::Client::Gauge.new(:ruby_version, 'The current Ruby version.')
15
+
16
+ # Register your metrics with the registry we previously created.
17
+ prometheus.register(ZUORA_VERSION);ZUORA_VERSION.set({version:ZuoraAPI::VERSION,name:app_name},0)
18
+ prometheus.register(CONNECT_VERSION);CONNECT_VERSION.set({version:ZuoraConnect::VERSION,name:app_name},0)
19
+ prometheus.register(RAILS_VERSION);RAILS_VERSION.set({version:Rails.version,name:app_name},0)
20
+ prometheus.register(RUBY_V);RUBY_V.set({version:RUBY_VERSION,name:app_name},0)
21
+
22
+ # Do they have resque jobs?
23
+ if defined? Resque.redis
24
+ REDIS_CONNECTION = Prometheus::Client::Gauge.new(:redis_connection, 'The status of the redis connection, 0 or 1')
25
+ FINISHED_JOBS = Prometheus::Client::Gauge.new(:finished_jobs, 'Done resque jobs')
26
+ WORKERS = Prometheus::Client::Gauge.new(:workers, 'Total resque workers')
27
+ ACTIVE_WORKERS = Prometheus::Client::Gauge.new(:active_workers, 'Active resque workers')
28
+ FAILED_JOBS = Prometheus::Client::Gauge.new(:failed_jobs, 'Failed resque jobs')
29
+ PENDING_JOBS = Prometheus::Client::Gauge.new(:pending_jobs, 'Pending resque jobs')
30
+
31
+ prometheus.register(REDIS_CONNECTION)
32
+ prometheus.register(FINISHED_JOBS)
33
+ prometheus.register(ACTIVE_WORKERS)
34
+ prometheus.register(WORKERS)
35
+ prometheus.register(FAILED_JOBS)
36
+ prometheus.register(PENDING_JOBS)
37
+
38
+ end
39
+
40
+ end
41
+ end
@@ -1,5 +1,6 @@
1
1
  ZuoraConnect::Engine.routes.draw do
2
2
  get '/health' => 'static#health'
3
+ get '/internal/data' => 'static#metrics'
3
4
  get '/invalid_session' => 'static#session_error', :as => :invalid_session
4
5
  get '/invalid_instance' => "static#invalid_app_instance_error", :as => :invalid_instance
5
6
  namespace :api do
@@ -0,0 +1,111 @@
1
+ module Middleware
2
+ require 'uri'
3
+
4
+ # Object of this class is passed to the ActiveSupport::Notification hook
5
+ class PageRequest
6
+
7
+ # This method is triggered when a non error page is loaded (not 404)
8
+ def call(name, started, finished, unique_id, payload)
9
+
10
+ # If the url contains any css or JavaScript files then do not collect metrics for them
11
+ block_words = ["css", "assets", "jpg", "png", "jpeg", "ico"]
12
+ if block_words.any? { |word| payload[:path].include?(word) }
13
+ return nil
14
+ end
15
+
16
+ # Getting the endpoint and the content_type
17
+ content_hash = {:html => "text/html", :js => "application/javascript", :json => "application/json"}
18
+ content_hash.key?(payload[:format]) ? content_type = content_hash[payload[:format]] : content_type = payload[:format]
19
+ request_path = "#{payload[:controller]}##{payload[:action]}"
20
+ response_time = finished-started
21
+
22
+ # payloads with 500 requests do not have status as it is not set by the controller
23
+ # https://github.com/rails/rails/issues/33335
24
+ status_code = payload[:status] ? payload[:status] : payload[:exception_object].present? ? 500 : ""
25
+
26
+ # Write to telegraf
27
+ ZuoraConnect::AppInstanceBase.write_to_telegraf("endpoint_name": request_path, "method_name": payload[:method], "status_code": status_code, "response_time": response_time, "db_runtime": payload[:db_runtime].to_f, "view_runtime": payload[:view_runtime], "content_type": content_type, "direction": "inbound")
28
+
29
+ end
30
+ end
31
+
32
+
33
+ class MetricsMiddleware
34
+
35
+ require "zuora_connect/version"
36
+ require "zuora_api/version"
37
+ require "telegraf"
38
+
39
+ def initialize(app)
40
+ @app = app
41
+ end
42
+
43
+ def call(env)
44
+ start_time = Time.now
45
+ @status, @headers, @response = @app.call(env)
46
+ end_time = Time.now
47
+
48
+ # If the url contains any CSS or JavaScript files then do not collect metrics for them
49
+ block_words = ["css", "assets", "jpg", "png", "jpeg", "ico"]
50
+ if block_words.any? { |word| env['PATH_INFO'].include?(word) }
51
+ return [@status, @headers, @response]
52
+ end
53
+
54
+
55
+ response_time = end_time - start_time
56
+
57
+ if defined? Prometheus
58
+ #Prometheus Stuff
59
+ if env['PATH_INFO'] == '/connect/internal/metrics'
60
+
61
+ #Do something before each scrape
62
+ if defined? Resque.redis
63
+
64
+ app_name = ENV['DEIS_APP'].present? ? "#{ENV['DEIS_APP']}" : "#{Rails.application.class.parent_name}"
65
+ begin
66
+
67
+ Resque.redis.ping
68
+
69
+ Prometheus::REDIS_CONNECTION.set({connection:'redis',name:app_name},1)
70
+ Prometheus::FINISHED_JOBS.set({type:'resque',name:app_name},Resque.info[:processed])
71
+ Prometheus::PENDING_JOBS.set({type:'resque',name:app_name},Resque.info[:pending])
72
+ Prometheus::ACTIVE_WORKERS.set({type:'resque',name:app_name},Resque.info[:working])
73
+ Prometheus::WORKERS.set({type:'resque',name:app_name},Resque.info[:workers])
74
+ Prometheus::FAILED_JOBS.set({type:'resque',name:app_name},Resque.info[:failed])
75
+
76
+ rescue Redis::CannotConnectError
77
+ Prometheus::REDIS_CONNECTION.set({connection:'redis',name:app_name},0)
78
+ end
79
+
80
+ if ZuoraConnect.configuration.custom_prometheus_update_block != nil
81
+ ZuoraConnect.configuration.custom_prometheus_update_block.call()
82
+ end
83
+ end
84
+
85
+ end
86
+ end
87
+ # Writing to telegraf: Handle 404 and 500 requests
88
+ if @status == 404 || @status == 304
89
+ # Getting the endpoint and content_type
90
+ request_path = @status == 404 ? "ActionController#RoutingError" : env["action_controller.instance"].present? ? "#{env["action_controller.instance"].class}##{env["action_controller.instance"].action_name}" : ""
91
+
92
+ # Uncomment following block of code for handling engine requests/requests without controller
93
+ # else
94
+ # # Handling requests which do not have controllers (engines)
95
+ # if env["SCRIPT_NAME"].present?
96
+ # controller_path = "#{env['SCRIPT_NAME'][1..-1]}"
97
+ # controller_path = controller_path.sub("/", "::")
98
+ # request_path = "#{controller_path}#UnknownAction"
99
+ # end
100
+
101
+ content_type = @headers['Content-Type'].split(';')[0] if @headers['Content-Type']
102
+ ZuoraConnect::AppInstanceBase.write_to_telegraf("endpoint_name": request_path, "method_name": env['REQUEST_METHOD'], "status_code": @status, "response_time": response_time, "content_type": content_type, "direction": "inbound") if request_path.present?
103
+ end
104
+
105
+ [@status, @headers, @response]
106
+
107
+ end
108
+
109
+ end
110
+
111
+ end
@@ -38,18 +38,29 @@ module Resque
38
38
  end
39
39
 
40
40
  def reserve_with_round_robin
41
- qs = rotated_queues
42
- qs.each do |queue|
43
- log! "Checking #{queue}"
44
- if should_work_on_queue?(queue) && @job_in_progress = Resque::Job.reserve(queue)
45
- log! "Found job on #{queue}"
46
- return @job_in_progress
47
- end
48
- # Start the next search at the queue after the one from which we pick a job.
41
+ grouped_queues = queues.sort.group_by{|u| /(\d{1,20})_.*/.match(u) ? /(\d{1,20})_.*/.match(u).captures.first : nil}
42
+
43
+ #Instance queue grouping
44
+ if !grouped_queues.keys.include?(nil) && grouped_queues.keys.size > 0
45
+ @n ||= 0
49
46
  @n += 1
47
+ @n = @n % grouped_queues.keys.size
48
+
49
+ grouped_queues.keys.rotate(@n).each do |key|
50
+ grouped_queues[key].each do |queue|
51
+ log! "Checking #{queue}"
52
+ if should_work_on_queue?(queue) && @job_in_progress = Resque::Job.reserve(queue)
53
+ log! "Found job on #{queue}"
54
+ return @job_in_progress
55
+ end
56
+ end
57
+ @n += 1 # Start the next search at the queue after the one from which we pick a job.
58
+ end
59
+ nil
60
+ else
61
+ return reserve_without_round_robin
50
62
  end
51
-
52
- nil
63
+
53
64
  rescue Exception => e
54
65
  log "Error reserving job: #{e.inspect}"
55
66
  log e.backtrace.join("\n")
@@ -79,7 +90,7 @@ module Resque
79
90
 
80
91
  #Remove Queues under Api Limits
81
92
  api_limit_instances = Redis.current.keys('APILimits:*').map {|key| key.split('APILimits:').last.to_i}
82
- real_queues = real_queues.select {|key| key if !api_limit_instances.include?((key.match(/^(\d*)_.*/) || [])[1].to_i)}
93
+ real_queues = real_queues.select {|key| key if !api_limit_instances.include?((key.match(/^(\d*)_.*/) || [])[1].to_i)} ## 2
83
94
 
84
95
  #Queue Pausing
85
96
  paused_instances = Redis.current.keys('resque:PauseQueue:*').map {|key| key.split('resque:PauseQueue:').last.to_i}
@@ -4,10 +4,11 @@ require 'zuora_connect/exceptions'
4
4
  require 'zuora_connect/controllers/helpers'
5
5
  require 'zuora_connect/views/helpers'
6
6
  require 'zuora_connect/railtie'
7
- require 'resque/additions'
7
+ require 'resque/additions'
8
8
  require 'resque/dynamic_queues'
9
9
  require 'resque/self_lookup'
10
10
 
11
+
11
12
  module ZuoraConnect
12
13
  class << self
13
14
  attr_accessor :configuration
@@ -1,6 +1,6 @@
1
1
  module ZuoraConnect
2
2
  class Configuration
3
- attr_accessor :oauth_client_id, :oauth_client_secret, :oauth_client_redirect_uri,:use_s3, :default_locale,:dev_mode_appinstance ,:dev_mode_admin, :dev_mode_user, :dev_mode_pass, :default_time_zone,:delayed_job,:url, :private_key, :dev_mode_logins,:dev_mode_mode, :dev_mode_options, :mode, :timeout,:dev_mode_secret_access_key,:dev_mode_access_key_id,:aws_region, :s3_bucket_name, :s3_folder_name, :additional_apartment_models
3
+ attr_accessor :oauth_client_id, :oauth_client_secret, :oauth_client_redirect_uri,:use_s3, :default_locale,:dev_mode_appinstance ,:dev_mode_admin, :dev_mode_user, :dev_mode_pass, :default_time_zone,:delayed_job,:url, :private_key, :dev_mode_logins,:dev_mode_mode, :dev_mode_options, :mode, :timeout,:dev_mode_secret_access_key,:dev_mode_access_key_id,:aws_region, :s3_bucket_name, :s3_folder_name, :additional_apartment_models, :telegraf_endpoint, :enable_inbound_metrics_flag, :enable_outbound_metrics_flag, :telegraf_client, :custom_prometheus_update_block, :app_name, :influxdb_series_name_inbound, :influxdb_series_name_outbound
4
4
 
5
5
  def initialize
6
6
  @default_locale = :en
@@ -11,6 +11,15 @@ module ZuoraConnect
11
11
  @use_s3 = false
12
12
  @private_key = ENV["CONNECT_KEY"]
13
13
  @additional_apartment_models = []
14
+ @telegraf_endpoint = 'udp://telegraf-app-metrics.monitoring.svc.cluster.local:8094'
15
+ @enable_inbound_metrics_flag = false
16
+ @enable_outbound_metrics_flag = false
17
+ @telegraf_client = Telegraf::Agent.new @telegraf_endpoint
18
+
19
+ # Setting the app name for telegraf write
20
+ @app_name = ENV['DEIS_APP'].present? ? "#{ENV['DEIS_APP']}" : "#{Rails.application.class.parent_name}"
21
+ @influxdb_series_name_inbound = "request-inbound"
22
+ @influxdb_series_name_outbound = "request-outbound"
14
23
 
15
24
  # OAuth Settings
16
25
  @oauth_client_id = ""
@@ -6,6 +6,7 @@ module ZuoraConnect
6
6
 
7
7
  def authenticate_app_api_request
8
8
  #Skip session for api requests
9
+ Thread.current[:appinstance] = nil
9
10
  request.session_options[:skip] = true
10
11
  start_time = Time.now
11
12
  if request.headers["API-Token"].present?
@@ -24,8 +25,8 @@ module ZuoraConnect
24
25
  end
25
26
 
26
27
  def authenticate_connect_app_request
28
+ Thread.current[:appinstance] = nil
27
29
  start_time = Time.now
28
-
29
30
  if ZuoraConnect.configuration.mode == "Production"
30
31
  if request["data"]
31
32
  setup_instance_via_data
@@ -34,6 +34,23 @@ module ZuoraConnect
34
34
  end
35
35
  end
36
36
 
37
+ class APIError < Error
38
+ attr_reader :code, :response
39
+ attr_writer :default_message
40
+
41
+ def initialize(message: nil,response: nil, code: nil)
42
+ @code = code
43
+ @message = message
44
+ @response = response
45
+ @default_message = "Connect update error."
46
+ end
47
+
48
+ def to_s
49
+ @message || @default_message
50
+ end
51
+
52
+ end
53
+
37
54
  class AccessDenied < Error
38
55
  attr_writer :default_message
39
56
 
@@ -1,5 +1,9 @@
1
+ require 'middleware/metrics_middleware'
2
+
1
3
  module ZuoraConnect
2
4
  class Railtie < Rails::Railtie
5
+
6
+
3
7
  config.before_initialize do
4
8
  version = Rails.version
5
9
  if version >= "5.0.0"
@@ -12,6 +16,24 @@ module ZuoraConnect
12
16
  ::Rails.configuration.action_dispatch.x_sendfile_header = nil
13
17
  end
14
18
 
19
+ # Base object not being loaded at this point for some reason
20
+ if ZuoraConnect::Configuration.new.enable_inbound_metrics_flag == true
21
+
22
+ if defined? Prometheus
23
+ initializer "prometheus.configure_rails_initialization" do |app|
24
+ app.middleware.use Prometheus::Middleware::Exporter,(options ={:path => '/connect/internal/metrics'})
25
+ end
26
+ end
27
+ initializer "zuora_connect.configure_rails_initialization" do |app|
28
+ app.middleware.insert_after Rack::Sendfile, Middleware::MetricsMiddleware
29
+ end
30
+
31
+ # hook to process_action
32
+ ActiveSupport::Notifications.subscribe('process_action.action_controller', Middleware::PageRequest.new)
33
+
34
+ end
35
+
36
+
15
37
  initializer(:rails_stdout_logging, before: :initialize_logger) do
16
38
  if Rails.env != 'development' && !ENV['DEIS_APP'].blank?
17
39
  require 'lograge'
@@ -23,7 +45,7 @@ module ZuoraConnect
23
45
  Rails.configuration.lograge.custom_options = lambda do |event|
24
46
  exceptions = %w(controller action format id)
25
47
  {
26
- appinstance_id: Thread.current[:appinstance].present? ? Thread.current[:appinstance].id : "Unknown",
48
+ appinstance_id: Thread.current[:appinstance].present? ? Thread.current[:appinstance].id : 0,
27
49
  params: event.payload[:params].except(*exceptions),
28
50
  exception: event.payload[:exception],
29
51
  exception_object: event.payload[:exception_object]
@@ -1,4 +1,3 @@
1
1
  module ZuoraConnect
2
- VERSION = "1.5.317"
2
+ VERSION = "1.6.0"
3
3
  end
4
-
metadata CHANGED
@@ -1,20 +1,23 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zuora_connect
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.317
4
+ version: 1.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Connect Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-04-25 00:00:00.000000000 Z
11
+ date: 2018-08-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: apartment
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.2'
20
+ - - ">="
18
21
  - !ruby/object:Gem::Version
19
22
  version: 1.2.0
20
23
  type: :runtime
@@ -22,6 +25,9 @@ dependencies:
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
27
  - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '1.2'
30
+ - - ">="
25
31
  - !ruby/object:Gem::Version
26
32
  version: 1.2.0
27
33
  - !ruby/object:Gem::Dependency
@@ -29,6 +35,9 @@ dependencies:
29
35
  requirement: !ruby/object:Gem::Requirement
30
36
  requirements:
31
37
  - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: 1.5.0
40
+ - - ">="
32
41
  - !ruby/object:Gem::Version
33
42
  version: 1.4.0
34
43
  type: :runtime
@@ -36,6 +45,9 @@ dependencies:
36
45
  version_requirements: !ruby/object:Gem::Requirement
37
46
  requirements:
38
47
  - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: 1.5.0
50
+ - - ">="
39
51
  - !ruby/object:Gem::Version
40
52
  version: 1.4.0
41
53
  - !ruby/object:Gem::Dependency
@@ -94,6 +106,20 @@ dependencies:
94
106
  - - ">="
95
107
  - !ruby/object:Gem::Version
96
108
  version: '0'
109
+ - !ruby/object:Gem::Dependency
110
+ name: telegraf
111
+ requirement: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - "~>"
114
+ - !ruby/object:Gem::Version
115
+ version: 0.4.0
116
+ type: :runtime
117
+ prerelease: false
118
+ version_requirements: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - "~>"
121
+ - !ruby/object:Gem::Version
122
+ version: 0.4.0
97
123
  - !ruby/object:Gem::Dependency
98
124
  name: railties
99
125
  requirement: !ruby/object:Gem::Requirement
@@ -226,6 +252,7 @@ files:
226
252
  - app/views/zuora_connect/static/session_error.html.erb
227
253
  - config/initializers/apartment.rb
228
254
  - config/initializers/object_method_hooks.rb
255
+ - config/initializers/prometheus.rb
229
256
  - config/initializers/redis.rb
230
257
  - config/initializers/resque.rb
231
258
  - config/initializers/to_bool.rb
@@ -240,6 +267,7 @@ files:
240
267
  - db/migrate/20110503003603_add_catalog_mappings_to_app_instance.rb
241
268
  - db/migrate/20110503003604_catalog_default.rb
242
269
  - db/migrate/20180301052853_add_catalog_attempted_at.rb
270
+ - lib/middleware/metrics_middleware.rb
243
271
  - lib/resque/additions.rb
244
272
  - lib/resque/dynamic_queues.rb
245
273
  - lib/resque/self_lookup.rb
@@ -317,44 +345,44 @@ signing_key:
317
345
  specification_version: 4
318
346
  summary: Summary of Connect.
319
347
  test_files:
320
- - test/models/zuora_connect/app_instance_test.rb
321
- - test/test_helper.rb
322
348
  - test/controllers/zuora_connect/api/v1/app_instance_controller_test.rb
349
+ - test/dummy/bin/rake
350
+ - test/dummy/bin/bundle
351
+ - test/dummy/bin/setup
352
+ - test/dummy/bin/rails
353
+ - test/dummy/Rakefile
354
+ - test/dummy/public/favicon.ico
323
355
  - test/dummy/public/404.html
324
356
  - test/dummy/public/422.html
325
357
  - test/dummy/public/500.html
326
- - test/dummy/public/favicon.ico
327
- - test/dummy/Rakefile
328
- - test/dummy/README.rdoc
329
358
  - test/dummy/app/controllers/application_controller.rb
330
- - test/dummy/app/helpers/application_helper.rb
331
359
  - test/dummy/app/views/layouts/application.html.erb
332
- - test/dummy/app/assets/stylesheets/application.css
360
+ - test/dummy/app/helpers/application_helper.rb
333
361
  - test/dummy/app/assets/javascripts/application.js
362
+ - test/dummy/app/assets/stylesheets/application.css
363
+ - test/dummy/README.rdoc
364
+ - test/dummy/config.ru
365
+ - test/dummy/config/locales/en.yml
334
366
  - test/dummy/config/routes.rb
367
+ - test/dummy/config/environment.rb
368
+ - test/dummy/config/database.yml
369
+ - test/dummy/config/secrets.yml
335
370
  - test/dummy/config/boot.rb
336
- - test/dummy/config/initializers/assets.rb
337
- - test/dummy/config/initializers/wrap_parameters.rb
338
- - test/dummy/config/initializers/session_store.rb
371
+ - test/dummy/config/environments/production.rb
372
+ - test/dummy/config/environments/development.rb
373
+ - test/dummy/config/environments/test.rb
374
+ - test/dummy/config/initializers/backtrace_silencers.rb
339
375
  - test/dummy/config/initializers/filter_parameter_logging.rb
376
+ - test/dummy/config/initializers/assets.rb
340
377
  - test/dummy/config/initializers/inflections.rb
378
+ - test/dummy/config/initializers/session_store.rb
379
+ - test/dummy/config/initializers/wrap_parameters.rb
341
380
  - test/dummy/config/initializers/mime_types.rb
342
- - test/dummy/config/initializers/backtrace_silencers.rb
343
381
  - test/dummy/config/initializers/cookies_serializer.rb
344
- - test/dummy/config/environment.rb
345
382
  - test/dummy/config/application.rb
346
- - test/dummy/config/database.yml
347
- - test/dummy/config/secrets.yml
348
- - test/dummy/config/environments/production.rb
349
- - test/dummy/config/environments/test.rb
350
- - test/dummy/config/environments/development.rb
351
- - test/dummy/config/locales/en.yml
352
- - test/dummy/bin/rails
353
- - test/dummy/bin/rake
354
- - test/dummy/bin/setup
355
- - test/dummy/bin/bundle
356
- - test/dummy/config.ru
383
+ - test/lib/generators/zuora_connect/datatable_generator_test.rb
384
+ - test/test_helper.rb
385
+ - test/fixtures/zuora_connect/app_instances.yml
357
386
  - test/integration/navigation_test.rb
387
+ - test/models/zuora_connect/app_instance_test.rb
358
388
  - test/zuora_connect_test.rb
359
- - test/fixtures/zuora_connect/app_instances.yml
360
- - test/lib/generators/zuora_connect/datatable_generator_test.rb