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

Constants

VALID_STEP_TYPES

Datatypes recognized in a response step.

Public Class Methods

apply( bolt_class, from: :default, send_label: 'Send', steps: [] )

Add some response-specific configuration to bolts that have responses.

# File lib/ravn/bde/bolt/responses.rb, line 33
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
validate( opts )

Inspect configuration for any errors and normalize values to simplify type checks on displays.

# File lib/ravn/bde/bolt/responses.rb, line 52
def self::validate( opts )

        # Ignore and remove any unknown top-level keys.
        opts = opts.slice( :from, :send_label, :steps )

        # Ensure any configured steps look sane.
        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

                # Lookin' good!
                acc << step

        rescue => err
                raise Ravn::BDE::ValidationError, "%s (at index %p)" % [ err.message, i ]
        end

        return opts
end

Public Instance Methods

all_responses_received?()

Returns true if all expected responses have been received.

# File lib/ravn/bde/bolt/responses.rb, line 173
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.

# File lib/ravn/bde/bolt/responses.rb, line 142
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
ready_to_be_finished?()

Returns true if the bolt should be terminated and saved to history.

# File lib/ravn/bde/bolt/responses.rb, line 167
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.

# File lib/ravn/bde/bolt/responses.rb, line 181
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
respond_phase( event )

Send responses from displays as direct messages to the bolt’s originator.

# File lib/ravn/bde/bolt/responses.rb, line 100
def respond_phase( event )
        self.log.debug "responding to %s with: %p" % [ event.data[:callsign], event.data[:responses] ]

        # If it's a response to your own bolt, send it as a summary
        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
send_phase( event )

Overloaded to set up the expected responses table.

# File lib/ravn/bde/bolt/responses.rb, line 91
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
summarize_phase( event )

Overloaded to record response events. This may finish the bolt (via super) if it was the last expected response.

# File lib/ravn/bde/bolt/responses.rb, line 122
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