Class: ElectricSlide::CallQueue

Inherits:
Object
  • Object
show all
Includes:
Celluloid
Defined in:
lib/electric_slide/call_queue.rb

Defined Under Namespace

Classes: InvalidConnectionType, InvalidRequeueMethod

Constant Summary collapse

ENDED_CALL_EXCEPTIONS =
[
  Adhearsion::Call::Hangup,
  Adhearsion::Call::ExpiredError,
  Adhearsion::Call::CommandTimeout,
  Celluloid::DeadActorError,
  Punchblock::ProtocolError
]
CONNECTION_TYPES =
[
  :call,
  :bridge,
].freeze
AGENT_RETURN_METHODS =
[
  :auto,
  :manual,
].freeze
Error =
Class.new(StandardError)
MissingAgentError =
Class.new(Error)
DuplicateAgentError =
Class.new(Error)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ CallQueue

Returns a new instance of CallQueue.



75
76
77
78
79
80
81
82
83
84
# File 'lib/electric_slide/call_queue.rb', line 75

def initialize(opts = {})
  @agents = []      # Needed to keep track of global list of agents
  @queue = []       # Calls waiting for an agent

  update(
    agent_strategy: opts[:agent_strategy] || AgentStrategy::LongestIdle,
    connection_type: opts[:connection_type] || :call,
    agent_return_method: opts[:agent_return_method] || :auto
  )
end

Instance Attribute Details

#agent_return_methodObject

Returns the value of attribute agent_return_method.



45
46
47
# File 'lib/electric_slide/call_queue.rb', line 45

def agent_return_method
  @agent_return_method
end

#agent_strategyObject

Returns the value of attribute agent_strategy.



45
46
47
# File 'lib/electric_slide/call_queue.rb', line 45

def agent_strategy
  @agent_strategy
end

#connection_typeObject

Returns the value of attribute connection_type.



45
46
47
# File 'lib/electric_slide/call_queue.rb', line 45

def connection_type
  @connection_type
end

Class Method Details

.valid_agent_return_method?(agent_return_method) ⇒ Boolean

Returns:

  • (Boolean)


71
72
73
# File 'lib/electric_slide/call_queue.rb', line 71

def self.valid_agent_return_method?(agent_return_method)
  AGENT_RETURN_METHODS.include? agent_return_method
end

.valid_connection_type?(connection_type) ⇒ Boolean

Returns:

  • (Boolean)


67
68
69
# File 'lib/electric_slide/call_queue.rb', line 67

def self.valid_connection_type?(connection_type)
  CONNECTION_TYPES.include? connection_type
end

.valid_with?(attrs = {}) ⇒ Boolean

Returns:

  • (Boolean)


47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/electric_slide/call_queue.rb', line 47

def self.valid_with?(attrs = {})
  return false unless Hash === attrs

  if agent_strategy = attrs[:agent_strategy]
    begin
      agent_strategy.new
    rescue Exception
      return false
    end
  end
  if connection_type = attrs[:connection_type]
    return false unless valid_connection_type? connection_type
  end
  if agent_return_method = attrs[:agent_return_method]
    return false unless valid_agent_return_method? agent_return_method
  end

  true
end

Instance Method Details

#add_agent(agent) ⇒ Object

Registers an agent to the queue

Parameters:

  • agent (Agent)

    The agent to be added to the queue

Raises:

  • ArgumentError if the agent is malformed

  • DuplicateAgentError if this agent ID already exists

See Also:



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/electric_slide/call_queue.rb', line 163

def add_agent(agent)
  abort ArgumentError.new("#add_agent called with nil object") if agent.nil?
  abort DuplicateAgentError.new("Agent is already in the queue") if get_agent(agent.id)

  agent.queue = current_actor
  accept_agent! agent

  logger.info "Adding agent #{agent} to the queue"
  @agents << agent
  @strategy << agent if agent.presence == :available
  # Fake the presence callback since this is a new agent
  agent.callback :presence_change, current_actor, agent.call, agent.presence, :unavailable

  async.check_for_connections
end

#agent_available?Boolean

Checks whether an agent is available to take a call

Returns:

  • (Boolean)

    True if an agent is available



116
117
118
# File 'lib/electric_slide/call_queue.rb', line 116

def agent_available?
  @strategy.agent_available?
end

#available_agent_summaryHash

Returns information about the number of available agents The data returned depends on the AgentStrategy in use. The data will always include a :total count of the agents available

Returns:

  • (Hash)

    Summary information about agents available, depending on strategy



124
125
126
127
# File 'lib/electric_slide/call_queue.rb', line 124

def available_agent_summary
  # TODO: Make this a delegator?
  @strategy.available_agent_summary
end

#call_waiting?Boolean

Checks whether any callers are waiting

Returns:

  • (Boolean)

    True if a caller is waiting



343
344
345
# File 'lib/electric_slide/call_queue.rb', line 343

def call_waiting?
  @queue.length > 0
end

#calls_waitingFixnum

Returns the number of callers waiting in the queue

Returns:

  • (Fixnum)


349
350
351
# File 'lib/electric_slide/call_queue.rb', line 349

def calls_waiting
  @queue.length
end

#check_for_connectionsObject

Checks to see if any callers are waiting for an agent and attempts to connect them to an available agent



243
244
245
# File 'lib/electric_slide/call_queue.rb', line 243

def check_for_connections
  connect checkout_agent, get_next_caller while call_waiting? && agent_available?
end

#checkout_agentAgent

Assigns the first available agent, marking the agent :on_call

Returns:



131
132
133
134
135
136
137
# File 'lib/electric_slide/call_queue.rb', line 131

def checkout_agent
  agent = @strategy.checkout_agent
  if agent
    agent.update_presence(:on_call)
  end
  agent
end

#conditionally_return_agent(agent, return_method = @agent_return_method) ⇒ Object

Raises:

  • (ArgumentError)


323
324
325
326
327
328
329
330
331
332
333
# File 'lib/electric_slide/call_queue.rb', line 323

def conditionally_return_agent(agent, return_method = @agent_return_method)
  raise ArgumentError, "Invalid requeue method; must be one of #{AGENT_RETURN_METHODS.join ','}" unless AGENT_RETURN_METHODS.include? return_method

  if agent && @agents.include?(agent) && agent.on_call? && return_method == :auto
    logger.info "Returning agent #{agent.id} to queue"
    return_agent agent
  else
    logger.debug "Not returning agent #{agent.inspect} to the queue"
    return_agent agent, :after_call
  end
end

#connect(agent, queued_call) ⇒ Object

Connect an Agent to a caller

Parameters:



290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
# File 'lib/electric_slide/call_queue.rb', line 290

def connect(agent, queued_call)
  unless queued_call && queued_call.active?
    logger.warn "Inactive queued call found in #connect"
    return_agent agent
    return
  end

  queued_call[:agent] = agent

  logger.info "Connecting #{agent} with #{remote_party queued_call}"
  case @connection_type
  when :call
    call_agent agent, queued_call
  when :bridge
    bridge_agent agent, queued_call
  end
rescue *ENDED_CALL_EXCEPTIONS
  ignoring_ended_calls do
    if queued_call && queued_call.active?
      logger.warn "Dead call exception in #connect but queued_call still alive, reinserting into queue"
      priority_enqueue queued_call
    end
  end
  ignoring_ended_calls do
    if agent.call && agent.call.active?
      logger.warn "Dead call exception in #connect but agent call still alive, reinserting into queue"
      agent.callback :connection_failed, current_actor, agent.call, queued_call

      return_agent agent
    end
  end
end

#enqueue(call) ⇒ Object

Add a call to the end of the queue, the normal FIFO queue behavior

Parameters:



265
266
267
268
269
270
271
272
273
274
# File 'lib/electric_slide/call_queue.rb', line 265

def enqueue(call)
  ignoring_ended_calls do
    logger.info "Adding call from #{remote_party call} to the queue"
    call[:electric_slide_enqueued_at] = DateTime.now
    call.on_end { remove_call call }
    @queue << call unless @queue.include? call

    check_for_connections
  end
end

#get_agent(id) ⇒ Agent, Nil

Finds an agent known to the queue by that agent’s ID

Parameters:

  • id (String)

    The ID of the agent to locate

Returns:

  • (Agent, Nil)

    Agent object if found, Nil otherwise



154
155
156
# File 'lib/electric_slide/call_queue.rb', line 154

def get_agent(id)
  @agents.detect { |agent| agent.id == id }
end

#get_agentsArray

Returns a copy of the set of agents that are known to the queue

Returns:

  • (Array)

    Array of Agent objects



141
142
143
# File 'lib/electric_slide/call_queue.rb', line 141

def get_agents
  @agents.dup
end

#get_next_callerAdhearsion::Call

Returns the next waiting caller

Returns:



337
338
339
# File 'lib/electric_slide/call_queue.rb', line 337

def get_next_caller
  @queue.shift
end

#get_queued_callsArray

Returns a copy of the set of calls waiting to be answered that are known to the queue

Returns:

  • (Array)

    Array of Adhearsion::Call objects



147
148
149
# File 'lib/electric_slide/call_queue.rb', line 147

def get_queued_calls
  @queue.dup
end

#priority_enqueue(call) ⇒ Object

Add a call to the head of the queue. Among other reasons, this is used when a caller is sent to an agent, but the connection fails because the agent is not available.

Parameters:



250
251
252
253
254
255
256
257
258
259
260
261
# File 'lib/electric_slide/call_queue.rb', line 250

def priority_enqueue(call)
  # In case this is a re-insert on agent failure...
  # ... reset `:agent` call variable
  call[:agent] = nil
  # ... set, but don't reset, the enqueue time
  call[:electric_slide_enqueued_at] ||= DateTime.now

  call.on_end { remove_call call }
  @queue.unshift call

  check_for_connections
end

#remove_agent(agent, extra_params = {}) ⇒ Agent, Nil

Removes an agent from the queue entirely

Parameters:

  • agent (Agent)

    The Agent to be removed from the queue

  • extra_params (Hash) (defaults to: {})

    Application specific extra params

Returns:

  • (Agent, Nil)

    The Agent object if removed, Nil otherwise



233
234
235
236
237
238
239
# File 'lib/electric_slide/call_queue.rb', line 233

def remove_agent(agent, extra_params = {})
  agent.update_presence(:unavailable, extra_params)
  @strategy.delete agent
  @agents.delete agent
  logger.info "Removing agent #{agent} from the queue"
rescue Adhearsion::Call::ExpiredError
end

#remove_call(call) ⇒ Object

Remove a waiting call from the queue. Used if the caller hangs up or is otherwise removed.

Parameters:



278
279
280
281
282
283
284
285
# File 'lib/electric_slide/call_queue.rb', line 278

def remove_call(call)
  ignoring_ended_calls do
    unless call[:electric_slide_connected_at]
      logger.info "Caller #{remote_party call} has abandoned the queue"
    end
  end
  @queue.delete call
end

#return_agent(agent, new_presence = :available, address = nil) ⇒ Object

Marks an agent as available to take a call. To be called after an agent completes a call and is ready to take the next call.

Parameters:

  • agent (Agent)

    The Agent that is being returned to the queue

  • new_presence (Symbol) (defaults to: :available)

    The Agent‘s new presence

  • address (String, Optional) (defaults to: nil)

    The Agent‘s address. Only specified if it has changed



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/electric_slide/call_queue.rb', line 202

def return_agent(agent, new_presence = :available, address = nil)
  logger.debug "Returning #{agent} to the queue"

  return false unless get_agent(agent.id)

  agent.update_presence(new_presence)
  agent.address = address if address

  case agent.presence
  when :available
    bridged_agent_health_check agent

    @strategy << agent
    check_for_connections
  when :unavailable
    @strategy.delete agent
  end
  agent
end

#return_agent!(*args) ⇒ Object

Marks an agent as available to take a call.

See Also:



225
226
227
# File 'lib/electric_slide/call_queue.rb', line 225

def return_agent!(*args)
  return_agent(*args) || abort(MissingAgentError.new('Agent is not in the queue. Unable to return agent.'))
end

#update(attrs) ⇒ Object



86
87
88
89
90
91
# File 'lib/electric_slide/call_queue.rb', line 86

def update(attrs)
  attrs.each do |attr, value|
    setter = "#{attr}="
    send setter, value if respond_to?(setter)
  end unless attrs.nil?
end

#update_agent(agent, agent_attrs) ⇒ Object

Updates a queued agent’s attributes



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/electric_slide/call_queue.rb', line 180

def update_agent(agent, agent_attrs)
  abort ArgumentError.new('Agent must not be `nil`') unless agent
  unless get_agent(agent.id)
    abort MissingAgentError.new('Agent is not in the queue')
  end

  # check if the agent is allowed to have the given set of attributes using
  # a dupe, to preserve the state of the original in case of failure
  agent.dup.tap do |double_agent|
    double_agent.update agent_attrs
    accept_agent! double_agent
  end

  agent.update agent_attrs
  return_agent agent, agent.presence
end