telephony 1.0.3

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.
Files changed (365) hide show
  1. checksums.yaml +15 -0
  2. data/README.md +105 -0
  3. data/Rakefile +39 -0
  4. data/app/assets/fonts/telephony/zest-telephony.eot +0 -0
  5. data/app/assets/fonts/telephony/zest-telephony.svg +73 -0
  6. data/app/assets/fonts/telephony/zest-telephony.ttf +0 -0
  7. data/app/assets/fonts/telephony/zest-telephony.woff +0 -0
  8. data/app/assets/images/telephony/backspace.png +0 -0
  9. data/app/assets/images/telephony/icon-spinner.gif +0 -0
  10. data/app/assets/javascripts/telephony/application.js +12 -0
  11. data/app/assets/javascripts/telephony/collections/agents.js +4 -0
  12. data/app/assets/javascripts/telephony/config.js.erb +11 -0
  13. data/app/assets/javascripts/telephony/models/agent.js +103 -0
  14. data/app/assets/javascripts/telephony/models/conversation.js +226 -0
  15. data/app/assets/javascripts/telephony/models/device.js +30 -0
  16. data/app/assets/javascripts/telephony/models/transfer.js +65 -0
  17. data/app/assets/javascripts/telephony/namespace.js +6 -0
  18. data/app/assets/javascripts/telephony/push.js +80 -0
  19. data/app/assets/javascripts/telephony/vendor/backbone.js +1159 -0
  20. data/app/assets/javascripts/telephony/vendor/chosen.jquery.js +1018 -0
  21. data/app/assets/javascripts/telephony/vendor/jquery.form.js +1117 -0
  22. data/app/assets/javascripts/telephony/vendor/pusher.js +1304 -0
  23. data/app/assets/javascripts/telephony/vendor/test_dependencies.js +18 -0
  24. data/app/assets/javascripts/telephony/vendor/underscore.js +839 -0
  25. data/app/assets/javascripts/telephony/views/agents_view.js +69 -0
  26. data/app/assets/javascripts/telephony/views/application_view.js +78 -0
  27. data/app/assets/javascripts/telephony/views/call_queue_view.js +21 -0
  28. data/app/assets/javascripts/telephony/views/conversation_buttons_view.js +15 -0
  29. data/app/assets/javascripts/telephony/views/conversation_view.js +322 -0
  30. data/app/assets/javascripts/telephony/views/status_view.js +36 -0
  31. data/app/assets/javascripts/telephony/views/transfer_view.js +87 -0
  32. data/app/assets/javascripts/telephony/views/twilio_client_view.js +117 -0
  33. data/app/assets/javascripts/telephony/views/widget_view.js +55 -0
  34. data/app/assets/javascripts/telephony/widget.js +5 -0
  35. data/app/assets/javascripts/templates/telephony/agents_view.jst.ejs +8 -0
  36. data/app/assets/javascripts/templates/telephony/call_queue_view.jst.ejs +2 -0
  37. data/app/assets/javascripts/templates/telephony/conversation_buttons_view.jst.ejs +23 -0
  38. data/app/assets/javascripts/templates/telephony/conversation_view.jst.ejs +10 -0
  39. data/app/assets/javascripts/templates/telephony/status_view.jst.ejs +4 -0
  40. data/app/assets/javascripts/templates/telephony/transfer_view.jst.ejs +38 -0
  41. data/app/assets/javascripts/templates/telephony/twilio_client_view.jst.ejs +10 -0
  42. data/app/assets/stylesheets/telephony/font.css +43 -0
  43. data/app/assets/stylesheets/telephony/reset.css.scss +10 -0
  44. data/app/assets/stylesheets/telephony/widget.css.scss +512 -0
  45. data/app/controllers/telephony/agents_controller.rb +46 -0
  46. data/app/controllers/telephony/application_controller.rb +4 -0
  47. data/app/controllers/telephony/call_centers_controller.rb +9 -0
  48. data/app/controllers/telephony/conversations_controller.rb +74 -0
  49. data/app/controllers/telephony/inbound/conversation_queues_controller.rb +21 -0
  50. data/app/controllers/telephony/playable_listeners_controller.rb +43 -0
  51. data/app/controllers/telephony/providers/twilio/application_controller.rb +26 -0
  52. data/app/controllers/telephony/providers/twilio/calls_controller.rb +130 -0
  53. data/app/controllers/telephony/providers/twilio/inbound_calls_controller.rb +54 -0
  54. data/app/controllers/telephony/providers/twilio/musics_controller.rb +12 -0
  55. data/app/controllers/telephony/providers/twilio/voicemails_controller.rb +29 -0
  56. data/app/controllers/telephony/signals/agents/presences_controller.rb +64 -0
  57. data/app/controllers/telephony/transfers_controller.rb +18 -0
  58. data/app/controllers/telephony/twilio_client_controller.rb +37 -0
  59. data/app/controllers/telephony/voicemails_controller.rb +13 -0
  60. data/app/controllers/telephony/widget_controller.rb +10 -0
  61. data/app/helpers/telephony/application_helper.rb +4 -0
  62. data/app/helpers/telephony/calls_helper.rb +13 -0
  63. data/app/models/telephony/agent.rb +186 -0
  64. data/app/models/telephony/agent_state_machine.rb +54 -0
  65. data/app/models/telephony/base.rb +7 -0
  66. data/app/models/telephony/blacklisted_number.rb +5 -0
  67. data/app/models/telephony/call.rb +94 -0
  68. data/app/models/telephony/call_center.rb +26 -0
  69. data/app/models/telephony/call_state_machine.rb +98 -0
  70. data/app/models/telephony/conversation.rb +273 -0
  71. data/app/models/telephony/conversation_state_machine.rb +109 -0
  72. data/app/models/telephony/conversations_presenter.rb +83 -0
  73. data/app/models/telephony/events.rb +6 -0
  74. data/app/models/telephony/events/answer.rb +6 -0
  75. data/app/models/telephony/events/base.rb +118 -0
  76. data/app/models/telephony/events/busy.rb +6 -0
  77. data/app/models/telephony/events/call_answered.rb +22 -0
  78. data/app/models/telephony/events/call_fail.rb +6 -0
  79. data/app/models/telephony/events/complete_hold.rb +28 -0
  80. data/app/models/telephony/events/complete_one_step_transfer.rb +17 -0
  81. data/app/models/telephony/events/complete_resume.rb +28 -0
  82. data/app/models/telephony/events/complete_two_step_transfer.rb +6 -0
  83. data/app/models/telephony/events/conference.rb +6 -0
  84. data/app/models/telephony/events/connect.rb +22 -0
  85. data/app/models/telephony/events/customer_left_two_step_transfer.rb +6 -0
  86. data/app/models/telephony/events/dial_agent.rb +6 -0
  87. data/app/models/telephony/events/ended.rb +19 -0
  88. data/app/models/telephony/events/enqueue.rb +6 -0
  89. data/app/models/telephony/events/fail_one_step_transfer.rb +9 -0
  90. data/app/models/telephony/events/fail_two_step_transfer.rb +6 -0
  91. data/app/models/telephony/events/initialize_widget.rb +15 -0
  92. data/app/models/telephony/events/initiate_hold.rb +6 -0
  93. data/app/models/telephony/events/initiate_one_step_transfer.rb +6 -0
  94. data/app/models/telephony/events/initiate_resume.rb +6 -0
  95. data/app/models/telephony/events/initiate_two_step_transfer.rb +6 -0
  96. data/app/models/telephony/events/leave_two_step_transfer.rb +17 -0
  97. data/app/models/telephony/events/leave_voicemail.rb +22 -0
  98. data/app/models/telephony/events/no_answer.rb +6 -0
  99. data/app/models/telephony/events/play_closed_greeting.rb +6 -0
  100. data/app/models/telephony/events/play_message.rb +6 -0
  101. data/app/models/telephony/events/reject.rb +6 -0
  102. data/app/models/telephony/events/rona.rb +6 -0
  103. data/app/models/telephony/events/start.rb +17 -0
  104. data/app/models/telephony/events/straight_to_voicemail.rb +6 -0
  105. data/app/models/telephony/events/terminate.rb +6 -0
  106. data/app/models/telephony/events/transfer.rb +35 -0
  107. data/app/models/telephony/inbound_conversation_queue.rb +101 -0
  108. data/app/models/telephony/playable.rb +7 -0
  109. data/app/models/telephony/playable_listener.rb +42 -0
  110. data/app/models/telephony/pusher_event_publisher.rb +26 -0
  111. data/app/models/telephony/recording.rb +4 -0
  112. data/app/models/telephony/voicemail.rb +36 -0
  113. data/app/observers/telephony/agent_observer.rb +8 -0
  114. data/app/observers/telephony/call_observer.rb +15 -0
  115. data/app/observers/telephony/conversation_observer.rb +9 -0
  116. data/app/observers/telephony/event_observer.rb +9 -0
  117. data/app/views/layouts/telephony/application.html.erb +14 -0
  118. data/app/views/telephony/providers/twilio/calls/child_detached.builder +5 -0
  119. data/app/views/telephony/providers/twilio/calls/complete_hold.builder +6 -0
  120. data/app/views/telephony/providers/twilio/calls/connect.builder +13 -0
  121. data/app/views/telephony/providers/twilio/calls/dial.builder +14 -0
  122. data/app/views/telephony/providers/twilio/calls/done.builder +5 -0
  123. data/app/views/telephony/providers/twilio/calls/join_conference.builder +7 -0
  124. data/app/views/telephony/providers/twilio/calls/whisper_tone.builder +3 -0
  125. data/app/views/telephony/providers/twilio/inbound_calls/closed_hours.builder +7 -0
  126. data/app/views/telephony/providers/twilio/inbound_calls/create.builder +8 -0
  127. data/app/views/telephony/providers/twilio/inbound_calls/enqueue.builder +8 -0
  128. data/app/views/telephony/providers/twilio/inbound_calls/reject.builder +5 -0
  129. data/app/views/telephony/providers/twilio/inbound_calls/wait_music.builder +5 -0
  130. data/app/views/telephony/providers/twilio/musics/hold.builder +5 -0
  131. data/app/views/telephony/providers/twilio/voicemails/new.builder +6 -0
  132. data/app/views/telephony/twilio_client/index.html.erb +74 -0
  133. data/app/views/telephony/widget/index.erb +3 -0
  134. data/config/cucumber.yml +8 -0
  135. data/config/database.yml +57 -0
  136. data/config/environment.rb +67 -0
  137. data/config/initializers/pusher.rb +21 -0
  138. data/config/initializers/telephony.rb +44 -0
  139. data/config/routes.rb +94 -0
  140. data/config/wopr.yml.example +38 -0
  141. data/db/migrate/20130806213053_bootstrap_db.rb +105 -0
  142. data/db/migrate/20131009204026_create_telephony_blacklisted_numbers.rb +11 -0
  143. data/lib/agent_generator.rb +24 -0
  144. data/lib/tasks/cucumber.rake +65 -0
  145. data/lib/tasks/jasmine.rake +8 -0
  146. data/lib/tasks/telephony_tasks.rake +4 -0
  147. data/lib/telephony.rb +34 -0
  148. data/lib/telephony/concerns/controllers/twilio_request_verifier.rb +39 -0
  149. data/lib/telephony/conversation_data.rb +67 -0
  150. data/lib/telephony/engine.rb +19 -0
  151. data/lib/telephony/error.rb +8 -0
  152. data/lib/telephony/error/agent_on_a_call.rb +2 -0
  153. data/lib/telephony/error/base.rb +3 -0
  154. data/lib/telephony/error/connection.rb +2 -0
  155. data/lib/telephony/error/not_in_progress.rb +2 -0
  156. data/lib/telephony/error/queue_empty.rb +2 -0
  157. data/lib/telephony/helper.rb +11 -0
  158. data/lib/telephony/jobs/agent_offline.rb +28 -0
  159. data/lib/telephony/jobs/pusher_event.rb +21 -0
  160. data/lib/telephony/providers/twilio_provider.rb +162 -0
  161. data/lib/telephony/version.rb +3 -0
  162. data/spec/controllers/telephony/agents_controller_spec.rb +117 -0
  163. data/spec/controllers/telephony/call_centers_controller_spec.rb +25 -0
  164. data/spec/controllers/telephony/conversations_controller_spec.rb +229 -0
  165. data/spec/controllers/telephony/playable_listeners_controller_spec.rb +138 -0
  166. data/spec/controllers/telephony/providers/twilio/calls_controller_spec.rb +33 -0
  167. data/spec/controllers/telephony/providers/twilio/musics_controller_spec.rb +20 -0
  168. data/spec/controllers/telephony/signals/agents/presences_controller_spec.rb +154 -0
  169. data/spec/controllers/telephony/twilio_client_controller_spec.rb +58 -0
  170. data/spec/controllers/telephony/widget_controller_spec.rb +25 -0
  171. data/spec/dummy/Rakefile +7 -0
  172. data/spec/dummy/app/assets/javascripts/application.js +6 -0
  173. data/spec/dummy/app/assets/javascripts/lib/event_logger.js +82 -0
  174. data/spec/dummy/app/assets/javascripts/vendor/backbone.js +1159 -0
  175. data/spec/dummy/app/assets/javascripts/vendor/chosen.jquery.js +1018 -0
  176. data/spec/dummy/app/assets/javascripts/vendor/jquery.form.js +1117 -0
  177. data/spec/dummy/app/assets/javascripts/vendor/underscore.js +839 -0
  178. data/spec/dummy/app/assets/stylesheets/application.css +23 -0
  179. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  180. data/spec/dummy/app/controllers/widget_host_controller.rb +2 -0
  181. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  182. data/spec/dummy/app/views/layouts/application.html.erb +12 -0
  183. data/spec/dummy/app/views/widget_host/index.html.erb +53 -0
  184. data/spec/dummy/config.ru +4 -0
  185. data/spec/dummy/config/application.rb +61 -0
  186. data/spec/dummy/config/boot.rb +10 -0
  187. data/spec/dummy/config/call_centers.yml +35 -0
  188. data/spec/dummy/config/call_centers.yml.example +35 -0
  189. data/spec/dummy/config/database.yml +56 -0
  190. data/spec/dummy/config/environment.rb +5 -0
  191. data/spec/dummy/config/environments/cucumber.rb +39 -0
  192. data/spec/dummy/config/environments/development.rb +39 -0
  193. data/spec/dummy/config/environments/production.rb +67 -0
  194. data/spec/dummy/config/environments/test.rb +39 -0
  195. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  196. data/spec/dummy/config/initializers/inflections.rb +15 -0
  197. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  198. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  199. data/spec/dummy/config/initializers/session_store.rb +8 -0
  200. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  201. data/spec/dummy/config/locales/en.yml +5 -0
  202. data/spec/dummy/config/pusher.yml +14 -0
  203. data/spec/dummy/config/pusher.yml.example +14 -0
  204. data/spec/dummy/config/routes.rb +5 -0
  205. data/spec/dummy/config/sms_whitelist.json.example +4 -0
  206. data/spec/dummy/config/twilio.yml +57 -0
  207. data/spec/dummy/config/twilio.yml.example +57 -0
  208. data/spec/dummy/db/schema.rb +122 -0
  209. data/spec/dummy/db/seeds.rb +6 -0
  210. data/spec/dummy/log/production.log +156 -0
  211. data/spec/dummy/log/test.log +13812 -0
  212. data/spec/dummy/public/404.html +26 -0
  213. data/spec/dummy/public/422.html +26 -0
  214. data/spec/dummy/public/500.html +25 -0
  215. data/spec/dummy/public/assets/application-628309db7ce8c0abdd91f8f796881599.js +32 -0
  216. data/spec/dummy/public/assets/application-628309db7ce8c0abdd91f8f796881599.js.gz +0 -0
  217. data/spec/dummy/public/assets/application-6c551430f3bdd32ba57c9c41e5f91846.css +24 -0
  218. data/spec/dummy/public/assets/application-6c551430f3bdd32ba57c9c41e5f91846.css.gz +0 -0
  219. data/spec/dummy/public/assets/telephony/backspace-9f5b195e049ed90eb20ecdda6e264b0a.png +0 -0
  220. data/spec/dummy/public/assets/telephony/icon-spinner-ddbae473a70c5425810aeee31d4a8ad7.gif +0 -0
  221. data/spec/dummy/public/assets/telephony/zest-telephony-0e6b13673634da80de9ae09bcca253e3.eot +0 -0
  222. data/spec/dummy/public/assets/telephony/zest-telephony-37f394757ccb11b978f16d9fd32cb3b5.ttf +0 -0
  223. data/spec/dummy/public/assets/telephony/zest-telephony-e7efdbc60c0a1c8404951bc79c7fb3a8.woff +0 -0
  224. data/spec/dummy/public/assets/telephony/zest-telephony-f34287df626936908408f53a33db9e83.svg +73 -0
  225. data/spec/dummy/public/favicon.ico +0 -0
  226. data/spec/dummy/script/rails +6 -0
  227. data/spec/dummy/tmp/cache/assets/C98/020/sprockets%2Fa83f8254d688334ff179984e423f502f +0 -0
  228. data/spec/dummy/tmp/cache/assets/CA4/550/sprockets%2F407f9191fe573f2435c55c017cb0d022 +0 -0
  229. data/spec/dummy/tmp/cache/assets/CAA/6A0/sprockets%2F3265550e2d2c7f5a864ca85d255504c2 +0 -0
  230. data/spec/dummy/tmp/cache/assets/CB0/F60/sprockets%2F462f9efed31e6710732376ae06c27208 +0 -0
  231. data/spec/dummy/tmp/cache/assets/CB6/E20/sprockets%2Fa7092cd9f16917099d754f1380af13e1 +0 -0
  232. data/spec/dummy/tmp/cache/assets/CC7/9F0/sprockets%2F0517100815cd45c43fec1d8510fcb630 +0 -0
  233. data/spec/dummy/tmp/cache/assets/CCA/BF0/sprockets%2F16ea15e0b5d367c34b812612a4207e3e +0 -0
  234. data/spec/dummy/tmp/cache/assets/CD8/370/sprockets%2F357970feca3ac29060c1e3861e2c0953 +0 -0
  235. data/spec/dummy/tmp/cache/assets/CF1/960/sprockets%2F36ef26985821ec702c74dae4af789903 +0 -0
  236. data/spec/dummy/tmp/cache/assets/D07/F30/sprockets%2Fd12862f20df0c9ef7aa2b4216d247079 +0 -0
  237. data/spec/dummy/tmp/cache/assets/D09/BF0/sprockets%2F5c8595efaa8cc5235a09801c60e25a16 +0 -0
  238. data/spec/dummy/tmp/cache/assets/D0D/250/sprockets%2F5e759d66e374910dae5a51b221e6e3a4 +0 -0
  239. data/spec/dummy/tmp/cache/assets/D0D/E30/sprockets%2Fa282879e0788ba133f5bdc211cf134e8 +0 -0
  240. data/spec/dummy/tmp/cache/assets/D11/8E0/sprockets%2F2a193a8af29963f1113bef05788e02ff +0 -0
  241. data/spec/dummy/tmp/cache/assets/D14/270/sprockets%2F7f95a752910ed33d728532f8fabd340d +0 -0
  242. data/spec/dummy/tmp/cache/assets/D17/BE0/sprockets%2F58749d7aea4ca766c2520801f0f96b9b +0 -0
  243. data/spec/dummy/tmp/cache/assets/D18/270/sprockets%2Fbb2d88813b1795a97e9a7c14100eef77 +0 -0
  244. data/spec/dummy/tmp/cache/assets/D22/310/sprockets%2Fbae9148ce1229214dfbb0c425b0054d0 +0 -0
  245. data/spec/dummy/tmp/cache/assets/D30/EB0/sprockets%2F2c668b88beb4c9805d91ed4587f549e7 +0 -0
  246. data/spec/dummy/tmp/cache/assets/D32/A10/sprockets%2F13fe41fee1fe35b49d145bcc06610705 +0 -0
  247. data/spec/dummy/tmp/cache/assets/D3C/690/sprockets%2F681ba175fdd2da8d590b7e624b6135a6 +0 -0
  248. data/spec/dummy/tmp/cache/assets/D44/BC0/sprockets%2F7214f66dd91fdd51f00c3882293f5eea +0 -0
  249. data/spec/dummy/tmp/cache/assets/D46/FA0/sprockets%2F4f9543cd9e68ba3141fd488afb13681b +0 -0
  250. data/spec/dummy/tmp/cache/assets/D4E/1B0/sprockets%2Ff7cbd26ba1d28d48de824f0e94586655 +0 -0
  251. data/spec/dummy/tmp/cache/assets/D51/9A0/sprockets%2Fb6e4cbcf30f6f611671695f79ea846d5 +0 -0
  252. data/spec/dummy/tmp/cache/assets/D55/920/sprockets%2F0a02242aa4c4febcc796419aba103c69 +0 -0
  253. data/spec/dummy/tmp/cache/assets/D5A/EA0/sprockets%2Fd771ace226fc8215a3572e0aa35bb0d6 +0 -0
  254. data/spec/dummy/tmp/cache/assets/D5F/6F0/sprockets%2F155e7bc44f326371cce0ca8bc5b2414e +0 -0
  255. data/spec/dummy/tmp/cache/assets/D6B/250/sprockets%2Fe4eecce7a3371b11038c92f07cb28e67 +0 -0
  256. data/spec/dummy/tmp/cache/assets/D6B/E70/sprockets%2F103a456ae3692e3cca20b7696fdee8f0 +0 -0
  257. data/spec/dummy/tmp/cache/assets/D7F/AD0/sprockets%2Fb1c02d12303b1fe2f981771cbb0c4cbd +0 -0
  258. data/spec/dummy/tmp/cache/assets/D81/D00/sprockets%2F0c68b6d9dce98681a2a710d9fc3f7e39 +0 -0
  259. data/spec/dummy/tmp/cache/assets/D94/4D0/sprockets%2F4608a5bdf27ddf11bcaed1375911e9a2 +0 -0
  260. data/spec/dummy/tmp/cache/assets/D9A/580/sprockets%2F433005b19aaf7ed5cba9c7f8cd30b775 +0 -0
  261. data/spec/dummy/tmp/cache/assets/DA4/270/sprockets%2F860ab3bd30204e02c0a5d82bd4bbabe1 +0 -0
  262. data/spec/dummy/tmp/cache/assets/DA4/480/sprockets%2F53e39285bb57a2ddb6ff856b2eb491da +0 -0
  263. data/spec/dummy/tmp/cache/assets/DA6/3C0/sprockets%2F587c7e35f47442eadaa530acc5e9f5e6 +0 -0
  264. data/spec/dummy/tmp/cache/assets/DAB/DA0/sprockets%2Fb8fb5e8518ed3d38a7f6a8f18a9208aa +0 -0
  265. data/spec/dummy/tmp/cache/assets/DB2/700/sprockets%2Ffa85cb79b94fce1b8344d983ea863d2e +0 -0
  266. data/spec/dummy/tmp/cache/assets/DCC/4F0/sprockets%2Fbc0f3c61e16afb5a285ba8b39846ed8b +0 -0
  267. data/spec/dummy/tmp/cache/assets/DDC/400/sprockets%2Fcffd775d018f68ce5dba1ee0d951a994 +0 -0
  268. data/spec/dummy/tmp/cache/assets/DDC/BE0/sprockets%2F2c0a4d4675a61b014aedbbabb3f071fc +0 -0
  269. data/spec/dummy/tmp/cache/assets/E04/640/sprockets%2F3c5ddf2bbefd9d70f1b506b519cf856e +0 -0
  270. data/spec/dummy/tmp/cache/assets/E04/7F0/sprockets%2F35f1b2ff729a5ec21f6dc5f7d6faa67b +0 -0
  271. data/spec/dummy/tmp/cache/assets/E04/890/sprockets%2F2f5173deea6c795b8fdde723bb4b63af +0 -0
  272. data/spec/dummy/tmp/cache/assets/E07/230/sprockets%2Febd4f2dd708879d593aff008ccb75acc +0 -0
  273. data/spec/dummy/tmp/cache/assets/E17/9D0/sprockets%2F8db2b79f6c4eabe7a9ebe7a7898ec066 +0 -0
  274. data/spec/dummy/tmp/cache/assets/E31/F10/sprockets%2Fcebacf56667da1cc2961b59fd922eafb +0 -0
  275. data/spec/factories/agent.rb +26 -0
  276. data/spec/factories/calls.rb +106 -0
  277. data/spec/factories/conversations.rb +171 -0
  278. data/spec/factories/events.rb +250 -0
  279. data/spec/factories/playable_listeners.rb +6 -0
  280. data/spec/factories/recordings.rb +6 -0
  281. data/spec/factories/voicemails.rb +8 -0
  282. data/spec/fixtures/vcr_cassettes/Authenticating_an_online_user.yml +90 -0
  283. data/spec/fixtures/vcr_cassettes/Creating_a_conversation/creates_a_new_call.yml +42 -0
  284. data/spec/fixtures/vcr_cassettes/Creating_a_conversation/creates_a_new_conversation.yml +42 -0
  285. data/spec/fixtures/vcr_cassettes/Creating_a_conversation/returns_the_conversation_as_JSON.yml +42 -0
  286. data/spec/fixtures/vcr_cassettes/Pusher_channel_as_JSON.yml +90 -0
  287. data/spec/fixtures/vcr_cassettes/Pusher_to_publish_the_event.yml +32 -0
  288. data/spec/fixtures/vcr_cassettes/Reloading_the_widget_during_a_call/_create/during_a_call/after_a_failed_transfer/pushes_the_event_for_the_active_call.yml +1110 -0
  289. data/spec/fixtures/vcr_cassettes/Telephony_ConversationsController/_create/by_default/creates_a_new_call.yml +45 -0
  290. data/spec/fixtures/vcr_cassettes/Telephony_ConversationsController/_create/by_default/creates_a_new_conversation.yml +43 -0
  291. data/spec/fixtures/vcr_cassettes/Telephony_ConversationsController/_create/by_default/returns_the_conversation_as_JSON.yml +44 -0
  292. data/spec/fixtures/vcr_cassettes/Telephony_Providers_TwilioProvider/_call/allows_the_phone_to_ring_for_60_seconds.yml +42 -0
  293. data/spec/fixtures/vcr_cassettes/Telephony_Providers_TwilioProvider/_call/includes_a_status_change_callback_url.yml +42 -0
  294. data/spec/fixtures/vcr_cassettes/Telephony_Providers_TwilioProvider/_call/places_a_call.yml +42 -0
  295. data/spec/fixtures/vcr_cassettes/Telephony_Providers_TwilioProvider/_dial_into_conference/allows_the_phone_to_ring_for_15_seconds.yml +42 -0
  296. data/spec/fixtures/vcr_cassettes/Telephony_Providers_TwilioProvider/_dial_into_conference/places_a_call_to_redirect_the_participant_to_a_conference.yml +42 -0
  297. data/spec/fixtures/vcr_cassettes/Telephony_Providers_TwilioProvider/_hangup/by_default/hangs_up_the_call.yml +42 -0
  298. data/spec/fixtures/vcr_cassettes/buying_number_for_an_area_code.yml +42 -0
  299. data/spec/fixtures/vcr_cassettes/returns_success.yml +90 -0
  300. data/spec/javascripts/helpers/jasmine-jquery.js +546 -0
  301. data/spec/javascripts/helpers/mock-ajax.js +207 -0
  302. data/spec/javascripts/support/jasmine.yml +80 -0
  303. data/spec/javascripts/telephony/models/agent_spec.js +40 -0
  304. data/spec/javascripts/telephony/models/conversation_spec.js +515 -0
  305. data/spec/javascripts/telephony/models/device_spec.js +80 -0
  306. data/spec/javascripts/telephony/models/transfer_spec.js +22 -0
  307. data/spec/javascripts/telephony/push_spec.js +427 -0
  308. data/spec/javascripts/telephony/views/agents_view_spec.js +101 -0
  309. data/spec/javascripts/telephony/views/application_view_spec.js +74 -0
  310. data/spec/javascripts/telephony/views/conversation_view_spec.js +626 -0
  311. data/spec/javascripts/telephony/views/status_view_spec.js +30 -0
  312. data/spec/javascripts/telephony/views/transfer_view_spec.js +187 -0
  313. data/spec/javascripts/telephony/views/twilio_client_view_spec.js +77 -0
  314. data/spec/javascripts/telephony/views/widget_view_spec.js +20 -0
  315. data/spec/lib/telephony/concerns/controllers/twilio_request_verifier_spec.rb +77 -0
  316. data/spec/lib/telephony/conversation_data_spec.rb +342 -0
  317. data/spec/lib/telephony/helper_spec.rb +24 -0
  318. data/spec/lib/telephony/jobs/agent_offline_spec.rb +37 -0
  319. data/spec/lib/telephony/jobs/pusher_event_spec.rb +39 -0
  320. data/spec/lib/telephony/providers/twilio_provider_spec.rb +439 -0
  321. data/spec/lib/telephony_spec.rb +104 -0
  322. data/spec/models/telephony/agent_spec.rb +479 -0
  323. data/spec/models/telephony/agent_state_machine_spec.rb +131 -0
  324. data/spec/models/telephony/call_center_spec.rb +32 -0
  325. data/spec/models/telephony/call_spec.rb +597 -0
  326. data/spec/models/telephony/call_state_machine_spec.rb +472 -0
  327. data/spec/models/telephony/conversation_spec.rb +751 -0
  328. data/spec/models/telephony/conversation_state_machine_spec.rb +387 -0
  329. data/spec/models/telephony/conversations_presenter_spec.rb +40 -0
  330. data/spec/models/telephony/event_spec.rb +716 -0
  331. data/spec/models/telephony/inbound_conversation_queue_spec.rb +243 -0
  332. data/spec/models/telephony/playable_listener_spec.rb +43 -0
  333. data/spec/models/telephony/pusher_event_publisher_spec.rb +36 -0
  334. data/spec/models/telephony/recording_spec.rb +6 -0
  335. data/spec/models/telephony/voicemail_spec.rb +96 -0
  336. data/spec/observers/telephony/agent_observer_spec.rb +21 -0
  337. data/spec/observers/telephony/event_observer_spec.rb +19 -0
  338. data/spec/requests/create_conversation_spec.rb +39 -0
  339. data/spec/requests/dequeue_call_spec.rb +42 -0
  340. data/spec/requests/list_conversations_spec.rb +33 -0
  341. data/spec/requests/presences_spec.rb +51 -0
  342. data/spec/requests/providers/twilio/calls/child_answered_spec.rb +24 -0
  343. data/spec/requests/providers/twilio/calls/child_detached_spec.rb +249 -0
  344. data/spec/requests/providers/twilio/calls/dial_spec.rb +132 -0
  345. data/spec/requests/providers/twilio/calls/done_spec.rb +69 -0
  346. data/spec/requests/providers/twilio/calls/join_conference_spec.rb +56 -0
  347. data/spec/requests/providers/twilio/calls/leave_queue_spec.rb +44 -0
  348. data/spec/requests/providers/twilio/calls/parent_answered_spec.rb +59 -0
  349. data/spec/requests/providers/twilio/inbound/connect_dequeued_call_spec.rb +124 -0
  350. data/spec/requests/providers/twilio/inbound/enqueue_inbound_call_spec.rb +108 -0
  351. data/spec/requests/providers/twilio/inbound/enqueued_call_wait_music_spec.rb +13 -0
  352. data/spec/requests/providers/twilio/voicemails/leave_voicemail_spec.rb +78 -0
  353. data/spec/requests/search_conversations_spec.rb +101 -0
  354. data/spec/requests/transfer_spec.rb +65 -0
  355. data/spec/requests/voicemail_api_spec.rb +58 -0
  356. data/spec/spec_helper.rb +27 -0
  357. data/spec/support/factory_girl.rb +5 -0
  358. data/spec/support/matchers/be_complete_hold.rb +20 -0
  359. data/spec/support/matchers/be_whisper_tone.rb +35 -0
  360. data/spec/support/matchers/join_conference.rb +23 -0
  361. data/spec/support/nokogiri.rb +1 -0
  362. data/spec/support/pusher_helper.rb +24 -0
  363. data/spec/support/vcr.rb +12 -0
  364. data/spec/support/webmock.rb +1 -0
  365. metadata +692 -0
@@ -0,0 +1,26 @@
1
+ require 'yaml'
2
+
3
+ module Telephony
4
+ class CallCenter
5
+ attr_reader :name, :host, :username, :password
6
+
7
+ def self.load rails_env, config_path = 'config/call_centers.yml'
8
+ @config ||= YAML.load_file(config_path)[rails_env]
9
+ end
10
+
11
+ def self.all
12
+ @config.map { |entry| new entry }
13
+ end
14
+
15
+ def self.find_by_name name
16
+ all.detect { |call_center| call_center.name == name }
17
+ end
18
+
19
+ def initialize opts
20
+ @name = opts['name']
21
+ @host = opts['host']
22
+ @username = opts['username']
23
+ @password = opts['password']
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,98 @@
1
+ require 'state_machine'
2
+
3
+ module Telephony
4
+ module CallStateMachine
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ state_machine :state, :initial => :not_initiated do
9
+ event :connect do
10
+ transition not_initiated: :connecting
11
+ end
12
+
13
+ event :reject do
14
+ transition not_initiated: :terminated
15
+ end
16
+
17
+ event :no_answer do
18
+ transition connecting: :terminated
19
+ end
20
+
21
+ event :busy do
22
+ transition connecting: :terminated
23
+ end
24
+
25
+ event :call_fail do
26
+ transition connecting: :terminated
27
+ end
28
+
29
+ event :answer do
30
+ transition connecting: :in_progress
31
+ end
32
+
33
+ event :conference do
34
+ transition connecting: :in_conference,
35
+ in_progress: :in_conference,
36
+ in_progress_hold: :in_conference,
37
+ in_conference: :in_conference
38
+ end
39
+
40
+ event :dial_agent do
41
+ transition in_conference: :in_progress,
42
+ in_progress: :in_progress,
43
+ in_progress_hold: :in_progress
44
+ end
45
+
46
+ event :straight_to_voicemail do
47
+ transition :not_initiated => :terminated
48
+ end
49
+
50
+ event :terminate do
51
+ transition all - [:terminated] => :terminated
52
+ end
53
+
54
+ event :complete_hold do
55
+ transition [:in_progress, :in_conference] => :in_progress_hold
56
+ end
57
+
58
+ before_transition :on => :answer do |call|
59
+ call.connected_at = Time.now
60
+ end
61
+
62
+ before_transition :on => :terminate do |call|
63
+ call.terminated_at = Time.now
64
+ end
65
+
66
+ after_transition do |call, transition|
67
+ call.events.log name: transition.event,
68
+ data: {
69
+ call_id: call.id,
70
+ call_state: transition.to,
71
+ conversation_id: call.conversation.id,
72
+ conversation_state: call.conversation.state
73
+ }
74
+ end
75
+
76
+ after_transition :on => :conference do |call|
77
+ call.conversation(true)
78
+ call.conversation.check_for_successful_transfer
79
+ call.conversation.check_for_successful_resume
80
+ call.conversation.check_for_successful_hold
81
+ end
82
+
83
+ after_transition :on => :dial_agent do |call|
84
+ call.conversation(true).terminate_conferenced_calls call.id
85
+ end
86
+
87
+ after_transition any => :terminated do |call|
88
+ call.conversation(true).check_for_terminate
89
+ end
90
+
91
+ after_transition :on => :complete_hold do |call|
92
+ call.conversation(true).check_for_successful_hold
93
+ end
94
+
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,273 @@
1
+ module Telephony
2
+ class Conversation < Base
3
+ extend Telephony::NumberHelper
4
+ include ConversationStateMachine
5
+
6
+ module CONVERSATION_TYPES
7
+ OUTBOUND = 'outbound'
8
+ INBOUND = 'inbound'
9
+ end
10
+
11
+ has_many :calls, order: 'id'
12
+ belongs_to :transferee, class_name: 'Agent'
13
+ has_many :events, class_name: 'Events::Base'
14
+
15
+ attr_accessible :state,
16
+ :loan_id,
17
+ :initiator_id,
18
+ :caller_id,
19
+ :number,
20
+ :conversation_type,
21
+ :transferee
22
+
23
+ def self.begin!(args)
24
+ agent = Agent.find_by_csr_id args[:from_id]
25
+
26
+ if agent.generate_caller_id?
27
+ caller_id = Telephony.provider.caller_id_for extract_area_code(args[:to])
28
+ else
29
+ caller_id = Telephony.provider.outbound_caller_id
30
+ end
31
+
32
+ conversation = transaction do
33
+ conversation = create loan_id: args[:loan_id],
34
+ initiator_id: args[:from_id],
35
+ caller_id: caller_id
36
+
37
+ conversation.calls.create! number: agent.number,
38
+ participant_id: args[:from_id],
39
+ participant_type: args[:from_type],
40
+ agent: agent
41
+
42
+ conversation.calls.create! number: args[:to],
43
+ participant_id: args[:to_id],
44
+ participant_type: args[:to_type]
45
+ conversation
46
+ end
47
+ conversation.connect!
48
+ conversation.initiating_call.connect!
49
+ conversation.initiating_call.make!
50
+ conversation
51
+ end
52
+
53
+ def self.find_with_lock(id)
54
+ transaction do
55
+ yield find(id, joins: {calls: :agent}, lock: true, readonly: false)
56
+ end
57
+ end
58
+
59
+ def self.find_inbound_with_lock(id)
60
+ transaction do
61
+ yield find(id, lock: true)
62
+ end
63
+ end
64
+
65
+ def self.find_inbound_calls_with_lock(id)
66
+ transaction do
67
+ yield find(id, joins: :calls, lock: true)
68
+ end
69
+ end
70
+
71
+ def self.create_inbound!(args)
72
+ create! args.merge(conversation_type: 'inbound')
73
+ end
74
+
75
+ def self.queue_size
76
+ where(state: 'enqueued').count
77
+ end
78
+
79
+ def not_initiated_call
80
+ calls.detect(&:not_initiated?)
81
+ end
82
+
83
+ def active_on_call
84
+ calls.reject(&:terminated?)
85
+ end
86
+
87
+ def check_for_successful_transfer
88
+ complete_two_step_transfer if all_agents_in_conference? &&
89
+ (customer.in_conference? || customer.in_progress_hold?)
90
+ end
91
+
92
+ def check_for_successful_hold
93
+ complete_hold if all_agents_in_conference? && customer.in_progress_hold?
94
+ end
95
+
96
+ def check_for_successful_resume
97
+ complete_resume if all_agents_in_conference? && customer.in_conference?
98
+ end
99
+
100
+ def terminate_conferenced_calls(except_call_id)
101
+ calls.select(&:in_conference?).each do |call|
102
+ if call.id != except_call_id
103
+ Telephony.provider.hangup call.sid
104
+ call.terminate!
105
+ end
106
+ end
107
+ end
108
+
109
+ def check_for_terminate
110
+ if active_on_call.size == 0
111
+ terminate! unless terminated?
112
+ elsif one_step_transferring? && active_on_call.size == 1
113
+ fail_one_step_transfer!
114
+ elsif leaving_voicemail? && active_on_call.size == 1
115
+ # noop
116
+ elsif inbound? && connecting? && active_on_call.first == customer
117
+ rona!
118
+ elsif active_on_call.size == 1
119
+ lone_call = active_on_call.first
120
+ if lone_call.in_conference? || lone_call.in_progress_hold?
121
+ Telephony.provider.hangup lone_call.sid if lone_call.sid
122
+ end
123
+ lone_call.terminate!
124
+ elsif (two_step_transferring? || two_step_transferring_hold?) && active_on_call.size == 2
125
+ fail_two_step_transfer!
126
+ elsif (in_progress_two_step_transfer? || in_progress_two_step_transfer_hold?) && active_on_call.size == 2 && customer.terminated?
127
+ customer_left_two_step_transfer!
128
+ elsif (in_progress_two_step_transfer? || in_progress_two_step_transfer_hold?) && active_on_call.size == 2
129
+ leave_two_step_transfer!
130
+ end
131
+ end
132
+
133
+ def child_call(sid=nil)
134
+ (sid && calls.detect { |c| c.sid == sid }) || active_on_call[1]
135
+ end
136
+
137
+ def customer
138
+ calls.detect { |call| !call.agent? }
139
+ end
140
+
141
+ def initiating_call
142
+ calls.first
143
+ end
144
+
145
+ def hold!
146
+ initiate_hold!
147
+
148
+ if customer.in_conference?
149
+ customer.redirect_to_hold
150
+ if active_agent_legs.size == 1
151
+ # If there's only one agent on, redirect them so they
152
+ # get the conference music
153
+ active_agent_leg.redirect_to_conference
154
+ end
155
+ elsif customer == child_call
156
+ customer.redirect_to_hold
157
+ else
158
+ child_call.redirect_to_conference
159
+ end
160
+ end
161
+
162
+ def resume!
163
+ initiate_resume!
164
+ customer.redirect_to_conference
165
+ end
166
+
167
+ def transfer!(csr_id, one_step)
168
+ unless in_progress? || in_progress_hold?
169
+ errors[:base] << "Conversation already #{state}"
170
+ return false
171
+ end
172
+
173
+ agent = Agent.find_by_csr_id csr_id
174
+
175
+ agent.with_lock do
176
+ one_step ? one_step_transfer!(agent) : two_step_transfer!(agent)
177
+ end
178
+ end
179
+
180
+ def as_json(options={})
181
+ super({ include: { calls: { include: :recordings } } }.deep_merge(options))
182
+ end
183
+
184
+ def outbound?
185
+ conversation_type == CONVERSATION_TYPES::OUTBOUND
186
+ end
187
+
188
+ def inbound?
189
+ conversation_type == CONVERSATION_TYPES::INBOUND
190
+ end
191
+
192
+ def active_agent_legs
193
+ active_on_call.select do |call|
194
+ call.agent.present?
195
+ end
196
+ end
197
+
198
+ def active_agent_leg
199
+ active_agent_legs.first
200
+ end
201
+
202
+ def first_active_agent
203
+ leg = active_agent_legs[0]
204
+ leg.agent if leg
205
+ end
206
+
207
+ def first_inactive_agent
208
+ leg = calls.detect do |call|
209
+ call.agent? && call.terminated?
210
+ end
211
+ leg.agent if leg
212
+ end
213
+
214
+ def second_active_agent
215
+ leg = active_agent_legs[1]
216
+ leg.agent if leg
217
+ end
218
+
219
+ private
220
+
221
+ def one_step_transfer!(agent)
222
+ agent2_leg = calls.create number: agent.phone_number,
223
+ participant_id: agent.csr_id,
224
+ participant_type: 'csr',
225
+ agent: agent
226
+
227
+ if agent.transferrable?
228
+ initiate_one_step_transfer!
229
+ agent2_leg.connect!
230
+ else
231
+ leave_voicemail!
232
+ agent2_leg.straight_to_voicemail!
233
+ end
234
+
235
+ customer.dial_agent!
236
+ customer.redirect_to_dial
237
+
238
+ rescue => error
239
+ Rails.logger.error "Failed to one-step transfer: #{error.message}"
240
+ errors[:base] << "#{error.message}"
241
+ agent2_leg.terminate
242
+ false
243
+ end
244
+
245
+ def two_step_transfer!(agent)
246
+ unless agent.transferrable?
247
+ errors[:base] << 'Agent is unavailable'
248
+ return false
249
+ end
250
+
251
+ agent2_leg = calls.create number: agent.phone_number,
252
+ participant_id: agent.csr_id,
253
+ participant_type: 'csr',
254
+ agent: agent
255
+
256
+ initiate_two_step_transfer!
257
+
258
+ child_call.redirect_to_conference unless two_step_transferring_hold?
259
+ agent2_leg.dial_into_conference!
260
+ agent2_leg.connect!
261
+
262
+ rescue => error
263
+ Rails.logger.error "Failed to two-step transfer: #{error.message}"
264
+ errors[:base] << "#{error.message}"
265
+ agent2_leg.terminate!
266
+ false
267
+ end
268
+
269
+ def all_agents_in_conference?
270
+ active_agent_legs.all?(&:in_conference?)
271
+ end
272
+ end
273
+ end
@@ -0,0 +1,109 @@
1
+ require 'state_machine'
2
+
3
+ module Telephony
4
+ module ConversationStateMachine
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ state_machine :state, :initial => :initiated do
9
+
10
+ event :play_message do
11
+ transition :initiated => :playing_message
12
+ end
13
+
14
+ event :play_closed_greeting do
15
+ transition :initiated => :terminated
16
+ end
17
+
18
+ event :enqueue do
19
+ transition :playing_message => :enqueued
20
+ end
21
+
22
+ event :connect do
23
+ transition [:initiated, :enqueued] => :connecting
24
+ end
25
+
26
+ event :rona do
27
+ transition :connecting => :enqueued
28
+ end
29
+
30
+ event :start do
31
+ transition :connecting => :in_progress
32
+ end
33
+
34
+ event :initiate_one_step_transfer do
35
+ transition [:in_progress, :in_progress_hold] => :one_step_transferring
36
+ end
37
+
38
+ event :complete_one_step_transfer do
39
+ transition :one_step_transferring => :in_progress
40
+ end
41
+
42
+ event :fail_one_step_transfer do
43
+ transition :one_step_transferring => :leaving_voicemail
44
+ end
45
+
46
+ event :initiate_two_step_transfer do
47
+ transition :in_progress => :two_step_transferring,
48
+ :in_progress_hold => :two_step_transferring_hold
49
+ end
50
+
51
+ event :complete_two_step_transfer do
52
+ transition :two_step_transferring => :in_progress_two_step_transfer,
53
+ :two_step_transferring_hold => :in_progress_two_step_transfer_hold
54
+ end
55
+
56
+ event :leave_two_step_transfer do
57
+ transition :in_progress_two_step_transfer => :in_progress,
58
+ :in_progress_two_step_transfer_hold => :in_progress_hold
59
+ end
60
+
61
+ event :customer_left_two_step_transfer do
62
+ transition :in_progress_two_step_transfer => :agents_only,
63
+ :in_progress_two_step_transfer_hold => :agents_only
64
+ end
65
+
66
+ event :fail_two_step_transfer do
67
+ transition :two_step_transferring => :in_progress,
68
+ :two_step_transferring_hold => :in_progress_hold
69
+ end
70
+
71
+ event :initiate_hold do
72
+ transition :in_progress => :initiating_hold,
73
+ :in_progress_two_step_transfer => :initiating_two_step_transfer_hold
74
+ end
75
+
76
+ event :complete_hold do
77
+ transition :initiating_hold => :in_progress_hold,
78
+ :initiating_two_step_transfer_hold => :in_progress_two_step_transfer_hold
79
+ end
80
+
81
+ event :initiate_resume do
82
+ transition :in_progress_hold => :initiating_resume,
83
+ :in_progress_two_step_transfer_hold => :initiating_two_step_transfer_resume
84
+ end
85
+
86
+ event :complete_resume do
87
+ transition :initiating_resume => :in_progress,
88
+ :initiating_two_step_transfer_resume => :in_progress_two_step_transfer
89
+ end
90
+
91
+ event :leave_voicemail do
92
+ transition all - [:leaving_voicemail, :terminated] => :leaving_voicemail
93
+ end
94
+
95
+ event :terminate do
96
+ transition all - [:terminated] => :terminated
97
+ end
98
+
99
+ after_transition do |conversation, transition|
100
+ conversation.events.log name: transition.event,
101
+ data: {
102
+ conversation_id: conversation.id,
103
+ conversation_state: transition.to
104
+ }
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end