Diff
checker
Text
Text
Images
Documents
Excel
Folders
Legal
Enterprise
Desktop
Pricing
Sign in
Download Diffchecker Desktop
Compare text
Find the difference between two text files
Tools
History
Real-time editor
Hide whitespace changes
Hide unchanged lines
Disable line wrap
Layout
Split
Unified
Diff precision
Smart
Word
Char
Text styles
Change appearance
Syntax highlighting
Choose syntax
Ignore
Transform text
Go to first change
Edit input
Diffchecker Desktop
The most secure way to run Diffchecker. Get the Diffchecker Desktop app: your diffs never leave your computer!
Get Desktop
foremansalt
Created
2 years ago
Diff never expires
Clear
Export
Share
Explain
49 removals
Lines
Total
Removed
Characters
Total
Removed
To continue using this feature, upgrade to
Diff
checker
Pro
View Pricing
176 lines
Copy
148 additions
Lines
Total
Added
Characters
Total
Added
To continue using this feature, upgrade to
Diff
checker
Pro
View Pricing
262 lines
Copy
module ForemanSalt
module ForemanSalt
class ReportImporter
class ReportImporter
delegate :logger, to: :Rails
delegate :logger, to: :Rails
attr_reader :report
attr_reader :report
Copy
Copied
Copy
Copied
# Define logger as a class method
def self.logger
Rails.logger
end
def self.import(raw, proxy_id = nil)
def self.import(raw, proxy_id = nil)
Copy
Copied
Copy
Copied
logger.info "Starting import with raw data: #{raw.inspect} and proxy_id: #{proxy_id}"
raise ::Foreman::Exception, _('Invalid report') unless raw.is_a?(Hash)
raise ::Foreman::Exception, _('Invalid report') unless raw.is_a?(Hash)
raw.map do |host, report|
raw.map do |host, report|
Copy
Copied
Copy
Copied
logger.debug "Processing report for host: #{host}"
importer = ForemanSalt::ReportImporter.new(host, report, proxy_id)
importer = ForemanSalt::ReportImporter.new(host, report, proxy_id)
importer.import
importer.import
report = importer.report
report = importer.report
report.origin = 'Salt'
report.origin = 'Salt'
report.save!
report.save!
Copy
Copied
Copy
Copied
logger.info "Report saved for host: #{host}"
report
report
end
end
Copy
Copied
Copy
Copied
rescue => e
logger.error "Import failed: #{e.message}"
raise e
end
end
def initialize(host, raw, proxy_id = nil)
def initialize(host, raw, proxy_id = nil)
Copy
Copied
Copy
Copied
logger.debug "Initializing ReportImporter with host: #{host}, proxy_id: #{proxy_id}"
@host = find_or_create_host(host)
@host = find_or_create_host(host)
@raw = raw
@raw = raw
@proxy_id = proxy_id
@proxy_id = proxy_id
end
end
def import
def import
Copy
Copied
Copy
Copied
logger.info "
p
rocessing report for #{@host}"
logger.info "
P
rocessing report for #{@host}"
logger.debug { "Report
: #{@raw.inspect}" }
logger.debug { "Report
raw data
: #{@raw.inspect}" }
if @host.new_record? && !Setting[:create_new_host_when_report_is_uploaded]
if @host.new_record? && !Setting[:create_new_host_when_report_is_uploaded]
Copy
Copied
Copy
Copied
logger.info("
s
kipping report for #{@host} as it
s an unknown host and create_new_host_when_report_is_uploaded setting is disabled")
logger.info("
S
kipping report for #{@host} as it
'
s an unknown host and create_new_host_when_report_is_uploaded setting is disabled")
return ConfigReport.new
return ConfigReport.new
end
end
@host.salt_proxy_id ||= @proxy_id
@host.salt_proxy_id ||= @proxy_id
@host.last_report = start_time
@host.last_report = start_time
Copy
Copied
Copy
Copied
if [Array, String].
member?
@raw.class
if [Array, String].
include?(
@raw.class
)
logger.debug "Detected failures in raw data; processing failures"
process_failures # If Salt sends us only an array (or string), it's a list of fatal failures
process_failures # If Salt sends us only an array (or string), it's a list of fatal failures
else
else
Copy
Copied
Copy
Copied
logger.debug "Processing normal report data"
process_normal
process_normal
end
end
Copy
Copied
Copy
Copied
@host.save(validate:
false)
if
@host.save(validate:
true)
logger.debug "Host #{@host.name} saved successfully"
else
logger.warn "Host #{@host.name} failed to save: #{@host.errors.full_messages.join(', ')}"
end
@host.reload
@host.reload
@host.refresh_statuses([HostStatus.find_status_by_humanized_name('configuration')])
@host.refresh_statuses([HostStatus.find_status_by_humanized_name('configuration')])
Copy
Copied
Copy
Copied
logger.info("Imported report for #{@host} in #{
(Time.zone.now - start_time).round(2)
} seconds")
duration = (Time.zone.now - start_time).round(2)
logger.info("Imported report for #{@host} in #{
duration
} seconds")
rescue => e
logger.error "Failed to import report for #{@host}: #{e.message}"
raise e
end
end
private
private
def find_or_create_host(host)
def find_or_create_host(host)
Copy
Copied
Copy
Copied
logger.debug "Finding or creating host: #{host}"
@host ||= Host::Managed.find_by(name: host)
@host ||= Host::Managed.find_by(name: host)
unless @host
unless @host
Copy
Copied
Copy
Copied
new
= Host::Managed.new(name: host)
logger.info "Host not found; creating new host with name: #{host}"
new.save(validate: false)
new
_host
= Host::Managed.new(name: host)
@host = new
if new_host.save(validate: true)
logger.debug "New host #{host} created successfully"
@host = new_host
else
logger.error "Failed to create host #{host}: #{new_host.errors.full_messages.join(', ')}"
raise ::Foreman::Exception, _('Failed to create host')
end
end
end
@host
@host
Copy
Copied
Copy
Copied
rescue => e
logger.error "Error in find_or_create_host: #{e.message}"
raise e
end
end
def import_log_messages
def import_log_messages
Copy
Copied
Copy
Copied
logger.debug "Importing log messages"
@raw.each do |resource, result|
@raw.each do |resource, result|
Copy
Copied
Copy
Copied
level =
if result['changes'].blank? && result['result']
level =
determine_log_level(result)
:info
source_value = resource.to_s
elsif result['result'] == false
source = Source.find_or_create_by(value:
source_value)
:err
logger.debug "Log source: #{source_value}"
else
# nil mean "unchanged" when running highstate with test=True
:notice
end
source = Source.find_or_create_by(value:
resource)
Copy
Copied
Copy
Copied
message
= if result['changes']['diff']
message
_value = extract_message(result)
result['changes']['diff']
message = Message.find_or_create_by(value: message_value)
elsif result['pchanges'].present? && result['pchanges'].include?('diff')
logger.debug "Log message: #{message_value}"
result['pchanges']['diff']
elsif result['comment'].presence
result['comment']
else
'No message available'
end
Copy
Copied
Copy
Copied
message = Message.find_or_create_by(value: message)
Log.create(message_id: message.id, source_id: source.id, report: @report, level: level)
Log.create(message_id: message.id, source_id: source.id, report: @report, level: level)
Copy
Copied
Copy
Copied
logger.debug "Log entry created with level #{level} for resource #{resource}"
end
end
Copy
Copied
Copy
Copied
rescue => e
logger.error "Error importing log messages: #{e.message}"
raise e
end
end
def calculate_metrics
def calculate_metrics
Copy
Copied
Copy
Copied
logger.debug "Calculating metrics from raw data"
success = 0
success = 0
failed = 0
failed = 0
changed = 0
changed = 0
restarted = 0
restarted = 0
restarted_failed = 0
restarted_failed = 0
pending = 0
pending = 0
time = {}
time = {}
@raw.each do |resource, result|
@raw.each do |resource, result|
Copy
Copied
Copy
Copied
next unless result.is_a?
Hash
next unless result.is_a?
(
Hash
)
logger.debug "Processing resource: #{resource}"
Copy
Copied
Copy
Copied
if result['result']
if result['result']
== true
success += 1
success += 1
Copy
Copied
Copy
Copied
logger.debug "Resource #{resource} succeeded"
if resource.match(/^service_/) && result['comment'].include?('restarted')
if resource.match(/^service_/) && result['comment'].include?('restarted')
restarted += 1
restarted += 1
Copy
Copied
Copy
Copied
logger.debug "Service #{resource} was restarted"
elsif result['changes'].present?
elsif result['changes'].present?
changed += 1
changed += 1
Copy
Copied
Copy
Copied
logger.debug "Resource #{resource} changed"
elsif result['pchanges'].present?
elsif result['pchanges'].present?
pending += 1
pending += 1
Copy
Copied
Copy
Copied
logger.debug "Resource #{resource} has pending changes"
end
end
elsif result['result'].nil?
elsif result['result'].nil?
pending += 1
pending += 1
Copy
Copied
Copy
Copied
elsif
!
result['result']
logger.debug "Resource #{resource} is pending"
elsif
result['result']
== false
if resource.match(/^service_/) && result['comment'].include?('restarted')
if resource.match(/^service_/) && result['comment'].include?('restarted')
restarted_failed += 1
restarted_failed += 1
Copy
Copied
Copy
Copied
logger.debug "Service #{resource} failed to restart"
else
else
failed += 1
failed += 1
Copy
Copied
Copy
Copied
logger.debug "Resource #{resource} failed"
end
end
end
end
Copy
Copied
Copy
Copied
duration =
if result['
duration
'].is_a? String
duration =
parse_
duration
(result['duration'])
begin
Float(result['duration'].delete(' ms'))
rescue StandardError
nil
end
else
result['duration']
end
# Convert duration from milliseconds to seconds
duration /= 1000 if duration.is_a? Float
time[resource] = duration || 0
time[resource] = duration || 0
Copy
Copied
Copy
Copied
logger.debug "Duration for resource #{resource}: #{time[resource]} seconds"
end
end
time[:total] = time.values.compact.sum || 0
time[:total] = time.values.compact.sum || 0
Copy
Copied
Copy
Copied
logger.debug "Total execution time: #{time[:total]} seconds"
events = { total: changed + failed + restarted + restarted_failed, success: success + restarted, failure: failed + restarted_failed }
events = { total: changed + failed + restarted + restarted_failed, success: success + restarted, failure: failed + restarted_failed }
Copy
Copied
Copy
Copied
changes = { total: changed + restarted }
changes = { total: changed + restarted }
Copy
Copied
Copy
Copied
resources = {
'total' => @raw.size,
'applied' => changed,
'restarted' => restarted,
'failed' => failed,
'failed_restarts' => restarted_failed,
'skipped' => 0,
'scheduled' => 0,
'pending' => pending
}
Copy
Copied
Copy
Copied
resources = { 'total' => @raw.size, 'applied' => changed, 'restarted' => restarted, 'failed' => failed,
logger.debug "Metrics calculated: events=#{events}, changes=#{changes}, resources=#{resources}"
'failed_restarts' => restarted_failed, 'skipped' => 0, 'scheduled' => 0, 'pending' => pending }
{ events: events, resources: resources, changes: changes, time: time }
{ events: events, resources: resources, changes: changes, time: time }
Copy
Copied
Copy
Copied
rescue => e
logger.error "Error calculating metrics: #{e.message}"
raise e
end
end
def process_normal
def process_normal
Copy
Copied
Copy
Copied
logger.debug "Processing normal report"
metrics = calculate_metrics
metrics = calculate_metrics
status = ConfigReportStatusCalculator.new(counters: metrics[:resources].slice(*::ConfigReport::METRIC)).calculate
status = ConfigReportStatusCalculator.new(counters: metrics[:resources].slice(*::ConfigReport::METRIC)).calculate
Copy
Copied
Copy
Copied
logger.debug "Calculated status: #{status.inspect}"
@report = ConfigReport.new(host: @host, reported_at: start_time, status: status, metrics: metrics)
@report = ConfigReport.new(host: @host, reported_at: start_time, status: status, metrics: metrics)
Copy
Copied
Copy
Copied
return @report unless
@report.save
if
@report.save
logger.debug "Report saved successfully for host #{@host.name}"
import_log_messages
import_log_messages
else
logger.error "Failed to save report: #{@report.errors.full_messages.join(', ')}"
end
rescue => e
logger.error "Error processing normal report: #{e.message}"
raise e
end
end
def process_failures
def process_failures
Copy
Copied
Copy
Copied
@raw = [@raw] unless @raw.is_a?
Array
logger.debug "Processing failure report"
@raw = [@raw] unless @raw.is_a?
(
Array
)
status = ConfigReportStatusCalculator.new(counters: { 'failed' => @raw.size }).calculate
status = ConfigReportStatusCalculator.new(counters: { 'failed' => @raw.size }).calculate
Copy
Copied
Copy
Copied
logger.debug "Calculated status for failures: #{status.inspect}"
@report = ConfigReport.create(host: @host, reported_at: Time.zone.now, status: status, metrics: {})
@report = ConfigReport.create(host: @host, reported_at: Time.zone.now, status: status, metrics: {})
source = Source.find_or_create_by(value: 'Salt')
source = Source.find_or_create_by(value: 'Salt')
@raw.each do |failure|
@raw.each do |failure|
message = Message.find_or_create_by(value: failure)
message = Message.find_or_create_by(value: failure)
Log.create(message_id: message.id, source_id: source.id, report: @report, level: :err)
Log.create(message_id: message.id, source_id: source.id, report: @report, level: :err)
Copy
Copied
Copy
Copied
logger.debug "Logged failure message: #{failure}"
end
end
Copy
Copied
Copy
Copied
rescue => e
logger.error "Error processing failures: #{e.message}"
raise e
end
end
def start_time
def start_time
@start_time ||= Time.zone.now
@start_time ||= Time.zone.now
end
end
Copy
Copied
Copy
Copied
def determine_log_level(result)
if result['changes'].blank? && result['result'] == true
:info
elsif result['result'] == false
:err
else
:notice
end
end
def extract_message(result)
if result['changes'] && result['changes']['diff']
result['changes']['diff']
elsif result['pchanges'] && result['pchanges']['diff']
result['pchanges']['diff']
elsif result['comment'].present?
result['comment']
else
'No message available'
end
end
def parse_duration(duration)
if duration.is_a?(String)
duration_in_ms = duration.delete(' ms')
Float(duration_in_ms) / 1000
else
duration.to_f / 1000
end
rescue
logger.warn "Unable to parse duration: #{duration}"
nil
end
end
end
end
end
Saved diffs
Original text
Open file
module ForemanSalt class ReportImporter delegate :logger, to: :Rails attr_reader :report def self.import(raw, proxy_id = nil) raise ::Foreman::Exception, _('Invalid report') unless raw.is_a?(Hash) raw.map do |host, report| importer = ForemanSalt::ReportImporter.new(host, report, proxy_id) importer.import report = importer.report report.origin = 'Salt' report.save! report end end def initialize(host, raw, proxy_id = nil) @host = find_or_create_host(host) @raw = raw @proxy_id = proxy_id end def import logger.info "processing report for #{@host}" logger.debug { "Report: #{@raw.inspect}" } if @host.new_record? && !Setting[:create_new_host_when_report_is_uploaded] logger.info("skipping report for #{@host} as its an unknown host and create_new_host_when_report_is_uploaded setting is disabled") return ConfigReport.new end @host.salt_proxy_id ||= @proxy_id @host.last_report = start_time if [Array, String].member? @raw.class process_failures # If Salt sends us only an array (or string), it's a list of fatal failures else process_normal end @host.save(validate: false) @host.reload @host.refresh_statuses([HostStatus.find_status_by_humanized_name('configuration')]) logger.info("Imported report for #{@host} in #{(Time.zone.now - start_time).round(2)} seconds") end private def find_or_create_host(host) @host ||= Host::Managed.find_by(name: host) unless @host new = Host::Managed.new(name: host) new.save(validate: false) @host = new end @host end def import_log_messages @raw.each do |resource, result| level = if result['changes'].blank? && result['result'] :info elsif result['result'] == false :err else # nil mean "unchanged" when running highstate with test=True :notice end source = Source.find_or_create_by(value: resource) message = if result['changes']['diff'] result['changes']['diff'] elsif result['pchanges'].present? && result['pchanges'].include?('diff') result['pchanges']['diff'] elsif result['comment'].presence result['comment'] else 'No message available' end message = Message.find_or_create_by(value: message) Log.create(message_id: message.id, source_id: source.id, report: @report, level: level) end end def calculate_metrics success = 0 failed = 0 changed = 0 restarted = 0 restarted_failed = 0 pending = 0 time = {} @raw.each do |resource, result| next unless result.is_a? Hash if result['result'] success += 1 if resource.match(/^service_/) && result['comment'].include?('restarted') restarted += 1 elsif result['changes'].present? changed += 1 elsif result['pchanges'].present? pending += 1 end elsif result['result'].nil? pending += 1 elsif !result['result'] if resource.match(/^service_/) && result['comment'].include?('restarted') restarted_failed += 1 else failed += 1 end end duration = if result['duration'].is_a? String begin Float(result['duration'].delete(' ms')) rescue StandardError nil end else result['duration'] end # Convert duration from milliseconds to seconds duration /= 1000 if duration.is_a? Float time[resource] = duration || 0 end time[:total] = time.values.compact.sum || 0 events = { total: changed + failed + restarted + restarted_failed, success: success + restarted, failure: failed + restarted_failed } changes = { total: changed + restarted } resources = { 'total' => @raw.size, 'applied' => changed, 'restarted' => restarted, 'failed' => failed, 'failed_restarts' => restarted_failed, 'skipped' => 0, 'scheduled' => 0, 'pending' => pending } { events: events, resources: resources, changes: changes, time: time } end def process_normal metrics = calculate_metrics status = ConfigReportStatusCalculator.new(counters: metrics[:resources].slice(*::ConfigReport::METRIC)).calculate @report = ConfigReport.new(host: @host, reported_at: start_time, status: status, metrics: metrics) return @report unless @report.save import_log_messages end def process_failures @raw = [@raw] unless @raw.is_a? Array status = ConfigReportStatusCalculator.new(counters: { 'failed' => @raw.size }).calculate @report = ConfigReport.create(host: @host, reported_at: Time.zone.now, status: status, metrics: {}) source = Source.find_or_create_by(value: 'Salt') @raw.each do |failure| message = Message.find_or_create_by(value: failure) Log.create(message_id: message.id, source_id: source.id, report: @report, level: :err) end end def start_time @start_time ||= Time.zone.now end end end
Changed text
Open file
module ForemanSalt class ReportImporter delegate :logger, to: :Rails attr_reader :report # Define logger as a class method def self.logger Rails.logger end def self.import(raw, proxy_id = nil) logger.info "Starting import with raw data: #{raw.inspect} and proxy_id: #{proxy_id}" raise ::Foreman::Exception, _('Invalid report') unless raw.is_a?(Hash) raw.map do |host, report| logger.debug "Processing report for host: #{host}" importer = ForemanSalt::ReportImporter.new(host, report, proxy_id) importer.import report = importer.report report.origin = 'Salt' report.save! logger.info "Report saved for host: #{host}" report end rescue => e logger.error "Import failed: #{e.message}" raise e end def initialize(host, raw, proxy_id = nil) logger.debug "Initializing ReportImporter with host: #{host}, proxy_id: #{proxy_id}" @host = find_or_create_host(host) @raw = raw @proxy_id = proxy_id end def import logger.info "Processing report for #{@host}" logger.debug { "Report raw data: #{@raw.inspect}" } if @host.new_record? && !Setting[:create_new_host_when_report_is_uploaded] logger.info("Skipping report for #{@host} as it's an unknown host and create_new_host_when_report_is_uploaded setting is disabled") return ConfigReport.new end @host.salt_proxy_id ||= @proxy_id @host.last_report = start_time if [Array, String].include?(@raw.class) logger.debug "Detected failures in raw data; processing failures" process_failures # If Salt sends us only an array (or string), it's a list of fatal failures else logger.debug "Processing normal report data" process_normal end if @host.save(validate: true) logger.debug "Host #{@host.name} saved successfully" else logger.warn "Host #{@host.name} failed to save: #{@host.errors.full_messages.join(', ')}" end @host.reload @host.refresh_statuses([HostStatus.find_status_by_humanized_name('configuration')]) duration = (Time.zone.now - start_time).round(2) logger.info("Imported report for #{@host} in #{duration} seconds") rescue => e logger.error "Failed to import report for #{@host}: #{e.message}" raise e end private def find_or_create_host(host) logger.debug "Finding or creating host: #{host}" @host ||= Host::Managed.find_by(name: host) unless @host logger.info "Host not found; creating new host with name: #{host}" new_host = Host::Managed.new(name: host) if new_host.save(validate: true) logger.debug "New host #{host} created successfully" @host = new_host else logger.error "Failed to create host #{host}: #{new_host.errors.full_messages.join(', ')}" raise ::Foreman::Exception, _('Failed to create host') end end @host rescue => e logger.error "Error in find_or_create_host: #{e.message}" raise e end def import_log_messages logger.debug "Importing log messages" @raw.each do |resource, result| level = determine_log_level(result) source_value = resource.to_s source = Source.find_or_create_by(value: source_value) logger.debug "Log source: #{source_value}" message_value = extract_message(result) message = Message.find_or_create_by(value: message_value) logger.debug "Log message: #{message_value}" Log.create(message_id: message.id, source_id: source.id, report: @report, level: level) logger.debug "Log entry created with level #{level} for resource #{resource}" end rescue => e logger.error "Error importing log messages: #{e.message}" raise e end def calculate_metrics logger.debug "Calculating metrics from raw data" success = 0 failed = 0 changed = 0 restarted = 0 restarted_failed = 0 pending = 0 time = {} @raw.each do |resource, result| next unless result.is_a?(Hash) logger.debug "Processing resource: #{resource}" if result['result'] == true success += 1 logger.debug "Resource #{resource} succeeded" if resource.match(/^service_/) && result['comment'].include?('restarted') restarted += 1 logger.debug "Service #{resource} was restarted" elsif result['changes'].present? changed += 1 logger.debug "Resource #{resource} changed" elsif result['pchanges'].present? pending += 1 logger.debug "Resource #{resource} has pending changes" end elsif result['result'].nil? pending += 1 logger.debug "Resource #{resource} is pending" elsif result['result'] == false if resource.match(/^service_/) && result['comment'].include?('restarted') restarted_failed += 1 logger.debug "Service #{resource} failed to restart" else failed += 1 logger.debug "Resource #{resource} failed" end end duration = parse_duration(result['duration']) time[resource] = duration || 0 logger.debug "Duration for resource #{resource}: #{time[resource]} seconds" end time[:total] = time.values.compact.sum || 0 logger.debug "Total execution time: #{time[:total]} seconds" events = { total: changed + failed + restarted + restarted_failed, success: success + restarted, failure: failed + restarted_failed } changes = { total: changed + restarted } resources = { 'total' => @raw.size, 'applied' => changed, 'restarted' => restarted, 'failed' => failed, 'failed_restarts' => restarted_failed, 'skipped' => 0, 'scheduled' => 0, 'pending' => pending } logger.debug "Metrics calculated: events=#{events}, changes=#{changes}, resources=#{resources}" { events: events, resources: resources, changes: changes, time: time } rescue => e logger.error "Error calculating metrics: #{e.message}" raise e end def process_normal logger.debug "Processing normal report" metrics = calculate_metrics status = ConfigReportStatusCalculator.new(counters: metrics[:resources].slice(*::ConfigReport::METRIC)).calculate logger.debug "Calculated status: #{status.inspect}" @report = ConfigReport.new(host: @host, reported_at: start_time, status: status, metrics: metrics) if @report.save logger.debug "Report saved successfully for host #{@host.name}" import_log_messages else logger.error "Failed to save report: #{@report.errors.full_messages.join(', ')}" end rescue => e logger.error "Error processing normal report: #{e.message}" raise e end def process_failures logger.debug "Processing failure report" @raw = [@raw] unless @raw.is_a?(Array) status = ConfigReportStatusCalculator.new(counters: { 'failed' => @raw.size }).calculate logger.debug "Calculated status for failures: #{status.inspect}" @report = ConfigReport.create(host: @host, reported_at: Time.zone.now, status: status, metrics: {}) source = Source.find_or_create_by(value: 'Salt') @raw.each do |failure| message = Message.find_or_create_by(value: failure) Log.create(message_id: message.id, source_id: source.id, report: @report, level: :err) logger.debug "Logged failure message: #{failure}" end rescue => e logger.error "Error processing failures: #{e.message}" raise e end def start_time @start_time ||= Time.zone.now end def determine_log_level(result) if result['changes'].blank? && result['result'] == true :info elsif result['result'] == false :err else :notice end end def extract_message(result) if result['changes'] && result['changes']['diff'] result['changes']['diff'] elsif result['pchanges'] && result['pchanges']['diff'] result['pchanges']['diff'] elsif result['comment'].present? result['comment'] else 'No message available' end end def parse_duration(duration) if duration.is_a?(String) duration_in_ms = duration.delete(' ms') Float(duration_in_ms) / 1000 else duration.to_f / 1000 end rescue logger.warn "Unable to parse duration: #{duration}" nil end end end
Find difference