Skip to content

Commit

Permalink
Merge branch 'release/0.2.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
benlangfeld committed Jul 24, 2015
2 parents bb3b1b3 + f7d3f51 commit 9faba1e
Show file tree
Hide file tree
Showing 25 changed files with 943 additions and 212 deletions.
1 change: 1 addition & 0 deletions .rspec
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--color
3 changes: 2 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
source :rubygems
source 'https://rubygems.org'
gemspec

9 changes: 9 additions & 0 deletions Guardfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# encoding: utf-8

group 'rspec' do
guard 'rspec', cmd: 'bundle exec rspec' do
watch(%r{^spec/.+_spec\.rb$})
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
watch('spec/spec_helper.rb') { "spec/" }
end
end
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Copyright (c) 2011 The Adhearsion Foundation
Copyright (c) 2011 The Adhearsion Foundation, Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

Expand Down
153 changes: 153 additions & 0 deletions README.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
Electric Slide - Simple Call Distribution for Adhearsion
====================================================================

This library implements a simple FIFO (First-In, First-Out) call queue for Adhearsion.

To ensure proper operation, a few things are assumed:

* Agents will only be logged into a single queue at a time
If you have two types of agents (say "support" and "sales") then you should have two queues, each with their own pool of agents
* Agent authentication will happen before entering the queue - it is not the queue's concern
* The strategy for callers is FIFO: the caller who has been waiting the longest is the next to get an agent
* Queues will be implemented as a Celluloid Actor, which should protect the call selection strategies against race conditions
* There are two ways to connect an agent:
- If the Agent object provides an `address` attribute, and the queue's `connection_type` is set to `call`, then the queue will call the agent when a caller is waiting
- If the Agent object provides a `call` attribute, and the queue's `connection_type` is set to `bridge`, then the call queue will bridge the agent to the caller. In this mode, the agent hanging up will log him out of the queue

TODO:
* Example for using Matrioska to offer Agents and Callers interactivity while waiting
* How to handle MOH

## WARNING!

While you can have ElectricSlide keep track of custom queues, it is recommended to use the built-in CallQueue object.

The authors of ElectricSlide recommend NOT to subclass, monkeypatch, or otherwise alter the CallQueue implementation, as the likelihood of creating subtle race conditions is high.

Example Queue
-------------

```ruby
my_queue = ElectricSlide.create :my_queue, ElectricSlide::CallQueue

# Another way to get a handle on a queue
ElectricSlide.create :my_queue
my_queue = ElectricSlide.get_queue :my_queue
```


Example CallController for Queued Call
--------------------------------------

```ruby
class EnterTheQueue < Adhearsion::CallController
def run
answer

# Play music-on-hold to the caller until joined to an agent
player = play 'http://moh-server.example.com/stream.mp3', repeat_times: 0
call.on_joined do
player.stop!
end

ElectricSlide.get_queue(:my_queue).enqueue call

# The controller will exit, but the call will remain up
# The call will automatically hang up after speaking to an agent
call.auto_hangup = false
end
end
```


Adding an Agent to the Queue
----------------------------

ElectricSlide expects to be given a objects that quack like an agent. You can use the built-in `ElectricSlide::Agent` class, or you can provide your own.

To add an agent who will receive calls whenever a call is enqueued, do something like this:

```ruby
agent = ElectricSlide::Agent.new id: 1, address: 'sip:[email protected]', presence: :available
ElectricSlide.get_queue(:my_queue).add_agent agent
```

To inform the queue that the agent is no longer available you *must* use the ElectricSlide queue interface. /Do not attempt to alter agent objects directly!/

```ruby
ElectricSlide.update_agent 1, presence: offline
```

If it is more convenient, you may also pass `#update_agent` an Agent-like object:

```ruby
options = {
id: 1,
address: 'sip:[email protected]',
presence: offline
}
agent = ElectricSlide::Agent.new options
ElectricSlide.update_agent 1, agent
```

Switching connection types
--------------------------

ElectricSlide provides two methods for connecting callers to agents:
- `:call`: (default) If the Agent object provides an `address` attribute, and the queue's `connection_type` is set to `call`, then the queue will call the agent when a caller is waiting
- `:bridge`: If the Agent object provides a `call` attribute, and the queue's `connection_type` is set to `bridge`, then the call queue will bridge the agent to the caller. In this mode, the agent hanging up will log him out of the queue

To select the connection type, specify it when creating the queue:

```ruby
ElectricSlide.create_queue :my_queue, ElectricSlide::CallQueue, connection_type: :bridge
```

Selecting an Agent distribution strategy
----------------------------------------

Different use-cases have different requirements for selecting the next agent to take a call. ElectricSlide provides two strategies which may be used. You are also welcome to create your own distribution strategy by implementing the same interface as described in `ElectricSlide::AgentStrategy::LongestIdle`.

To select an agent strategy, specify it when creating the queue:

```ruby
ElectricSlide.create_queue :my_queue, ElectricSlide::CallQueue, agent_strategy: ElectricSlide::AgentStrategy::LongestIdle
```

Two strategies are provided out-of-the-box:

* `ElectricSlide::AgentStrategy::LongestIdle` selects the agent that has been idle for the longest amount of time.
* `ElectricSlide::AgentStrategy::FixedPriority` selects the agent with the lowest numeric priority first. In the event that more than one agent is available at a given priority, then the agent that has been idle the longest at the lowest numeric priority is selected.

Custom Agent Behavior
----------------------------

If you need custom functionality to occur whenever an Agent is selected to take a call, you can use the callbacks on the Agent object:

* `on_connect`
* `on_disconnect`

Confirmation Controllers
------------------------

In case you need to execute a confirmation controller on the call that is placed to the agent, such as "Press 1 to accept the call", you currently need to pass in the confirmation class name and the call object as metadata in the `call_options_for` callback in your `ElectricSlide::Agent` subclass.

```ruby
# an example from the Agent subclass
def dial_options_for(queue, queued_call)
{
from: caller_digits(queued_call.from),
timeout: on_pstn? ? APP_CONFIG.agent_timeout * 3 : APP_CONFIG.agent_timeout,
confirm: MyConfirmationController,
confirm_metadata: {caller: queued_call, agent: self},
}
end
```

You then need to handle the join in your confirmation controller, using for example:

```ruby
call.join metadata[:caller] if confirm!
```

where `confirm!` is your logic for deciding if you want the call to be connected or not. Hanging up during the confirmation controller or letting it finish without any action will result in the call being sent to the next agent.
15 changes: 15 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env ruby
# encoding: utf-8
# -*- ruby -*-
ENV['RUBY_FLAGS'] = "-I#{%w(lib ext bin spec).join(File::PATH_SEPARATOR)}"

require 'rubygems'
require 'bundler/gem_tasks'
require 'bundler/setup'

require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new

task ci: ['ci:setup:rspec', :spec]
task default: :spec

32 changes: 0 additions & 32 deletions ahn_acd.gemspec

This file was deleted.

36 changes: 36 additions & 0 deletions electric_slide.gemspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# encoding: utf-8
$:.push File.expand_path("../lib", __FILE__)
require 'electric_slide/version'
require 'date'

Gem::Specification.new do |s|
s.name = "electric_slide"
s.version = ElectricSlide::VERSION

s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Ben Klang"]

s.date = Date.today.to_s
s.description = "Automatic Call Distributor (ACD) for Adhearsion. Currently implements only Round Robin distribution strategies."
s.email = "dev&adhearsion.com"

s.files = `git ls-files`.split("\n")

s.has_rdoc = true
s.homepage = "http://github.com/adhearsion/electric_slide"
s.require_paths = ["lib"]
s.rubygems_version = "1.2.0"
s.summary = "Automatic Call Distributor for Adhearsion"

s.add_runtime_dependency 'adhearsion'
s.add_runtime_dependency 'countdownlatch'
s.add_runtime_dependency 'activesupport'
s.add_development_dependency 'rspec', ['>= 2.5.0']
s.add_development_dependency 'ci_reporter'
s.add_development_dependency 'guard'
s.add_development_dependency 'guard-rspec'
s.add_development_dependency 'simplecov'
s.add_development_dependency 'simplecov-rcov'

s.specification_version = 2
end
83 changes: 0 additions & 83 deletions lib/ahn_acd.rb

This file was deleted.

17 changes: 0 additions & 17 deletions lib/ahn_acd/queue_strategy.rb

This file was deleted.

Loading

0 comments on commit 9faba1e

Please sign in to comment.