call_center 0.0.9 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - ree
data/README.md CHANGED
@@ -12,62 +12,66 @@ Call Center streamlines the process of defining multi-party call workflows in yo
12
12
  Usage
13
13
  -----
14
14
 
15
- class Call
16
- include CallCenter
15
+ ```ruby
16
+ class Call
17
+ include CallCenter
17
18
 
18
- call_flow :state, :intial => :incoming do
19
- actor :customer do |call, event|
20
- "/voice/calls/flow?event=#{event}&actor=customer&call_id=#{call.id}"
21
- end
22
-
23
- state :incoming do
24
- response do |x|
25
- x.Gather :numDigits => '1', :action => customer(:wants_voicemail) do
26
- x.Say "Hello World"
27
- x.Play some_nice_music, :loop => 100
28
- end
29
- # <?xml version="1.0" encoding="UTF-8" ?>
30
- # <Response>
31
- # <Gather numDigits="1" action="/voice/calls/flow?event=wants_voicemail&actor=customer&call_id=5000">
32
- # <Say>Hello World</Say>
33
- # <Play loop="100">http://some.nice.music.com/1.mp3</Play>
34
- # </Gather>
35
- # </Response>
36
- end
37
-
38
- event :called, :to => :routing, :if => :agents_available?
39
- event :called, :to => :voicemail
40
- event :wants_voicemail, :to => :voicemail
41
- event :customer_hangs_up, :to => :cancelled
42
- end
19
+ call_flow :state, :intial => :incoming do
20
+ actor :customer do |call, event|
21
+ "/voice/calls/flow?event=#{event}&actor=customer&call_id=#{call.id}"
22
+ end
43
23
 
44
- state :voicemail do
45
- response do |x|
46
- x.Say "Please leave a message"
47
- x.Record(:action => customer(:voicemail_complete))
48
- # <?xml version="1.0" encoding="UTF-8" ?>
49
- # <Response>
50
- # <Say>Please leave a message</Say>
51
- # <Record action="/voice/calls/flow?event=voicemail_complete&actor=customer&call_id=5000"/>
52
- # </Response>
53
- end
54
-
55
- event :voicemail_complete, :to => :voicemail_completed
56
- event :customer_hangs_up, :to => :cancelled
24
+ state :incoming do
25
+ response do |x|
26
+ x.Gather :numDigits => '1', :action => customer(:wants_voicemail) do
27
+ x.Say "Hello World"
28
+ x.Play some_nice_music, :loop => 100
57
29
  end
30
+ # <?xml version="1.0" encoding="UTF-8" ?>
31
+ # <Response>
32
+ # <Gather numDigits="1" action="/voice/calls/flow?event=wants_voicemail&actor=customer&call_id=5000">
33
+ # <Say>Hello World</Say>
34
+ # <Play loop="100">http://some.nice.music.com/1.mp3</Play>
35
+ # </Gather>
36
+ # </Response>
37
+ end
58
38
 
59
- state :routing do
39
+ event :called, :to => :routing, :if => :agents_available?
40
+ event :called, :to => :voicemail
41
+ event :wants_voicemail, :to => :voicemail
42
+ event :customer_hangs_up, :to => :cancelled
43
+ end
60
44
 
61
- end
45
+ state :voicemail do
46
+ response do |x|
47
+ x.Say "Please leave a message"
48
+ x.Record(:action => customer(:voicemail_complete))
49
+ # <?xml version="1.0" encoding="UTF-8" ?>
50
+ # <Response>
51
+ # <Say>Please leave a message</Say>
52
+ # <Record action="/voice/calls/flow?event=voicemail_complete&actor=customer&call_id=5000"/>
53
+ # </Response>
62
54
  end
55
+
56
+ event :voicemail_complete, :to => :voicemail_completed
57
+ event :customer_hangs_up, :to => :cancelled
63
58
  end
64
59
 
60
+ state :routing do
61
+
62
+ end
63
+ end
64
+ end
65
+ ```
66
+
65
67
  Benefits of **CallCenter** is that it's backed by [state_machine](https://github.com/pluginaweek/state_machine). Which means you can interact with events the same you do in `state_machine`.
66
68
 
67
- @call.called!
68
- @call.wants_voicemail!
69
- @call.routing?
70
- @call.render # See Rendering
69
+ ```ruby
70
+ @call.called!
71
+ @call.wants_voicemail!
72
+ @call.routing?
73
+ @call.render # See Rendering
74
+ ```
71
75
 
72
76
  Flow
73
77
  ----
@@ -91,9 +95,11 @@ In order to DRY up the callbacks, it is best to have use standardized callback U
91
95
 
92
96
  By handling this in your controller, you can immediately retrieve the **Call** from persistence, run an event on the call, and return the rendered TwiML. Here's an example:
93
97
 
94
- def flow
95
- render :xml => @call.run(params[:event])
96
- end
98
+ ```ruby
99
+ def flow
100
+ render :xml => @call.run(params[:event])
101
+ end
102
+ ```
97
103
 
98
104
  For an in-depth example, take a look at [call_roulette](https://github.com/zendesk/call_roulette).
99
105
 
@@ -102,48 +108,56 @@ Rendering
102
108
 
103
109
  Rendering is your way of interacting with Twilio. Thus, it provides two facilities: access to an XML builder and access to your call.
104
110
 
105
- state :sales do
106
- response do |xml_builder, the_call|
107
- xml_builder.Say "This is #{the_call.agent.name}!" # Or agent.name, you can access he call implicitly
108
- end
109
- end
111
+ ```ruby
112
+ state :sales do
113
+ response do |xml_builder, the_call|
114
+ xml_builder.Say "This is #{the_call.agent.name}!" # Or agent.name, you can access he call implicitly
115
+ end
116
+ end
117
+ ```
110
118
 
111
119
  Renders with `@call.render` if the current state is :sales:
112
120
 
113
- <?xml version="1.0" encoding="UTF-8"?>
114
- <Response>
115
- <Say>This is Henry!</Say>
116
- </Response>
121
+ ```xml
122
+ <?xml version="1.0" encoding="UTF-8"?>
123
+ <Response>
124
+ <Say>This is Henry!</Say>
125
+ </Response>
126
+ ```
117
127
 
118
128
  Callbacks
119
129
  ---------
120
130
 
121
131
  You have control over what you want to happen before/after state transitions:
122
132
 
123
- state :voicemail do
124
- before(:always) { # Invokes before any transition }
125
- before(:always, :uniq => true) { # Invokes before transitions to a different state }
133
+ ```ruby
134
+ state :voicemail do
135
+ before(:always) { # Invokes before any transition }
136
+ before(:always, :uniq => true) { # Invokes before transitions to a different state }
126
137
 
127
- after(:always) { # Invokes after any transition }
128
- after(:success) { # Invokes after any successful transition }
129
- after(:failure) { # Invokes after any failed transition (those not covered in your call flow) }
138
+ after(:always) { # Invokes after any transition }
139
+ after(:success) { # Invokes after any successful transition }
140
+ after(:failure) { # Invokes after any failed transition (those not covered in your call flow) }
130
141
 
131
- after(:always, :uniq => true) { # Invokes after any transition to a different state }
132
- after(:success, :uniq => true) { # Successful unique transitions }
133
- after(:failure, :uniq => true) { # Failed unique transitions }
134
- end
142
+ after(:always, :uniq => true) { # Invokes after any transition to a different state }
143
+ after(:success, :uniq => true) { # Successful unique transitions }
144
+ after(:failure, :uniq => true) { # Failed unique transitions }
145
+ end
146
+ ```
135
147
 
136
148
  For example,
137
149
 
138
- state :voicemail do
139
- before(:always) { log_start_event }
150
+ ```ruby
151
+ state :voicemail do
152
+ before(:always) { log_start_event }
140
153
 
141
- after(:always) { log_end_event }
142
- after(:failure) { notify_airbrake }
154
+ after(:always) { log_end_event }
155
+ after(:failure) { notify_airbrake }
143
156
 
144
- after(:success, :uniq => true) { notify_browser }
145
- after(:failure, :uniq => true) { notify_cleanup_browser }
146
- end
157
+ after(:success, :uniq => true) { notify_browser }
158
+ after(:failure, :uniq => true) { notify_cleanup_browser }
159
+ end
160
+ ```
147
161
 
148
162
  Motivation
149
163
  ----------
@@ -173,9 +187,11 @@ Tools
173
187
 
174
188
  Should you be interested in what your call center workflow looks like, you can draw.
175
189
 
176
- Call.state_machines[:status].draw(:font => 'Helvetica Neue')
177
- # OR
178
- @call.draw_call_flow(:font => 'Helvetica Neue')
190
+ ```ruby
191
+ Call.state_machines[:status].draw(:font => 'Helvetica Neue')
192
+ # OR
193
+ @call.draw_call_flow(:font => 'Helvetica Neue')
194
+ ```
179
195
 
180
196
  Future
181
197
  ------
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.9
1
+ 0.1.0
data/call_center.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{call_center}
8
- s.version = "0.0.9"
8
+ s.version = "0.1.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Henry Hsu"]
12
- s.date = %q{2011-11-09}
12
+ s.date = %q{2012-06-04}
13
13
  s.description = %q{Support for describing call center workflows}
14
14
  s.email = %q{hhsu@zendesk.com}
15
15
  s.extra_rdoc_files = [
@@ -19,6 +19,7 @@ Gem::Specification.new do |s|
19
19
  s.files = [
20
20
  ".document",
21
21
  ".rvmrc",
22
+ ".travis.yml",
22
23
  "Gemfile",
23
24
  "Gemfile.lock",
24
25
  "Guardfile",
@@ -30,6 +31,7 @@ Gem::Specification.new do |s|
30
31
  "call_center.gemspec",
31
32
  "init.rb",
32
33
  "lib/call_center.rb",
34
+ "lib/call_center/conditional_stack.rb",
33
35
  "lib/call_center/core_ext/object_instance_exec.rb",
34
36
  "lib/call_center/deferred_callbacks.rb",
35
37
  "lib/call_center/flow_callback.rb",
@@ -42,6 +44,7 @@ Gem::Specification.new do |s|
42
44
  "test/call_center_test.rb",
43
45
  "test/core_ext_test.rb",
44
46
  "test/examples/call.rb",
47
+ "test/examples/dynamic_transition_call.rb",
45
48
  "test/examples/legacy_call.rb",
46
49
  "test/examples/multiple_flow_call.rb",
47
50
  "test/examples/non_standard_call.rb",
data/lib/call_center.rb CHANGED
@@ -3,6 +3,7 @@ require 'state_machine'
3
3
  require 'call_center/state_machine_ext'
4
4
  require 'call_center/flow_callback'
5
5
  require 'call_center/deferred_callbacks'
6
+ require 'call_center/conditional_stack'
6
7
 
7
8
  module CallCenter
8
9
  def self.included(base)
@@ -0,0 +1,51 @@
1
+ module CallCenter
2
+ class ConditionalStack
3
+ def initialize
4
+ @stack = []
5
+ end
6
+
7
+ def <<(obj)
8
+ @stack << obj
9
+ end
10
+
11
+ def pop
12
+ @stack.pop
13
+ end
14
+
15
+ def any?
16
+ @stack.any?
17
+ end
18
+
19
+ def inject(options)
20
+ current_stack = @stack.dup
21
+ options.merge(:if => lambda { |model|
22
+ current_stack.map { |conditional| conditional.evaluate(model) }.all?
23
+ })
24
+ end
25
+
26
+ class Conditional
27
+ attr_reader :name
28
+
29
+ def initialize(name)
30
+ @name = name
31
+ end
32
+
33
+ def evaluate(model)
34
+ result = model.send(@name)
35
+ if? ? result : !result
36
+ end
37
+ end
38
+
39
+ class IfConditional < Conditional
40
+ def if?
41
+ true
42
+ end
43
+ end
44
+
45
+ class UnlessConditional < Conditional
46
+ def if?
47
+ false
48
+ end
49
+ end
50
+ end
51
+ end
@@ -65,6 +65,8 @@ end
65
65
 
66
66
  # Extension for StateMachine::AlternateMachine to provide render blocks inside a state definition
67
67
  class StateMachine::AlternateMachine
68
+ attr_accessor :flow_stacks
69
+
68
70
  def response(state_name = nil, &blk)
69
71
  if @from_state
70
72
  @queued_sends << [[:response, @from_state], blk]
@@ -92,4 +94,37 @@ class StateMachine::AlternateMachine
92
94
  end
93
95
  @queued_sends << [[:flow_actors, name], blk]
94
96
  end
97
+
98
+ def event_with_blocks(*args, &blk)
99
+ options = args.extract_options!
100
+
101
+ if flow_stacks && flow_stacks.any?
102
+ options = flow_stacks.inject(options)
103
+ end
104
+
105
+ event_without_blocks(*args.push(options), &blk)
106
+ end
107
+
108
+ alias_method :event_without_blocks, :event
109
+ alias_method :event, :event_with_blocks
110
+
111
+ def flow_if(conditional, &blk)
112
+ self.flow_stacks ||= CallCenter::ConditionalStack.new
113
+ begin
114
+ self.flow_stacks << CallCenter::ConditionalStack::IfConditional.new(conditional)
115
+ yield if block_given?
116
+ ensure
117
+ self.flow_stacks.pop
118
+ end
119
+ end
120
+
121
+ def flow_unless(conditional, &blk)
122
+ self.flow_stacks ||= CallCenter::ConditionalStack.new
123
+ begin
124
+ self.flow_stacks << CallCenter::ConditionalStack::UnlessConditional.new(conditional)
125
+ yield if block_given?
126
+ ensure
127
+ self.flow_stacks.pop
128
+ end
129
+ end
95
130
  end
@@ -6,10 +6,92 @@ require 'test/examples/legacy_call'
6
6
  require 'test/examples/call'
7
7
  require 'test/examples/non_standard_call'
8
8
  require 'test/examples/multiple_flow_call'
9
+ require 'test/examples/dynamic_transition_call'
9
10
 
10
11
  class CallCenterTest < Test::Unit::TestCase
11
12
  include CallCenter::Test::DSL
12
13
 
14
+ context "dynamic transition workflow" do
15
+ setup do
16
+ @call = DynamicTransitionCall.new
17
+ @call.stubs(:notify)
18
+ end
19
+
20
+ context "agents available on phone" do
21
+ setup do
22
+ @call.stubs(:agents_available?).returns(true)
23
+ @call.stubs(:via_phone?).returns(true)
24
+ end
25
+
26
+ should "transition to routing on phone" do
27
+ @call.incoming_call!
28
+ assert_equal 'routing_on_phone', @call.state
29
+ end
30
+ end
31
+
32
+ context "agents available on client" do
33
+ setup do
34
+ @call.stubs(:agents_available?).returns(true)
35
+ @call.stubs(:via_phone?).returns(false)
36
+ end
37
+
38
+ should "transition to routing on client" do
39
+ @call.incoming_call!
40
+ assert_equal 'routing_on_client', @call.state
41
+ end
42
+ end
43
+
44
+ context "no agents available" do
45
+ setup do
46
+ @call.stubs(:agents_available?).returns(false)
47
+ @call.stubs(:via_phone?).returns(true)
48
+ @call.stubs(:voicemail_full?).returns(false)
49
+ end
50
+
51
+ should "transition to voicemail" do
52
+ @call.incoming_call!
53
+ assert_equal 'voicemail', @call.state
54
+ end
55
+ end
56
+
57
+ context "no agents available when voicemail is full" do
58
+ setup do
59
+ @call.stubs(:agents_available?).returns(false)
60
+ @call.stubs(:via_phone?).returns(false) # Doesn't matter
61
+ @call.stubs(:voicemail_full?).returns(true)
62
+ end
63
+
64
+ should "transition to voicemail" do
65
+ @call.incoming_call!
66
+ assert_equal 'cancelled', @call.state
67
+ end
68
+ end
69
+
70
+ context "out of area" do
71
+ setup do
72
+ @call.state = 'routing_on_client'
73
+ @call.stubs(:out_of_area?).returns(true)
74
+ end
75
+
76
+ should "transition to cancelled" do
77
+ @call.picks_up!
78
+ assert_equal 'cancelled', @call.state
79
+ end
80
+ end
81
+
82
+ context "not out of area" do
83
+ setup do
84
+ @call.state = 'routing_on_client'
85
+ @call.stubs(:out_of_area?).returns(false)
86
+ end
87
+
88
+ should "transition to in_conference" do
89
+ @call.picks_up!
90
+ assert_equal 'in_conference', @call.state
91
+ end
92
+ end
93
+ end
94
+
13
95
  [:call, :legacy_call].each do |call_type|
14
96
  context "#{call_type.to_s.gsub('_', ' ')} workflow" do
15
97
  setup do
@@ -0,0 +1,41 @@
1
+ class DynamicTransitionCall
2
+ include CallCenter
3
+ include CommonCallMethods
4
+
5
+ call_flow :state, :initial => :initial do
6
+ state :initial do
7
+ response do |x|
8
+ x.Say "Hello World"
9
+ end
10
+
11
+ flow_if :agents_available? do
12
+ flow_if :via_phone? do
13
+ event :incoming_call, :to => :routing_on_phone
14
+ end
15
+
16
+ flow_unless :via_phone? do
17
+ event :incoming_call, :to => :routing_on_client
18
+ end
19
+ end
20
+
21
+ flow_unless :agents_available? do
22
+ flow_unless :voicemail_full? do
23
+ event :incoming_call, :to => :voicemail
24
+ end
25
+
26
+ flow_if :voicemail_full? do
27
+ event :incoming_call, :to => :cancelled
28
+ end
29
+ end
30
+ end
31
+
32
+ state :routing_on_client do
33
+ flow_if :out_of_area? do
34
+ event :picks_up, :to => :cancelled
35
+ end
36
+ flow_unless :out_of_area? do
37
+ event :picks_up, :to => :in_conference
38
+ end
39
+ end
40
+ end
41
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: call_center
3
3
  version: !ruby/object:Gem::Version
4
- hash: 13
4
+ hash: 27
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
+ - 1
8
9
  - 0
9
- - 9
10
- version: 0.0.9
10
+ version: 0.1.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Henry Hsu
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-11-09 00:00:00 -08:00
18
+ date: 2012-06-04 00:00:00 -07:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -264,6 +264,7 @@ extra_rdoc_files:
264
264
  files:
265
265
  - .document
266
266
  - .rvmrc
267
+ - .travis.yml
267
268
  - Gemfile
268
269
  - Gemfile.lock
269
270
  - Guardfile
@@ -275,6 +276,7 @@ files:
275
276
  - call_center.gemspec
276
277
  - init.rb
277
278
  - lib/call_center.rb
279
+ - lib/call_center/conditional_stack.rb
278
280
  - lib/call_center/core_ext/object_instance_exec.rb
279
281
  - lib/call_center/deferred_callbacks.rb
280
282
  - lib/call_center/flow_callback.rb
@@ -287,6 +289,7 @@ files:
287
289
  - test/call_center_test.rb
288
290
  - test/core_ext_test.rb
289
291
  - test/examples/call.rb
292
+ - test/examples/dynamic_transition_call.rb
290
293
  - test/examples/legacy_call.rb
291
294
  - test/examples/multiple_flow_call.rb
292
295
  - test/examples/non_standard_call.rb