call_center 0.0.9 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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