vwo-sdk 1.36.0 → 1.37.1

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.
@@ -45,7 +45,7 @@ class VWO
45
45
  LogLevelEnum::DEBUG,
46
46
  'VARIATION_RANGE_ALLOCATION',
47
47
  {
48
- '{file}' => FileNameEnum::CampaignUtil,
48
+ '{file}' => FileNameEnum::CAMPAIGN_UTIL,
49
49
  '{campaignKey}' => campaign['key'],
50
50
  '{variationName}' => variation['name'],
51
51
  '{variationWeight}' => variation['weight'],
@@ -177,21 +177,19 @@ class VWO
177
177
  campaigns = get_campaigns_from_campaign_keys(campaign_key, settings_file, goal_identifier, goal_type_to_track)
178
178
  elsif campaign_key.is_a?(String)
179
179
  campaign = get_campaign_for_campaign_key_and_goal(campaign_key, settings_file, goal_identifier, goal_type_to_track)
180
- if campaign
181
- campaigns = [campaign]
182
- end
180
+ campaigns = [campaign] if campaign
183
181
  end
184
- if campaigns.length() == 0
182
+ if campaigns.length == 0
185
183
  Utils::Logger.log(
186
184
  LogLevelEnum::ERROR,
187
185
  'CAMPAIGN_NOT_FOUND_FOR_GOAL',
188
186
  {
189
- '{file}' => FileNameEnum::CampaignUtil,
187
+ '{file}' => FileNameEnum::CAMPAIGN_UTIL,
190
188
  '{goalIdentifier}' => goal_identifier
191
189
  }
192
190
  )
193
191
  end
194
- return campaigns
192
+ campaigns
195
193
  end
196
194
 
197
195
  # fetch all running campaigns (having goal identifier goal_type_to_track and goal type CUSTOM|REVENUE|ALL) from settings
@@ -204,38 +202,31 @@ class VWO
204
202
  campaigns = []
205
203
  if settings_file
206
204
  settings_file['campaigns'].each do |campaign|
207
- if campaign.key?(:status) && campaign[:status] != 'RUNNING'
208
- next
209
- end
205
+ next if campaign.key?(:status) && campaign[:status] != 'RUNNING'
206
+
210
207
  goal = get_campaign_goal(campaign, goal_identifier)
211
- if validate_goal(goal, goal_type_to_track)
212
- campaigns.push(campaign)
213
- end
208
+ campaigns.push(campaign) if validate_goal(goal, goal_type_to_track)
214
209
  end
215
210
  end
216
211
  campaigns
217
212
  end
218
213
 
219
214
  def validate_goal(goal, goal_type_to_track)
220
- result = goal && (
215
+ goal && (
221
216
  goal_type_to_track == 'ALL' ||
222
217
  (
223
- GOAL_TYPES.has_value?(goal['type']) &&
218
+ GOAL_TYPES.value?(goal['type']) &&
224
219
  (GOAL_TYPES.key? goal_type_to_track) &&
225
220
  goal['type'] == GOAL_TYPES[goal_type_to_track]
226
221
  )
227
222
  )
228
- return result
229
223
  end
230
224
 
231
225
  def get_campaigns_from_campaign_keys(campaign_keys, settings_file, goal_identifier, goal_type_to_track = 'ALL')
232
226
  campaigns = []
233
227
  campaign_keys.each do |campaign_key|
234
-
235
228
  campaign = get_campaign_for_campaign_key_and_goal(campaign_key, settings_file, goal_identifier, goal_type_to_track)
236
- if campaign
237
- campaigns.push(campaign)
238
- end
229
+ campaigns.push(campaign) if campaign
239
230
  end
240
231
  campaigns
241
232
  end
@@ -244,9 +235,7 @@ class VWO
244
235
  campaign = get_running_campaign(campaign_key, settings_file)
245
236
  if campaign
246
237
  goal = get_campaign_goal(campaign, goal_identifier)
247
- if validate_goal(goal, goal_type_to_track)
248
- return campaign
249
- end
238
+ return campaign if validate_goal(goal, goal_type_to_track)
250
239
  end
251
240
  nil
252
241
  end
@@ -265,7 +254,7 @@ class VWO
265
254
  )
266
255
  nil
267
256
  end
268
- return campaign
257
+ campaign
269
258
  end
270
259
 
271
260
  # Checks whether a campaign is part of a group.
@@ -273,10 +262,9 @@ class VWO
273
262
  # @param[Hash] :settings_file Settings file for the project
274
263
  # @param[Integer] :campaign_id Id of campaign which is to be checked
275
264
  # @return[Boolean]
276
- def is_part_of_group(settings_file, campaign_id)
277
- if settings_file["campaignGroups"] && (settings_file["campaignGroups"].has_key?(campaign_id.to_s))
278
- return true
279
- end
265
+ def part_of_group?(settings_file, campaign_id)
266
+ return true if settings_file['campaignGroups']&.key?(campaign_id.to_s)
267
+
280
268
  false
281
269
  end
282
270
 
@@ -288,24 +276,34 @@ class VWO
288
276
  def get_group_campaigns(settings_file, group_id)
289
277
  group_campaign_ids = []
290
278
  group_campaigns = []
291
- groups = settings_file["groups"]
279
+ groups = settings_file['groups']
292
280
 
293
- if groups && groups.has_key?(group_id.to_s)
294
- group_campaign_ids = groups[group_id.to_s]["campaigns"]
295
- end
281
+ group_campaign_ids = groups[group_id.to_s]['campaigns'] if groups&.key?(group_id.to_s)
296
282
 
297
- if group_campaign_ids
298
- group_campaign_ids.each do |campaign_id|
299
- settings_file["campaigns"].each do |campaign|
300
- if campaign["id"] == campaign_id && campaign["status"] == STATUS_RUNNING
301
- group_campaigns.push(campaign)
302
- end
303
- end
283
+ group_campaign_ids&.each do |campaign_id|
284
+ settings_file['campaigns'].each do |campaign|
285
+ group_campaigns.push(campaign) if campaign['id'] == campaign_id && campaign['status'] == STATUS_RUNNING
304
286
  end
305
287
  end
306
288
  group_campaigns
307
289
  end
308
290
 
291
+ def campaign_goal_already_tracked?(user_id, campaign, identifiers, goal_identifier)
292
+ if identifiers.include? goal_identifier
293
+ @logger.log(
294
+ LogLevelEnum::INFO,
295
+ 'CAMPAIGN_GOAL_ALREADY_TRACKED',
296
+ {
297
+ '{file}' => FILE,
298
+ '{userId}' => user_id,
299
+ '{campaignKey}' => campaign['key'],
300
+ '{goalIdentifier}' => goal_identifier
301
+ }
302
+ )
303
+ return true
304
+ end
305
+ false
306
+ end
309
307
  end
310
308
  end
311
309
  end
@@ -40,7 +40,7 @@ class VWO
40
40
  LogLevelEnum::DEBUG,
41
41
  'IMPRESSION_FOR_PUSH',
42
42
  {
43
- '{file}' => FileNameEnum::CustomDimensionsUtil,
43
+ '{file}' => FileNameEnum::CUSTOM_DIMENSTIONS_UTIL,
44
44
  '{properties}' => JSON.generate(params)
45
45
  }
46
46
  )
@@ -63,7 +63,7 @@ class VWO
63
63
  LogLevelEnum::DEBUG,
64
64
  'IMPRESSION_FOR_PUSH',
65
65
  {
66
- '{file}' => FileNameEnum::CustomDimensionsUtil,
66
+ '{file}' => FileNameEnum::CUSTOM_DIMENSTIONS_UTIL,
67
67
  '{properties}' => JSON.generate(params)
68
68
  }
69
69
  )
@@ -12,29 +12,22 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
-
16
15
  require_relative '../constants'
17
16
 
18
17
  # Utility module for generating uuid
19
18
  class VWO
20
19
  module Utils
21
20
  class DataLocationManager
22
-
23
21
  @@instance = nil
24
22
 
25
23
  def self.get_instance
26
- if @@instance.nil?
27
- @@instance = self.new
28
- end
24
+ @@instance = new if @@instance.nil?
29
25
  @@instance
30
26
  end
31
27
 
32
-
33
28
  def get_data_location
34
29
  url = VWO::CONSTANTS::ENDPOINTS::BASE_URL
35
- if @settings.key?("collectionPrefix")
36
- url = url + '/' + @settings["collectionPrefix"]
37
- end
30
+ url = "#{url}/#{@settings['collectionPrefix']}" if @settings.key?('collectionPrefix')
38
31
  url
39
32
  end
40
33
 
@@ -40,10 +40,10 @@ class VWO
40
40
  return !value || value == 0 ? false : true if variable_type == VariableTypes.BOOLEAN
41
41
 
42
42
  return value if variable_type == VariableTypes::JSON
43
- rescue StandardError => _e
43
+ rescue StandardError => e
44
44
  Logger.log(
45
45
  LogLevelEnum::ERROR,
46
- 'unable to type cast variable value: ' + _e.message,
46
+ "unable to type cast variable value: #{e.message}",
47
47
  {
48
48
  '{file}' => FILE
49
49
  }
@@ -67,8 +67,6 @@ class VWO
67
67
 
68
68
  impression = usage_stats.merge(impression)
69
69
 
70
- logger = VWO::Logger.get_instance
71
-
72
70
  if is_track_user_api
73
71
  impression['ed'] = JSON.generate(p: 'server')
74
72
  impression['url'] = HTTPS_PROTOCOL + get_url(ENDPOINTS::TRACK_USER)
@@ -76,7 +74,7 @@ class VWO
76
74
  LogLevelEnum::DEBUG,
77
75
  'IMPRESSION_FOR_TRACK_USER',
78
76
  {
79
- '{file}' => FileNameEnum::ImpressionUtil,
77
+ '{file}' => FileNameEnum::IMPRESSION_UTIL,
80
78
  '{properties}' => remove_sensitive_properties(impression)
81
79
  }
82
80
  )
@@ -88,7 +86,7 @@ class VWO
88
86
  LogLevelEnum::DEBUG,
89
87
  'IMPRESSION_FOR_TRACK_GOAL',
90
88
  {
91
- '{file}' => FileNameEnum::ImpressionUtil,
89
+ '{file}' => FileNameEnum::IMPRESSION_UTIL,
92
90
  '{properties}' => JSON.generate(impression)
93
91
  }
94
92
  )
@@ -129,6 +127,7 @@ class VWO
129
127
  # Else Properties(dict)
130
128
  def create_bulk_event_impression(settings_file, campaign_id, variation_id, user_id, goal_id = nil, revenue = nil)
131
129
  return unless valid_number?(campaign_id) && valid_string?(user_id)
130
+
132
131
  is_track_user_api = true
133
132
  is_track_user_api = false unless goal_id.nil?
134
133
  account_id = settings_file['accountId']
@@ -139,13 +138,13 @@ class VWO
139
138
  u: generator_for(user_id, account_id, true),
140
139
  sId: get_current_unix_timestamp
141
140
  }
142
- logger = VWO::Logger.get_instance
141
+
143
142
  if is_track_user_api
144
143
  Logger.log(
145
144
  LogLevelEnum::DEBUG,
146
145
  'IMPRESSION_FOR_TRACK_USER',
147
146
  {
148
- '{file}' => FileNameEnum::ImpressionUtil,
147
+ '{file}' => FileNameEnum::IMPRESSION_UTIL,
149
148
  '{properties}' => remove_sensitive_properties(impression)
150
149
  }
151
150
  )
@@ -156,7 +155,7 @@ class VWO
156
155
  LogLevelEnum::DEBUG,
157
156
  'IMPRESSION_FOR_TRACK_GOAL',
158
157
  {
159
- '{file}' => FileNameEnum::ImpressionUtil,
158
+ '{file}' => FileNameEnum::IMPRESSION_UTIL,
160
159
  '{properties}' => JSON.generate(impression)
161
160
  }
162
161
  )
@@ -179,12 +178,10 @@ class VWO
179
178
  env: settings_file['sdkKey'],
180
179
  eTime: get_current_unix_timestamp_in_millis,
181
180
  random: get_random_number,
182
- p: "FS"
181
+ p: 'FS'
183
182
  }
184
183
 
185
- if event_name == EventEnum::VWO_VARIATION_SHOWN
186
- properties = properties.merge(usage_stats)
187
- end
184
+ properties = properties.merge(usage_stats) if event_name == EventEnum::VWO_VARIATION_SHOWN
188
185
  properties
189
186
  end
190
187
 
@@ -193,10 +190,10 @@ class VWO
193
190
  # @param[Hash] :settings_file
194
191
  # @param[String] :user_id
195
192
  # @param[String] :event_name
196
- # @param[Hash] :usage_stats
193
+ # @param[Hash] :_usage_stats
197
194
  # @return[Hash] :properties
198
195
  #
199
- def get_event_base_payload(settings_file, user_id, event_name, usage_stats = {})
196
+ def get_event_base_payload(settings_file, user_id, event_name, _usage_stats = {})
200
197
  uuid = generator_for(user_id, (settings_file['accountId']), true)
201
198
  sdk_key = settings_file['sdkKey']
202
199
 
@@ -211,12 +208,12 @@ class VWO
211
208
  }
212
209
 
213
210
  # if usage_stats
214
- # props = props.merge(usage_stats)
211
+ # props = props.merge(_usage_stats)
215
212
  # end
216
213
 
217
- properties = {
214
+ {
218
215
  d: {
219
- msgId: uuid + '_' + Time.now.to_i.to_s,
216
+ msgId: "#{uuid} + '_' + #{Time.now.to_i}",
220
217
  visId: uuid,
221
218
  sessionId: Time.now.to_i,
222
219
  event: {
@@ -231,8 +228,6 @@ class VWO
231
228
  }
232
229
  }
233
230
  }
234
-
235
- properties
236
231
  end
237
232
 
238
233
  # Builds payload to track the visitor.
@@ -242,27 +237,26 @@ class VWO
242
237
  # @param[String] :event_name
243
238
  # @param[Integer] :campaign_id
244
239
  # @param[Integer] :variation_id
245
- # @param[Hash] :usage_stats
240
+ # @param[Hash] :_usage_stats
246
241
  # @return[Hash] :properties
247
242
  #
248
- def get_track_user_payload_data(settings_file, user_id, event_name, campaign_id, variation_id, usage_stats = {})
243
+ def get_track_user_payload_data(settings_file, user_id, event_name, campaign_id, variation_id, _usage_stats = {})
249
244
  properties = get_event_base_payload(settings_file, user_id, event_name)
250
245
  properties[:d][:event][:props][:id] = campaign_id
251
246
  properties[:d][:event][:props][:variation] = variation_id
252
247
 
253
- #this is currently required by data-layer team, we can make changes on DACDN and remove it from here
248
+ # this is currently required by data-layer team, we can make changes on DACDN and remove it from here
254
249
  properties[:d][:event][:props][:isFirst] = 1
255
250
 
256
- logger = VWO::Logger.get_instance
257
251
  Logger.log(
258
- LogLevelEnum::DEBUG,
259
- 'IMPRESSION_FOR_EVENT_ARCH_TRACK_USER',
260
- {
261
- '{file}' => FileNameEnum::ImpressionUtil,
262
- '{accountId}' => settings_file['accountId'],
263
- '{userId}' => user_id,
264
- '{campaignId}' => campaign_id.to_s
265
- }
252
+ LogLevelEnum::DEBUG,
253
+ 'IMPRESSION_FOR_EVENT_ARCH_TRACK_USER',
254
+ {
255
+ '{file}' => FileNameEnum::IMPRESSION_UTIL,
256
+ '{accountId}' => settings_file['accountId'],
257
+ '{userId}' => user_id,
258
+ '{campaignId}' => campaign_id.to_s
259
+ }
266
260
  )
267
261
  properties
268
262
  end
@@ -281,15 +275,14 @@ class VWO
281
275
  def get_track_goal_payload_data(settings_file, user_id, event_name, revenue_value, metric_map, revenue_props = [])
282
276
  properties = get_event_base_payload(settings_file, user_id, event_name)
283
277
 
284
- logger = VWO::Logger.get_instance
285
278
  metric = {}
286
279
  metric_map.each do |campaign_id, goal_id|
287
- metric[('id_' + campaign_id.to_s).to_sym] = ['g_' + goal_id.to_s]
280
+ metric["id_#{campaign_id}".to_sym] = ["g_#{goal_id}"]
288
281
  Logger.log(
289
282
  LogLevelEnum::DEBUG,
290
283
  'IMPRESSION_FOR_EVENT_ARCH_TRACK_GOAL',
291
284
  {
292
- '{file}' => FileNameEnum::ImpressionUtil,
285
+ '{file}' => FileNameEnum::IMPRESSION_UTIL,
293
286
  '{accountId}' => settings_file['accountId'],
294
287
  '{goalName}' => event_name,
295
288
  '{userId}' => user_id,
@@ -302,7 +295,7 @@ class VWO
302
295
  metric: metric
303
296
  }
304
297
 
305
- if revenue_props.length() != 0 && revenue_value
298
+ if revenue_props.length != 0 && revenue_value
306
299
  revenue_props.each do |revenue_prop|
307
300
  properties[:d][:event][:props][:vwoMeta][revenue_prop.to_sym] = revenue_value
308
301
  end
@@ -330,12 +323,11 @@ class VWO
330
323
  properties[:d][:visitor][:props][tag_key] = tag_value
331
324
  end
332
325
 
333
- logger = VWO::Logger.get_instance
334
326
  Logger.log(
335
327
  LogLevelEnum::DEBUG,
336
328
  'IMPRESSION_FOR_EVENT_ARCH_PUSH',
337
329
  {
338
- '{file}' => FileNameEnum::ImpressionUtil,
330
+ '{file}' => FileNameEnum::IMPRESSION_UTIL,
339
331
  '{accountId}' => settings_file['accountId'],
340
332
  '{userId}' => user_id,
341
333
  '{property}' => JSON.generate(custom_dimension_map)
@@ -345,7 +337,7 @@ class VWO
345
337
  end
346
338
 
347
339
  def get_batch_event_query_params(account_id, sdk_key, usage_stats = {})
348
- return {
340
+ {
349
341
  a: account_id,
350
342
  sd: SDK_NAME,
351
343
  sv: SDK_VERSION,
@@ -19,7 +19,6 @@ require_relative '../logger'
19
19
  class VWO
20
20
  module Utils
21
21
  class Logger
22
-
23
22
  DEBUG = ::Logger::DEBUG
24
23
  INFO = ::Logger::INFO
25
24
  ERROR = ::Logger::ERROR
@@ -32,43 +31,37 @@ class VWO
32
31
  @@api_name = api_name
33
32
  end
34
33
 
35
- def self.get_log_message(logsType, message_type)
36
- if @@logs.nil?
37
- @@logs = VwoLogMessages.getMessage
38
- end
34
+ def self.get_log_message(logs_type, message_type)
35
+ @@logs = VwoLogMessages.getMessage if @@logs.nil?
39
36
 
40
- if !@@logs[logsType].key?(message_type)
41
- return message_type
42
- end
43
- return @@logs[logsType][message_type]
37
+ return message_type unless @@logs[logs_type].key?(message_type)
38
+
39
+ @@logs[logs_type][message_type]
44
40
  end
45
41
 
46
42
  def self.log(level, message_type, params, disable_logs = false)
47
- if disable_logs
48
- return
49
- end
50
- if level == DEBUG
51
- message = get_log_message('debug_logs', message_type)
52
- elsif level == INFO
53
- message = get_log_message('info_logs', message_type)
54
- elsif level == ERROR
55
- message = get_log_message('error_logs', message_type)
56
- elsif level == WARN
57
- message = get_log_message('warning_logs', message_type)
58
- else
59
- message = ""
60
- end
43
+ return if disable_logs
44
+
45
+ message = case level
46
+ when DEBUG
47
+ get_log_message('debug_logs', message_type)
48
+ when INFO
49
+ get_log_message('info_logs', message_type)
50
+ when ERROR
51
+ get_log_message('error_logs', message_type)
52
+ when WARN
53
+ get_log_message('warning_logs', message_type)
54
+ else
55
+ ''
56
+ end
61
57
  message = message.dup
62
58
 
63
59
  if message && !message.empty?
64
- params.each {
65
- |key, value|
66
- if message.include? key
67
- message[key.to_s]= value.to_s
68
- end
69
- }
60
+ params.each do |key, value|
61
+ message[key.to_s] = value.to_s if message.include? key
62
+ end
70
63
  end
71
- message = '[' + @@api_name + '] ' + message
64
+ message = "[#{@@api_name}] #{message}"
72
65
  VWO::Logger.get_instance.log(level, message)
73
66
  end
74
67
  end
@@ -31,12 +31,11 @@ class VWO
31
31
  http.use_ssl = true
32
32
  uri.query = URI.encode_www_form(params)
33
33
  headers = {
34
- 'Authorization'=>params[:env],
35
- 'Content-Type' =>'application/json',
36
- 'Accept'=>'application/json'
34
+ 'Authorization' => params[:env],
35
+ 'Content-Type' => 'application/json',
36
+ 'Accept' => 'application/json'
37
37
  }
38
- response = http.post(uri, post_data.to_json, headers)
39
- response
38
+ http.post(uri, post_data.to_json, headers)
40
39
  end
41
40
 
42
41
  def self.event_post(url, params, post_data, user_agent_value)
@@ -46,11 +45,10 @@ class VWO
46
45
  uri.query = URI.encode_www_form(params)
47
46
  headers = {
48
47
  'User-Agent' => user_agent_value,
49
- 'Content-Type' =>'application/json',
50
- 'Accept'=>'application/json'
48
+ 'Content-Type' => 'application/json',
49
+ 'Accept' => 'application/json'
51
50
  }
52
- response = http.post(uri, post_data.to_json, headers)
53
- response
51
+ http.post(uri, post_data.to_json, headers)
54
52
  end
55
53
  end
56
54
  end
@@ -19,44 +19,54 @@ require_relative '../constants'
19
19
 
20
20
  # Generic utility module
21
21
  class VWO
22
- module Utils
23
- module Utility
24
- include Validations
25
- include VWO::Utils
26
- include VWO::CONSTANTS
22
+ module Utils
23
+ module Utility
24
+ include Validations
25
+ include VWO::Utils
26
+ include VWO::CONSTANTS
27
27
 
28
- # converting hash with keys as strings into hash with keys as strings
29
- # @param[Hash]
30
- # @return[Hash]
31
- def convert_to_symbol_hash(hashObject)
32
- hashObject ||= {}
33
- convertedHash = {}
34
- hashObject.each do |key, value|
35
- if valid_hash?(value)
36
- convertedHash[key.to_sym] = convert_to_symbol_hash(value)
37
- else
38
- convertedHash[key.to_sym] = value
39
- end
40
- end
41
- convertedHash
42
- end
28
+ # converting hash with keys as strings into hash with keys as strings
29
+ # @param[Hash]
30
+ # @return[Hash]
31
+ def convert_to_symbol_hash(hash_object)
32
+ hash_object ||= {}
33
+ converted_hash = {}
34
+ hash_object.each do |key, value|
35
+ converted_hash[key.to_sym] = if valid_hash?(value)
36
+ convert_to_symbol_hash(value)
37
+ else
38
+ value
39
+ end
40
+ end
41
+ converted_hash
42
+ end
43
+
44
+ def remove_sensitive_properties(properties)
45
+ properties.delete('env')
46
+ properties.delete('env'.to_sym)
47
+ JSON.generate(properties)
48
+ end
43
49
 
44
- def remove_sensitive_properties(properties)
45
- properties.delete("env")
46
- properties.delete("env".to_sym)
47
- JSON.generate(properties)
48
- end
50
+ def get_url(endpoint)
51
+ DataLocationManager.get_instance.get_data_location + endpoint
52
+ end
49
53
 
50
- def get_url(endpoint)
51
- return DataLocationManager.get_instance().get_data_location + endpoint
52
- end
54
+ def prepare_push_response(custom_dimension_map, resp, result)
55
+ custom_dimension_map.each do |tag_key, _tag_value|
56
+ result[tag_key] = resp
57
+ end
58
+ result
59
+ end
53
60
 
54
- def prepare_push_response(custom_dimension_map, resp, result)
55
- custom_dimension_map.each do |tag_key, tag_value|
56
- result[tag_key] = resp
57
- end
58
- result
59
- end
61
+ def get_variation_identifiers(variation)
62
+ if variation['goal_identifier']
63
+ identifiers = variation['goal_identifier'].split(VWO_DELIMITER)
64
+ else
65
+ variation['goal_identifier'] = ''
66
+ identifiers = []
60
67
  end
68
+ identifiers
69
+ end
61
70
  end
71
+ end
62
72
  end