Ravn::BDE::Bolt::
Responses
module
Manual responses bolt component. This handles workflows such as a simple “acknowledgement”, or a multi-step information prompt/gather.
Emits: - direct.bolt.responded.$bolt_id
Consumes: - bolt.respond.$bolt_id
- VALID_STEP_TYPES
Datatypes recognized in a response step.
apply( bolt_class, from: :default, send_label: 'Send', steps: [] )
Add some response-specific configuration to bolts that have responses.
def self::apply( bolt_class, from: :default, send_label: 'Send', steps: [] )
super
bolt_class.stateful_variable :expected_responses, default: {}
from_segment = bolt_class.segment_for_config( from )
bolt_class.singleton_attr_accessor( :responses_from )
bolt_class.singleton_attr_accessor( :send_label )
bolt_class.singleton_attr_accessor( :steps )
bolt_class.responses_from = from_segment
bolt_class.send_label = send_label
bolt_class.steps = steps
end
Inspect configuration for any errors and normalize values to simplify type checks on displays.
def self::validate( opts )
opts = opts.slice( :from, :send_label, :steps )
opts[ :steps ] = (opts[ :steps ] || []).
each_with_object( [] ).with_index do |(step, acc), i|
raise "Invalid step, expected Hash" unless step.is_a?( Hash )
raise "Missing 'label'" unless step[ :label ]
case step[ :type ]
when 'integer'
if step[ :range ]
range = Array( step[:range] ).values_at( 0, -1 ).map( &:to_i )
step[ :range ] = range
end
when 'select'
raise "Missing 'values' field on select" unless step[ :values ]
step[ :values ] = Array( step[:values].map( &:to_s ) )
else
raise "Unhandled step type, must be one of %p" % [ VALID_STEP_TYPES.join(', ') ]
end
acc << step
rescue => err
raise Ravn::BDE::ValidationError, "%s (at index %p)" % [ err.message, i ]
end
return opts
end
all_responses_received?()
Returns true
if all expected responses have been received.
def all_responses_received?
answers = self.expected_responses&.values or return false
return answers.all?
end
event_payload_for( phase, originating_event )
Return the ‘data’ section for the bolt message emitted during the given phase
.
def event_payload_for( phase, originating_event )
payload = super
case phase
when :send
payload[ :to ] |= self.expected_responses.keys
payload[ :expected_responses ] = self.expected_responses
when :receive
payload[ :send_label ] = self.class.send_label
payload[ :steps ] = self.class.steps
payload[ :expects_response ] = self.expects_response_from_me?
when :summarize
if originating_event.type.start_with?( 'direct.bolt.responded.' ) ||
originating_event.type.start_with?( 'bolt.respond.' )
callsign = originating_event.callsign
payload[ :expected_responses ] = { callsign => self.expected_responses[callsign] }
payload[ :responses ] = originating_event.data[:responses]
end
end
return payload
end
Returns true
if the bolt should be terminated and saved to history.
def ready_to_be_finished?
return super && self.all_responses_received?
end
recipient_segment_members()
Fetch the map of who acks are expected from at the current moment as a Hash keyed by callsign.
def recipient_segment_members
segment_members = self.class.responses_from.to_h
segment_members.delete( Ravn::BDE.callsign ) if
self.class.responses_from.name == 'Everyone'
return segment_members
end
Send responses from displays as direct messages to the bolt’s originator.
def respond_phase( event )
self.log.debug "responding to %s with: %p" % [ event.data[:callsign], event.data[:responses] ]
if self.is_target_of?( event )
self.summarize_phase( event )
else
data = {
instance_id: event.data[:instance_id],
responses: event.data[:responses]
}
fields = { to: event.data[:callsign], data: }
self.send_message( 'direct.bolt.responded.${bolt_id}', fields )
end
super
end
Overloaded to set up the expected responses table.
def send_phase( event )
self.expected_responses = self.recipient_segment_members
self.log.debug "Expecting responses from %d recipients" % [ self.expected_responses.size ]
super
end
Overloaded to record response events. This may finish the bolt (via super
) if it was the last expected response.
def summarize_phase( event )
if self.is_target_of?( event )
if event.type.start_with?( 'direct.bolt.responded.' ) ||
event.type.start_with?( 'bolt.respond.' )
callsign = event.callsign or raise "event is missing a callsign"
self.log.info "Got response for %s [%s] from %s" %
[ self.class.name, self.id, callsign ]
self.expected_responses = self.expected_responses.merge( callsign => Ravn.time )
end
else
self.log.warn "Ignoring event from another instance! %p" % [ event ]
end
super
end