Module: NdrWorkflow::EActionProgress

Included in:
EAction
Defined in:
lib/ndr_workflow/e_action_progress.rb

Overview

Logic to be shared by EAction and QueuedJob:

Constant Summary collapse

COMMENT_CLASSES =

Classes allowed inside YAML-serialized comments

[Ourdate, DateTime, Time, Date, ActiveSupport::TimeWithZone,
ActiveSupport::TimeZone, ActiveSupport::HashWithIndifferentAccess,
Symbol].freeze

Instance Method Summary collapse

Instance Method Details

#log_error(error, extramessage = nil) ⇒ Object

This method takes an exception and updates the e_action so that it can be seen easily through the UI.



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/ndr_workflow/e_action_progress.rb', line 81

def log_error(error, extramessage = nil)
  self.reload unless new_record?

  error_info = {
    'error_message' => [error.message, extramessage].compact.join(': '),
    'updated_at'    => Time.now
  }

  begin
    if defined?(NdrError)
      fingerprint, log = NdrError.log(error, { :user_id => self.startedby, :user_roles => '(BATCH)' }, nil)
      error_info['error_fingerprint'] = fingerprint.id if fingerprint
    else
      logger.warn("Failed to log EAction exception in the DB! (#{$!.to_s})")
    end
  rescue
    logger.warn("Failed to log EAction exception in the DB! (#{$!.to_s})")
  end

  data = progress.merge(error_info)
  max_len = (EAction.columns_hash['comments'].sql_type.match(/([0-9]*)\)/).nil? ? 0 : EAction.columns_hash['comments'].sql_type.match(/([0-9]*)\)/)[1]).to_i 
  #    if Era.db_oracle?
  #      max_len = EAction.columns_hash['comments'].sql_type.match(/VARCHAR2\(([0-9]*)\)/)[1].to_i
  #    elsif Era.db_postgres?
  #      max_len = EAction.columns_hash['comments'].sql_type.match(/character varying\(([0-9]*)\)/)[1].to_i
  #    end
  if YAML.dump(data).length > max_len
    # Horribly inefficient; preserve as much information as possible.
    len2 = max_len - YAML.dump(data.merge('error_message' => nil)).size
    while YAML.dump(data).length > max_len do
      len2 -= 1
      data['error_message'] = [error.message, extramessage].compact.collect{|s|
        "#{s.to_s[0..len2]}#{'...' if len2 < s.to_s.length}"
      }.join(': ')
    end
    logger.warn("EAction#log_error: Warning: Truncated error message: #{[error.message, extramessage].inspect} for EAction(#{id})")
  end
  update(comments: YAML.dump(data))
end

#progressObject



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/ndr_workflow/e_action_progress.rb', line 62

def progress
  if comments
    deserialised_comments = YAML.safe_load(comments, permitted_classes: COMMENT_CLASSES.freeze,
                                                     aliases: true)
    case deserialised_comments
    when Integer
      # Legacy number
      return {'percentage' => comments.to_i}
    when String
      return {'message' => deserialised_comments}
    when Hash
      return deserialised_comments
    end
  end
  return {}
end

#update_percentage(i, total, min_percent = 0, max_percent = 100) ⇒ Object

Set the progress percentage of an activity. Progress will range from min_percent to max_percent as i varies from 0 to total. Sample usage:

total = items.length
items.each_with_index do |item, i|
do_something
action.update_percentage(i+1, total)
end


20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/ndr_workflow/e_action_progress.rb', line 20

def update_percentage(i, total, min_percent = 0, max_percent = 100)
  @last_updated_at ||= progress['updated_at']
  @last_percentage ||= progress['percentage']

  current_percentage = total == 0 ? 0 : (min_percent + i * (max_percent - min_percent) / total).to_i
  if @last_updated_at.nil? || @last_percentage.nil? ||
     (current_percentage < @last_percentage) || # Always show negative changes
     (current_percentage > @last_percentage && @last_updated_at < 5.minutes.ago) || # Rate limit positive changes to once every 5 minutes...
     (current_percentage.divmod(5).first > @last_percentage.divmod(5).first && @last_updated_at < 1.minute.ago) # ...but allow >5% changes every 1 minute
    @last_updated_at = Time.now
    @last_percentage = current_percentage
    # yaml = YAML.dump( progress.merge({'percentage' => @last_percentage, 'updated_at' => @last_updated_at}))
    yaml = YAML.dump({'percentage' => @last_percentage, 'updated_at' => @last_updated_at})
    begin
      raise(ActiveRecord::StatementInvalid, <<-DESC) if Rails.env.test?
        libutils is not reliably available in the test schema, so we
        test the fallback behaviour. This ensures it is the fallback
        behaviour that we get, even if libutils is present.
      DESC

      # Try to update the progress in an anonymous transaction, providing feedback when we
      # wouldn't otherwise get any (i.e. when the update is in a transaction)
      # TODO: Test for the corner case where the e_action is updateed within a transaction and
      # where this is blocked, causing it to fail over to a standard (transaction based) update.
      # The SQL statement is sanitized for security.
      # SECURE: BNS 2012-10-09
      sanitized_sql = self.class.send(:sanitize_sql_array,[
        "BEGIN libutils.set_e_action_comments(?, ?); END;", self.e_actionid, yaml
      ])
      ActiveRecord::Base.connection.execute(sanitized_sql)
    rescue ActiveRecord::StatementInvalid => e
      # Update the old way, using standard update
      Rails.logger.warn("EAction#update_percentage failed to use libutils.set_e_action_comments (#{e.message})")
      puts "EAction#update_percentage failed to use libutils.set_e_action_comments (#{e.message})" unless Rails.env.test?

      # The EAction could be stale as a result of PREP. Eg ChildhoodPreallocator
      reload unless changed?
      update(comments: yaml)
    end
  end
end