Permalink
Please sign in to comment.
Showing
with
850 additions
and 0 deletions.
- +5 −0 .document
- +48 −0 .gitignore
- +5 −0 .infinity_test
- +1 −0 .rvmrc
- +15 −0 Gemfile
- +40 −0 Gemfile.lock
- +20 −0 LICENSE.txt
- +133 −0 README.md
- +53 −0 Rakefile
- +1 −0 VERSION
- +64 −0 call_center.gemspec
- +1 −0 init.rb
- +60 −0 lib/call_center.rb
- +21 −0 lib/call_center/core_ext/object_instance_exec.rb
- +61 −0 lib/call_center/evaluator/workflow_evaluator.rb
- +51 −0 lib/call_center/evaluator/workflow_reader.rb
- +18 −0 lib/call_center/evaluator/workflow_transition.rb
- +128 −0 test/call_center_test.rb
- +18 −0 test/core_ext_test.rb
- +42 −0 test/examples/call.rb
- +26 −0 test/examples/legacy_call.rb
- +39 −0 test/helper.rb
@@ -0,0 +1,5 @@ | ||
+lib/**/*.rb | ||
+bin/* | ||
+- | ||
+features/**/*.feature | ||
+LICENSE.txt |
48
.gitignore
@@ -0,0 +1,48 @@ | ||
+# rcov generated | ||
+coverage | ||
+ | ||
+# rdoc generated | ||
+rdoc | ||
+ | ||
+# yard generated | ||
+doc | ||
+.yardoc | ||
+ | ||
+# bundler | ||
+.bundle | ||
+ | ||
+# jeweler generated | ||
+pkg | ||
+ | ||
+# Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore: | ||
+# | ||
+# * Create a file at ~/.gitignore | ||
+# * Include files you want ignored | ||
+# * Run: git config --global core.excludesfile ~/.gitignore | ||
+# | ||
+# After doing this, these files will be ignored in all your git projects, | ||
+# saving you from having to 'pollute' every project you touch with them | ||
+# | ||
+# Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line) | ||
+# | ||
+# For MacOS: | ||
+# | ||
+#.DS_Store | ||
+ | ||
+# For TextMate | ||
+#*.tmproj | ||
+#tmtags | ||
+ | ||
+# For emacs: | ||
+#*~ | ||
+#\#* | ||
+#.\#* | ||
+ | ||
+# For vim: | ||
+#*.swp | ||
+ | ||
+# For redcar: | ||
+#.redcar | ||
+ | ||
+# For rubinius: | ||
+#*.rbc |
@@ -0,0 +1,5 @@ | ||
+infinity_test do | ||
+ before_run do | ||
+ clear :terminal | ||
+ end | ||
+end |
1
.rvmrc
@@ -0,0 +1 @@ | ||
+rvm use ree-1.8.7-2011.03@twilio_flow |
15
Gemfile
@@ -0,0 +1,15 @@ | ||
+source 'http://rubygems.org' | ||
+ | ||
+group :development do | ||
+ gem 'shoulda', '>= 0' | ||
+ gem 'bundler', '~> 1.0.0' | ||
+ gem 'jeweler', '~> 1.6.4' | ||
+ gem 'rcov', '>= 0' | ||
+ gem 'test-unit', :require => 'test/unit' | ||
+ gem 'infinity_test' | ||
+ gem 'actionpack', '~> 2.3.10' | ||
+ gem 'mocha' | ||
+end | ||
+ | ||
+gem 'builder' | ||
+gem 'state_machine' |
40
Gemfile.lock
@@ -0,0 +1,40 @@ | ||
+GEM | ||
+ remote: http://rubygems.org/ | ||
+ specs: | ||
+ actionpack (2.3.12) | ||
+ activesupport (= 2.3.12) | ||
+ rack (~> 1.1.0) | ||
+ activesupport (2.3.12) | ||
+ builder (3.0.0) | ||
+ git (1.2.5) | ||
+ infinity_test (1.0.3) | ||
+ notifiers (>= 1.1.0) | ||
+ watchr (>= 0.7) | ||
+ jeweler (1.6.4) | ||
+ bundler (~> 1.0) | ||
+ git (>= 1.2.5) | ||
+ rake | ||
+ mocha (0.9.12) | ||
+ notifiers (1.1.0) | ||
+ rack (1.1.2) | ||
+ rake (0.9.2) | ||
+ rcov (0.9.9) | ||
+ shoulda (2.11.3) | ||
+ state_machine (1.0.1) | ||
+ test-unit (2.3.0) | ||
+ watchr (0.7) | ||
+ | ||
+PLATFORMS | ||
+ ruby | ||
+ | ||
+DEPENDENCIES | ||
+ actionpack (~> 2.3.10) | ||
+ builder | ||
+ bundler (~> 1.0.0) | ||
+ infinity_test | ||
+ jeweler (~> 1.6.4) | ||
+ mocha | ||
+ rcov | ||
+ shoulda | ||
+ state_machine | ||
+ test-unit |
20
LICENSE.txt
@@ -0,0 +1,20 @@ | ||
+Copyright (c) 2011 Henry Hsu | ||
+ | ||
+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: | ||
+ | ||
+The above copyright notice and this permission notice shall be | ||
+included in all copies or substantial portions of the Software. | ||
+ | ||
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | ||
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | ||
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | ||
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
133
README.md
@@ -0,0 +1,133 @@ | ||
+Call Center | ||
+=========== | ||
+ | ||
+Support for defining call center workflows. | ||
+ | ||
+Overview | ||
+-------- | ||
+Call Center streamlines the process of defining multi-party call workflows in your application. Particularly, with [Twilio](http://www.twilio.com/docs) in mind. | ||
+ | ||
+[Twilio](http://www.twilio.com/docs) provides a two-part API for managing phone calls, and is mostly driven by callbacks. Call Center DRYs up the application logic dealing with a callback driven API so you can focus on the business logic of your call center. | ||
+ | ||
+### Not DRY | ||
+Twilio requests your application to return [TwiML](http://www.twilio.com/docs/api/twiml/) that describes the call workflow. TwiML contains commands which Twilio then executes. It is essentially an application-to-application API, synonymous to making a REST call. | ||
+ | ||
+In the context of "[Skinny Controller, Fat Model](http://weblog.jamisbuck.org/2006/10/18/skinny-controller-fat-model)", outgoing REST calls for the function of business logic are not a view concern but a model concern. Therefore, so is TwiML. | ||
+ | ||
+Twilio supports callbacks URLs and redirects to URLs that also render TwiML as a method of modifying live calls. Incoming callbacks are handled by the controller first, but the response is still a model concern. | ||
+ | ||
+Terminology | ||
+----------- | ||
+ | ||
+* **Call** - An application resource of yours that encapsulates a phone call. Phone calls are then acted on: answered, transferred, declined, etc. | ||
+* **Event** - Is something that happens outside or inside your application in relation to a **Call**. Someone picks up, hangs up, presses a button, etc. | ||
+* **State** - Is the status a **Call** is in which is descriptive of what's happened so far and what are the next things that should happen. (e.g. a call on hold is waiting for the agent to return) | ||
+* **CallFlow** - Is a definition of the process a **Call** goes through. **Events** drive the flow between **States**. (e.g. a simple workflow is when noone answers the call, send the call to voicemail) | ||
+* **Render** - Is the ability of the **CallFlow** to return TwiML to bring the call into the **State** or modify the live call through a **Redirect**. | ||
+* **Redirect** - Is a way of modifying a live call outside of a TwiML response (e.g. background jobs) | ||
+ | ||
+Usage | ||
+----- | ||
+ | ||
+ class Call | ||
+ include CallCenter | ||
+ | ||
+ call_flow :state, :intial => :answered do | ||
+ state :answered do | ||
+ event :incoming_call, :to => :voicemail, :if => :not_during_business_hours? | ||
+ event :incoming_call, :to => :sales | ||
+ end | ||
+ | ||
+ state :voicemail do | ||
+ event :customer_hangs_up, :to => :voicemail_completed | ||
+ end | ||
+ | ||
+ on_render(:sales) do |call, x| | ||
+ x.Say "This is Sales!" | ||
+ end | ||
+ | ||
+ on_render(:voicemail) do |call, x| | ||
+ x.Say "Leave a voicemail!" | ||
+ end | ||
+ end | ||
+ end | ||
+ | ||
+Flow | ||
+---- | ||
+ | ||
+The general application flow for a **CallFlow** is like this: | ||
+ | ||
+1. An incoming call is posted to your application | ||
+ * You create a **Call** | ||
+ * You execute an initial event | ||
+ * You respond by rendering TwiML. Your TwiML contains callbacks to events or redirects | ||
+2. Something happens and Twilio posts an event to your application | ||
+ * You find the **Call** | ||
+ * You store any new information | ||
+ * You execute the posted event | ||
+ * You respond by rendering TwiML. Your TwiML contains callbacks to events or redirects | ||
+3. Repeat 2. | ||
+ | ||
+Rendering | ||
+--------- | ||
+ | ||
+Rendering is your way of interacting with Twilio. Thus, it provides two facilities: access to an XML builder and access to your call. | ||
+ | ||
+ on_render(:sales) do |the_call, xml_builder| | ||
+ xml_builder.Say "This is Sales!" | ||
+ | ||
+ the_call.flag! # You can access the call explicitly | ||
+ flag! # Or access it implicitly | ||
+ end | ||
+ | ||
+Renders: | ||
+ | ||
+ <?xml version="1.0" encoding="UTF-8"?> | ||
+ <Response> | ||
+ <Say>This is Sales!</Say> | ||
+ </Response> | ||
+ | ||
+Redirects | ||
+--------- | ||
+ | ||
+Redirects are a request made to the [Twilio REST API](http://www.twilio.com/docs/api/rest/) that points to a callback URL which returns TwiML to be executed on a call. It is up to you how you want to perform this (e.g. with your favority http libraries, or with [Twilio Libraries](http://www.twilio.com/docs/libraries/)). | ||
+ | ||
+Redirect to events look like this: | ||
+ | ||
+ ... | ||
+ call_flow :state, :intial => :answered do | ||
+ state :answered do | ||
+ ... | ||
+ end | ||
+ | ||
+ state :ending_call do | ||
+ event :end_call, :to => :ended_call | ||
+ end | ||
+ | ||
+ on_render(:ending_call) do | ||
+ redirect_and_end_call!(:status => 'completed') | ||
+ end | ||
+ end | ||
+ ... | ||
+ | ||
+For your **Call** to support this syntax, it must adhere to the following API: | ||
+ | ||
+ class Call | ||
+ def redirect_to(event, *args) | ||
+ # where: | ||
+ # event #=> :end_call | ||
+ # args #=> [:status => 'completed] | ||
+ @account.calls.get(self.sid).update({:url => "http://myapp.com/call_flow?event=#{event}"}) | ||
+ end | ||
+ end | ||
+ | ||
+Tools | ||
+----- | ||
+ | ||
+### Drawing ### | ||
+ | ||
+Benefits of **CallCenter** is that it's backed by [state_machine](https://github.com/pluginaweek/state_machine). Should you be interested in what your call center workflow looks like, you can draw. | ||
+ | ||
+ Call.state_machines[:status].draw(:font => 'Helvetica Neue') | ||
+ # OR | ||
+ @call.draw_call_flow(:font => 'Helvetica Neue') |
53
Rakefile
@@ -0,0 +1,53 @@ | ||
+# encoding: utf-8 | ||
+ | ||
+require 'rubygems' | ||
+require 'bundler' | ||
+begin | ||
+ Bundler.setup(:default, :development) | ||
+rescue Bundler::BundlerError => e | ||
+ $stderr.puts e.message | ||
+ $stderr.puts "Run `bundle install` to install missing gems" | ||
+ exit e.status_code | ||
+end | ||
+require 'rake' | ||
+ | ||
+require 'jeweler' | ||
+Jeweler::Tasks.new do |gem| | ||
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options | ||
+ gem.name = "call_center" | ||
+ gem.homepage = "http://github.com/zendesk/call_center" | ||
+ gem.license = "MIT" | ||
+ gem.summary = %Q{Support for describing call center workflows} | ||
+ gem.description = %Q{Support for describing call center workflows} | ||
+ gem.email = "hhsu@zendesk.com" | ||
+ gem.authors = ["Henry Hsu"] | ||
+ # dependencies defined in Gemfile | ||
+end | ||
+Jeweler::RubygemsDotOrgTasks.new | ||
+ | ||
+require 'rake/testtask' | ||
+Rake::TestTask.new(:test) do |test| | ||
+ test.libs << 'lib' << 'test' | ||
+ test.pattern = 'test/**/test_*.rb' | ||
+ test.verbose = true | ||
+end | ||
+ | ||
+require 'rcov/rcovtask' | ||
+Rcov::RcovTask.new do |test| | ||
+ test.libs << 'test' | ||
+ test.pattern = 'test/**/*_test.rb' | ||
+ test.verbose = true | ||
+ test.rcov_opts << '--exclude "gems/*"' | ||
+end | ||
+ | ||
+task :default => :test | ||
+ | ||
+require 'rake/rdoctask' | ||
+Rake::RDocTask.new do |rdoc| | ||
+ version = File.exist?('VERSION') ? File.read('VERSION') : "" | ||
+ | ||
+ rdoc.rdoc_dir = 'rdoc' | ||
+ rdoc.title = "twilio_flow #{version}" | ||
+ rdoc.rdoc_files.include('README*') | ||
+ rdoc.rdoc_files.include('lib/**/*.rb') | ||
+end |
1
VERSION
@@ -0,0 +1 @@ | ||
+0.0.1 |
@@ -0,0 +1,64 @@ | ||
+# Generated by jeweler | ||
+# DO NOT EDIT THIS FILE DIRECTLY | ||
+# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' | ||
+# -*- encoding: utf-8 -*- | ||
+ | ||
+Gem::Specification.new do |s| | ||
+ s.name = %q{call_center} | ||
+ s.version = "0.0.1" | ||
+ | ||
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= | ||
+ s.authors = ["Henry Hsu"] | ||
+ s.date = %q{2011-07-22} | ||
+ s.description = %q{Support for describing call center workflows} | ||
+ s.email = %q{hhsu@zendesk.com} | ||
+ s.extra_rdoc_files = [ | ||
+ "LICENSE.txt", | ||
+ "README.md" | ||
+ ] | ||
+ s.homepage = %q{http://github.com/zendesk/call_center} | ||
+ s.licenses = ["MIT"] | ||
+ s.require_paths = ["lib"] | ||
+ s.rubygems_version = %q{1.5.3} | ||
+ s.summary = %q{Support for describing call center workflows} | ||
+ | ||
+ if s.respond_to? :specification_version then | ||
+ s.specification_version = 3 | ||
+ | ||
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then | ||
+ s.add_runtime_dependency(%q<builder>, [">= 0"]) | ||
+ s.add_runtime_dependency(%q<state_machine>, [">= 0"]) | ||
+ s.add_development_dependency(%q<shoulda>, [">= 0"]) | ||
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"]) | ||
+ s.add_development_dependency(%q<jeweler>, ["~> 1.6.4"]) | ||
+ s.add_development_dependency(%q<rcov>, [">= 0"]) | ||
+ s.add_development_dependency(%q<test-unit>, [">= 0"]) | ||
+ s.add_development_dependency(%q<infinity_test>, [">= 0"]) | ||
+ s.add_development_dependency(%q<actionpack>, ["~> 2.3.10"]) | ||
+ s.add_development_dependency(%q<mocha>, [">= 0"]) | ||
+ else | ||
+ s.add_dependency(%q<builder>, [">= 0"]) | ||
+ s.add_dependency(%q<state_machine>, [">= 0"]) | ||
+ s.add_dependency(%q<shoulda>, [">= 0"]) | ||
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"]) | ||
+ s.add_dependency(%q<jeweler>, ["~> 1.6.4"]) | ||
+ s.add_dependency(%q<rcov>, [">= 0"]) | ||
+ s.add_dependency(%q<test-unit>, [">= 0"]) | ||
+ s.add_dependency(%q<infinity_test>, [">= 0"]) | ||
+ s.add_dependency(%q<actionpack>, ["~> 2.3.10"]) | ||
+ s.add_dependency(%q<mocha>, [">= 0"]) | ||
+ end | ||
+ else | ||
+ s.add_dependency(%q<builder>, [">= 0"]) | ||
+ s.add_dependency(%q<state_machine>, [">= 0"]) | ||
+ s.add_dependency(%q<shoulda>, [">= 0"]) | ||
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"]) | ||
+ s.add_dependency(%q<jeweler>, ["~> 1.6.4"]) | ||
+ s.add_dependency(%q<rcov>, [">= 0"]) | ||
+ s.add_dependency(%q<test-unit>, [">= 0"]) | ||
+ s.add_dependency(%q<infinity_test>, [">= 0"]) | ||
+ s.add_dependency(%q<actionpack>, ["~> 2.3.10"]) | ||
+ s.add_dependency(%q<mocha>, [">= 0"]) | ||
+ end | ||
+end | ||
+ |

Oops, something went wrong.
0 comments on commit
aeba7fa