stealth 1.0.4 → 1.1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -4
  3. data/Gemfile +1 -1
  4. data/Gemfile.lock +32 -77
  5. data/README.md +1 -0
  6. data/VERSION +1 -1
  7. data/docs/03-basics.md +18 -2
  8. data/docs/04-sessions.md +12 -0
  9. data/docs/05-controllers.md +3 -3
  10. data/docs/07-replies.md +42 -1
  11. data/lib/stealth/base.rb +23 -9
  12. data/lib/stealth/controller/catch_all.rb +1 -1
  13. data/lib/stealth/controller/controller.rb +21 -9
  14. data/lib/stealth/controller/dynamic_delay.rb +64 -0
  15. data/lib/stealth/controller/replies.rb +55 -14
  16. data/lib/stealth/flow/base.rb +1 -1
  17. data/lib/stealth/flow/specification.rb +30 -8
  18. data/lib/stealth/flow/state.rb +10 -5
  19. data/lib/stealth/generators/builder/Gemfile +3 -0
  20. data/lib/stealth/generators/builder/Procfile.dev +1 -1
  21. data/lib/stealth/generators/builder/bot/controllers/catch_alls_controller.rb +3 -6
  22. data/lib/stealth/generators/builder/config/services.yml +9 -10
  23. data/lib/stealth/generators/generate/flow/controllers/controller.tt +0 -19
  24. data/lib/stealth/generators/generate/flow/helpers/helper.tt +2 -1
  25. data/lib/stealth/generators/generate/flow/replies/{ask_reply.tt → ask_example.tt} +0 -0
  26. data/lib/stealth/generators/generate.rb +2 -9
  27. data/lib/stealth/migrations/tasks.rb +2 -0
  28. data/lib/stealth/scheduled_reply.rb +1 -1
  29. data/lib/stealth/server.rb +2 -0
  30. data/lib/stealth/services/jobs/handle_message_job.rb +1 -1
  31. data/lib/stealth/session.rb +44 -16
  32. data/spec/controller/catch_all_spec.rb +2 -1
  33. data/spec/controller/controller_spec.rb +102 -13
  34. data/spec/controller/dynamic_delay_spec.rb +72 -0
  35. data/spec/controller/replies_spec.rb +94 -3
  36. data/spec/flow/state_spec.rb +33 -4
  37. data/spec/replies/messages/say_hola.yml+facebook.erb +6 -0
  38. data/spec/replies/messages/say_hola.yml+twilio.erb +6 -0
  39. data/spec/replies/messages/say_hola.yml.erb +6 -0
  40. data/spec/replies/messages/say_howdy_with_dynamic.yml +79 -0
  41. data/spec/replies/messages/say_offer_with_dynamic.yml +6 -0
  42. data/spec/replies/messages/say_yo.yml +6 -0
  43. data/spec/replies/messages/say_yo.yml+twitter +6 -0
  44. data/spec/session_spec.rb +17 -0
  45. data/spec/support/sample_messages.rb +24 -26
  46. data/stealth.gemspec +1 -3
  47. metadata +25 -41
  48. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -35
  49. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -17
  50. data/lib/stealth/generators/generate/flow/models/model.tt +0 -2
  51. data/lib/stealth/generators/generate/flow/replies/say_no_reply.tt +0 -2
  52. data/lib/stealth/generators/generate/flow/replies/say_yes_reply.tt +0 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cb5b41def6c4e13619ace50cff93ef042e41907d847d5bc391342a07b659c12f
4
- data.tar.gz: edff40b0be61bb0ce35db7376b85fcbeb287daadb0a936266c0fb3ba2a442b11
3
+ metadata.gz: 6c918a4ed7d98e6c5e8c60dab8e3e96d1f5a1d6407bb21843fa6a4eb8a46b0fc
4
+ data.tar.gz: e4fe19747e02ba2b17af1d932e2505153db47173a45bee8558d7ab2cd8c5309b
5
5
  SHA512:
6
- metadata.gz: d41a57396b557409239312b03ed2009630a0b8d9396f97a5b5582ef6a9d7a68fc76696bf3ea7629a1af9fab3f3ccb9b2dd0b53db8f32cb9376c7b7769b289a75
7
- data.tar.gz: 490adbca626688b0af47b89d857cf7bb14a307b49b1a6b92012d49b2eec5d5fc09f5176a5976b493f772af1c6fdc8c91cd8995d7cc08f8843db853135a09dd1c
6
+ metadata.gz: 0fbe087d4e4a7d5e60bc442524588ae6c63f1d842b35ebb7f73bf5b2a09716e07a08fc0d3867c7392045c781b8fff6fe6d9aa304c73d0c0229118a2f2dcc1bcf
7
+ data.tar.gz: 710cc3404711a0d18c765846acada15b94d0f1ebd0fb42196fcc85fc8c6254070bfcfee93fadf8db5bf439aa1acd286cd5c52ff5737017afdec65ddd64fd9c41
data/CHANGELOG.md CHANGED
@@ -1,10 +1,25 @@
1
- # Changelog for Stealth v1.0.1
1
+ # Changelog for Stealth v1.1.0
2
+
3
+ ## Breaking Changes
4
+
5
+ * [Sidekiq] Sidekiq queues have been renamed from `webhooks` and `default` to `stealth_webhooks` and `stealth_replies`. Please ensure your `Procfile` is adjusted accordingly.
6
+ * [Flows] `flow_map.current_state.fails_to` now returns a `Stealth::Session` instead of a `Stealth::Flow::State`. This might affect your `catch_alls`.
2
7
 
3
8
  ## Enhancements
4
9
 
5
- None.
10
+ * [Controllers] `current_session_id` now references the session ID in controllers
11
+ * [Replies] Added support for dynamic delays
12
+ * [Replies] Added support for service-specific variants
13
+ * [Models] ActiveRecord is now part of the generated bot Gemfile and can be removed from bots
14
+ * [Flows] Added support for `redirects_to` during state declaration. If specified, will automatically step a user to the specified state or session.
15
+ * [Flows] `redirects_to` and `fails_to` now support a session string as an argument (`my_flow->some_state`). This allows you to fail and redirect to other flows. A state name specified as a string or symbol is still allowed.
16
+ * [Errors] Backtraces are now more readable in logs
6
17
 
7
18
  ## Bug Fixes
8
19
 
9
- * [Docs] Fixed link to docs in the README.
10
- * [Generators] Fixed the generated name of controllers.
20
+ * [Generators] Fixed flow generation
21
+ * [Generators] Fixed sample Facebook services.yml example
22
+
23
+ ## Deprecations
24
+
25
+ * [Controllers] `current_user_id` has been soft deprecated in favor of `current_session_id`
data/Gemfile CHANGED
@@ -2,5 +2,5 @@ source 'https://rubygems.org'
2
2
  gemspec
3
3
 
4
4
  platforms :mri do
5
- gem 'oj', '~> 3.3'
5
+ gem 'oj', '~> 3.7'
6
6
  end
data/Gemfile.lock CHANGED
@@ -1,12 +1,10 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- stealth (1.0.3)
5
- activerecord (~> 5.2)
4
+ stealth (1.1.0.alpha)
6
5
  activesupport (~> 5.2)
7
6
  multi_json (~> 1.12)
8
7
  puma (~> 3.10)
9
- railties (~> 5.2)
10
8
  sidekiq (~> 5.0)
11
9
  sinatra (~> 2.0)
12
10
  thor (~> 0.20)
@@ -14,96 +12,53 @@ PATH
14
12
  GEM
15
13
  remote: https://rubygems.org/
16
14
  specs:
17
- actionpack (5.2.0)
18
- actionview (= 5.2.0)
19
- activesupport (= 5.2.0)
20
- rack (~> 2.0)
21
- rack-test (>= 0.6.3)
22
- rails-dom-testing (~> 2.0)
23
- rails-html-sanitizer (~> 1.0, >= 1.0.2)
24
- actionview (5.2.0)
25
- activesupport (= 5.2.0)
26
- builder (~> 3.1)
27
- erubi (~> 1.4)
28
- rails-dom-testing (~> 2.0)
29
- rails-html-sanitizer (~> 1.0, >= 1.0.3)
30
- activemodel (5.2.0)
31
- activesupport (= 5.2.0)
32
- activerecord (5.2.0)
33
- activemodel (= 5.2.0)
34
- activesupport (= 5.2.0)
35
- arel (>= 9.0)
36
- activesupport (5.2.0)
15
+ activesupport (5.2.1.1)
37
16
  concurrent-ruby (~> 1.0, >= 1.0.2)
38
17
  i18n (>= 0.7, < 2)
39
18
  minitest (~> 5.1)
40
19
  tzinfo (~> 1.1)
41
- arel (9.0.0)
42
- builder (3.2.3)
43
- concurrent-ruby (1.0.5)
20
+ concurrent-ruby (1.1.3)
44
21
  connection_pool (2.2.2)
45
- crass (1.0.4)
46
22
  diff-lcs (1.3)
47
- erubi (1.7.1)
48
- i18n (1.0.1)
23
+ i18n (1.1.1)
49
24
  concurrent-ruby (~> 1.0)
50
- loofah (2.2.2)
51
- crass (~> 1.0.2)
52
- nokogiri (>= 1.5.9)
53
- method_source (0.9.0)
54
- mini_portile2 (2.3.0)
55
25
  minitest (5.11.3)
56
- mock_redis (0.17.3)
26
+ mock_redis (0.19.0)
57
27
  multi_json (1.13.1)
58
- mustermann (1.0.2)
59
- nokogiri (1.8.4)
60
- mini_portile2 (~> 2.3.0)
61
- oj (3.5.0)
62
- puma (3.11.4)
63
- rack (2.0.4)
64
- rack-protection (2.0.3)
28
+ mustermann (1.0.3)
29
+ oj (3.7.1)
30
+ puma (3.12.0)
31
+ rack (2.0.6)
32
+ rack-protection (2.0.4)
65
33
  rack
66
- rack-test (0.8.3)
34
+ rack-test (1.1.0)
67
35
  rack (>= 1.0, < 3)
68
- rails-dom-testing (2.0.3)
69
- activesupport (>= 4.2.0)
70
- nokogiri (>= 1.6)
71
- rails-html-sanitizer (1.0.4)
72
- loofah (~> 2.2, >= 2.2.2)
73
- railties (5.2.0)
74
- actionpack (= 5.2.0)
75
- activesupport (= 5.2.0)
76
- method_source
77
- rake (>= 0.8.7)
78
- thor (>= 0.18.1, < 2.0)
79
- rake (12.3.1)
80
- redis (4.0.1)
81
- rspec (3.7.0)
82
- rspec-core (~> 3.7.0)
83
- rspec-expectations (~> 3.7.0)
84
- rspec-mocks (~> 3.7.0)
85
- rspec-core (3.7.1)
86
- rspec-support (~> 3.7.0)
87
- rspec-expectations (3.7.0)
36
+ redis (4.0.3)
37
+ rspec (3.8.0)
38
+ rspec-core (~> 3.8.0)
39
+ rspec-expectations (~> 3.8.0)
40
+ rspec-mocks (~> 3.8.0)
41
+ rspec-core (3.8.0)
42
+ rspec-support (~> 3.8.0)
43
+ rspec-expectations (3.8.1)
88
44
  diff-lcs (>= 1.2.0, < 2.0)
89
- rspec-support (~> 3.7.0)
90
- rspec-mocks (3.7.0)
45
+ rspec-support (~> 3.8.0)
46
+ rspec-mocks (3.8.0)
91
47
  diff-lcs (>= 1.2.0, < 2.0)
92
- rspec-support (~> 3.7.0)
93
- rspec-support (3.7.1)
94
- rspec_junit_formatter (0.3.0)
48
+ rspec-support (~> 3.8.0)
49
+ rspec-support (3.8.0)
50
+ rspec_junit_formatter (0.4.1)
95
51
  rspec-core (>= 2, < 4, != 2.12.0)
96
- sidekiq (5.1.3)
97
- concurrent-ruby (~> 1.0)
98
- connection_pool (~> 2.2, >= 2.2.0)
52
+ sidekiq (5.2.3)
53
+ connection_pool (~> 2.2, >= 2.2.2)
99
54
  rack-protection (>= 1.5.0)
100
55
  redis (>= 3.3.5, < 5)
101
- sinatra (2.0.3)
56
+ sinatra (2.0.4)
102
57
  mustermann (~> 1.0)
103
58
  rack (~> 2.0)
104
- rack-protection (= 2.0.3)
59
+ rack-protection (= 2.0.4)
105
60
  tilt (~> 2.0)
106
- thor (0.20.0)
61
+ thor (0.20.3)
107
62
  thread_safe (0.3.6)
108
63
  tilt (2.0.8)
109
64
  tzinfo (1.2.5)
@@ -114,11 +69,11 @@ PLATFORMS
114
69
 
115
70
  DEPENDENCIES
116
71
  mock_redis (~> 0.17)
117
- oj (~> 3.3)
118
- rack-test (~> 0.7)
72
+ oj (~> 3.7)
73
+ rack-test (~> 1.1)
119
74
  rspec (~> 3.6)
120
75
  rspec_junit_formatter (~> 0.3)
121
76
  stealth!
122
77
 
123
78
  BUNDLED WITH
124
- 1.16.1
79
+ 1.16.3
data/README.md CHANGED
@@ -23,6 +23,7 @@ Currently, there are gems for:
23
23
  ### Messaging
24
24
  * [Facebook Messenger](https://github.com/hellostealth/stealth-facebook)
25
25
  * [Twilio SMS](https://github.com/hellostealth/stealth-twilio)
26
+ * [Smooch](https://github.com/hellostealth/stealth-smooch)
26
27
 
27
28
  ### Natural Language Processing
28
29
  * [AWS Comprehend](https://github.com/hellostealth/stealth-aws-comprehend)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.4
1
+ 1.1.0.rc1
data/docs/03-basics.md CHANGED
@@ -75,6 +75,8 @@ class FlowMap
75
75
  state :say_hello
76
76
  state :ask_name
77
77
  state :get_name, fails_to: :ask_name
78
+ state :say_wow, redirects_to: :say_hello
79
+ state :say_bye, redirects_to: 'goodbye->say_goodbye'
78
80
  end
79
81
 
80
82
  flow :goodbye do
@@ -89,9 +91,9 @@ class FlowMap
89
91
  end
90
92
  ```
91
93
 
92
- Here we have defined three flows: `hello`, `goodbye`, and `catch_all`. For the most part, these are default flows that are generated automatically when you create a new bot but we have made a few changes to highlight some functionality.
94
+ Here we have defined three flows: `hello`, `goodbye`, and `catch_all`. These are the default flows that are generated for you when you create a new bot. We have made a few changes above to highlight some functionality.
93
95
 
94
- In the `hello` flow, the second state asks a user for their name. In the third state of `hello`, you see another option: `fails_to`. This is used to tell Stealth to return the user to the specified state if the `get_name` state raises an error or fails in another way. There are more details in the `CatchAll` section below.
96
+ Each flow consists of an arbitrary number of states. These states should each have a corresponding controller action by the same name. States also support two additional options: `fails_to` and `redirects_to` which we explain below.
95
97
 
96
98
  ## Default Flows
97
99
 
@@ -109,6 +111,20 @@ Error handling is one of the most important parts of building great bots. We rec
109
111
 
110
112
  See the Catch All (#catchalls) section for more information on how Stealth handles `catch_all` flows.
111
113
 
114
+ ## fails_to
115
+
116
+ The `fails_to` option allows you to specify a state that a user should be redirected to in case of an error. The `CatchAllsController` will still be responsible for determining how to handle the error, but by specifying a `fails_to` state here, the `CatchAllsController` is able to redirect accordingly.
117
+
118
+ A freshly generated bot will contain sample `CatchAll` code for redirecting a user to a `fails_to` state.
119
+
120
+ The `fails_to` option takes a state name (string or symbol) or a session key. See [Redis Backed Sessions](#sessions.redis_backed_sessions) (or in the FlowMap example above) for more info about session keys. By specifying a session key, you can fail to a completely different flow from the one where the error occurred.
121
+
122
+ ## redirects_to
123
+
124
+ The `redirects_to` option allows you specify a state that a user should be redirected to. This is useful if you have deprecated a state where existing users may still have open sessions pointing to the state. When a user returns to your bot, they will be redirected to the flow and state specified by this option.
125
+
126
+ Like `fails_to` above, the `redirects_to` option takes a state name (string or symbol) or a session key. See [Redis Backed Sessions](#sessions.redis_backed_sessions) (or in the FlowMap example above) for more info about session keys. By specifying a session key, you can fail to a completely different flow from the one where the error occurred.
127
+
112
128
  ## Say, Ask, Get
113
129
 
114
130
  Stealth recommends you use the `say`, `ask`, and `get` prefix for your flow state names. It's not required, but it is a convention we have found helpful to keep state names under control. It also helps other developers on your team follow along more easily.
data/docs/04-sessions.md CHANGED
@@ -15,3 +15,15 @@ User sessions are stored in Redis. Each session is a lightweight key-value pair.
15
15
  So if for example a user with ID `100023838288224423` is currently at the `hello` flow and `ask_name` state, the value for the key would be: `hello->ask_name`.
16
16
 
17
17
  You likely won't be interacting with sessions directly since Stealth manages it automatically for you. We just present it here for clarity into how sessions work.
18
+
19
+ ## Session Expiration
20
+
21
+ By default, Stealth will not expire your user sessions. If you want your sessions to expire, you can set the `session_ttl` option:
22
+
23
+ ```ruby
24
+ Stealth.config.session_ttl = 2_592_000 # seconds (30 * 24 * 60 * 60)
25
+ ```
26
+
27
+ The above configuration setting will result in stale keys expiring in 30 days. Sessions that have been accessed reset the TTL (time to live). If you set a value of zero (the default), session expiration will be disabled.
28
+
29
+ If a user returns to your bot after their session has expired, they will start again in the `Hello` flow or whatever you have defined to be your first flow.
@@ -110,7 +110,7 @@ class ContactsController < BotController
110
110
  end
111
111
  ```
112
112
 
113
- This would render the reply contained in `replies/contacts/say_contact_us.yml`.
113
+ This would render the reply contained in `replies/contacts/say_contact_us.yml`. See [Reply Variants](#variants) for additional naming options.
114
114
 
115
115
  ## `step_to_in`
116
116
 
@@ -174,6 +174,6 @@ Returns `true` or `false` depending on whether or not the `current_message` cont
174
174
 
175
175
  Returns `true` or `false` depending on whether or not the `current_message` contains attachments.
176
176
 
177
- ### current_user_id
177
+ ### current_session_id (previously current_user_id)
178
178
 
179
- A convenience method for accessing the user's ID.
179
+ A convenience method for accessing the session's ID.
data/docs/07-replies.md CHANGED
@@ -2,7 +2,7 @@
2
2
  title: Replies
3
3
  ---
4
4
 
5
- Stealth replies can send one or more replies to a user. Reply types are dependent on the specific messaging service you're using. Each service integration will detail it's supported reply types in it's respective docs.
5
+ Stealth replies can send one or more replies to a user. The supported reply types will depend on the specific messaging service you're using. Each service integration will detail it's supported reply types in it's respective docs.
6
6
 
7
7
  However, here is a generic reply using text, delays, and suggestions.
8
8
 
@@ -22,6 +22,22 @@ However, here is a generic reply using text, delays, and suggestions.
22
22
  - text: "No"
23
23
  ```
24
24
 
25
+ ## Reply Variants
26
+
27
+ By default, Stealth will look for your replies in the folder corresponding to your controller name. So, for example, if you have a `MessagesController`, Stealth will look for replies in `bot/replies/messages`.
28
+
29
+ If you have an action named `say_hello`, it will look for a reply file named `bot/replies/messages/say_hello.yml.erb` first, and then if that is not found, it will look for `bot/replies/messages/say_hello.yml`. If neither of these files are found, Stealth will raise a `Stealth::Errors::ReplyNotFound`.
30
+
31
+ In addition to these two naming conventions, Stealth 1.1+ supports Reply Variants. By adding the name of the service to your reply filename, Stealth will reply to users from that service using the designated reply file. That's a mouthful. Let's try an example.
32
+
33
+ For example, if the bot is replying to a message via an action called `hello`:
34
+
35
+ Facebook users would receive the reply in `hello.yml+facebook.erb`.
36
+ Twilio SMS users would receive the reply in `hello.yml+twilio.erb`.
37
+ Every other service would receive the reply in `hello.yml.erb`.
38
+
39
+ This allows you to take advantage of things like Facebook Messenger Cards while still maintaining compatibility for users using SMS.
40
+
25
41
  ## Format
26
42
 
27
43
  Stealth reply templates are written in YAML. Stealth doesn't use advanced YAML features, but we do recommend you familiarize yourself with the syntax. In the above reply example, you should be able to see there are 5 replies included in the reply file.
@@ -58,6 +74,31 @@ Delays are a common pattern of chatbot design. After a block of text, it's recom
58
74
 
59
75
  Stealth will pause for the duration specified. For service integrations that support it (like Facebook), Stealth will send a typing indicator while it is paused.
60
76
 
77
+ ### Dynamic Delays
78
+
79
+ Rather than specifying an explicit delay duration, you can optionally choose to specify a dynamic duration:
80
+
81
+ ```yaml
82
+ - reply_type: delay
83
+ duration: dynamic
84
+ ```
85
+
86
+ The dynamic delay uses a heuristic to dynamically determine the length of the delay. The previous message sent to the user is examined and depending on it's type and text length (in the case of text replies), an optimal duration is computed.
87
+
88
+ If you find that the dynamic delays are too fast for your taste, you can slow them down by setting the multiplier value to something between 0 and 1:
89
+
90
+ ```ruby
91
+ Stealth.config.dynamic_delay_muliplier = 0.5
92
+ ```
93
+
94
+ If you find them to be too slow, you can speed them up by setting the multipler to a value greater than 1:
95
+
96
+ ```ruby
97
+ Stealth.config.dynamic_delay_muliplier = 2.5
98
+ ```
99
+
100
+ You can set this option by setting the above value in an intializer file, i.e., `config/dynamic_delay_config.rb`.
101
+
61
102
  ## Naming Conventions
62
103
 
63
104
  Replies are named after a flow's state (which is also the controller's action). So for a given controller:
data/lib/stealth/base.rb CHANGED
@@ -37,6 +37,11 @@ module Stealth
37
37
  @configuration
38
38
  end
39
39
 
40
+ def self.set_config_defaults(config)
41
+ config.dynamic_delay_muliplier = 1.0
42
+ config.session_ttl = 0
43
+ end
44
+
40
45
  # Loads the services.yml configuration unless one has already been loaded
41
46
  def self.load_services_config(services_yaml)
42
47
  @semaphore ||= Mutex.new
@@ -49,7 +54,10 @@ module Stealth
49
54
  raise Stealth::Errors::ConfigurationError, "Could not find services.yml configuration for #{env} environment"
50
55
  end
51
56
 
52
- Stealth::Configuration.new(services_config[env])
57
+ config = Stealth::Configuration.new(services_config[env])
58
+ set_config_defaults(config)
59
+
60
+ config
53
61
  end
54
62
  end
55
63
  end
@@ -72,10 +80,12 @@ module Stealth
72
80
  require File.join(Stealth.root, 'config', 'flow_map')
73
81
  require_directory("bot")
74
82
 
75
- if ENV['DATABASE_URL'].present? && defined?(ActiveRecord)
76
- ActiveRecord::Base.establish_connection(ENV['DATABASE_URL'])
77
- else
78
- ActiveRecord::Base.establish_connection(YAML::load_file("config/database.yml")[Stealth.env])
83
+ if defined?(ActiveRecord)
84
+ if ENV['DATABASE_URL'].present?
85
+ ActiveRecord::Base.establish_connection(ENV['DATABASE_URL'])
86
+ else
87
+ ActiveRecord::Base.establish_connection(YAML::load_file("config/database.yml")[Stealth.env])
88
+ end
79
89
  end
80
90
  end
81
91
 
@@ -107,10 +117,14 @@ require 'stealth/controller/callbacks'
107
117
  require 'stealth/controller/replies'
108
118
  require 'stealth/controller/catch_all'
109
119
  require 'stealth/controller/helpers'
120
+ require 'stealth/controller/dynamic_delay'
110
121
  require 'stealth/controller/controller'
111
122
  require 'stealth/flow/base'
112
123
  require 'stealth/services/base_client'
113
- require 'stealth/migrations/configurator'
114
- require 'stealth/migrations/generators'
115
- require 'stealth/migrations/railtie_config'
116
- require 'stealth/migrations/tasks'
124
+
125
+ if defined?(ActiveRecord)
126
+ require 'stealth/migrations/configurator'
127
+ require 'stealth/migrations/generators'
128
+ require 'stealth/migrations/railtie_config'
129
+ require 'stealth/migrations/tasks'
130
+ end
@@ -48,7 +48,7 @@ module Stealth
48
48
  end
49
49
 
50
50
  def error_slug
51
- ['error', current_user_id, current_session.flow_string, current_session.state_string].join('-')
51
+ ['error', current_session_id, current_session.flow_string, current_session.state_string].join('-')
52
52
  end
53
53
 
54
54
  def calculate_catch_all_state(error_level)
@@ -5,17 +5,19 @@ module Stealth
5
5
  class Controller
6
6
 
7
7
  include Stealth::Controller::Callbacks
8
+ include Stealth::Controller::DynamicDelay
8
9
  include Stealth::Controller::Replies
9
10
  include Stealth::Controller::CatchAll
10
11
  include Stealth::Controller::Helpers
11
12
 
12
13
  attr_reader :current_message, :current_user_id, :current_flow,
13
- :current_service, :flow_controller, :action_name
14
+ :current_service, :flow_controller, :action_name,
15
+ :current_session_id
14
16
 
15
17
  def initialize(service_message:, current_flow: nil)
16
18
  @current_message = service_message
17
19
  @current_service = service_message.service
18
- @current_user_id = service_message.sender_id
20
+ @current_user_id = @current_session_id = service_message.sender_id
19
21
  @current_flow = current_flow
20
22
  @progressed = false
21
23
  end
@@ -47,17 +49,27 @@ module Stealth
47
49
  end
48
50
 
49
51
  def current_session
50
- @current_session ||= Stealth::Session.new(user_id: current_user_id)
52
+ @current_session ||= Stealth::Session.new(user_id: current_session_id)
51
53
  end
52
54
 
53
55
  def previous_session
54
- @previous_session ||= Stealth::Session.new(user_id: current_user_id, previous: true)
56
+ @previous_session ||= Stealth::Session.new(user_id: current_session_id, previous: true)
55
57
  end
56
58
 
57
59
  def action(action: nil)
58
60
  @action_name = action
59
61
  @action_name ||= current_session.state_string
60
62
 
63
+ # Check if the user needs to be redirected
64
+ if current_session.flow.current_state.redirects_to.present?
65
+ Stealth::Logger.l(
66
+ topic: "redirect",
67
+ message: "From #{current_session.session} to #{current_session.flow.current_state.redirects_to.session}"
68
+ )
69
+ step_to(session: current_session.flow.current_state.redirects_to)
70
+ return
71
+ end
72
+
61
73
  run_callbacks :action do
62
74
  begin
63
75
  flow_controller.send(@action_name)
@@ -76,8 +88,8 @@ module Stealth
76
88
  raise ArgumentError, "Please specify your step_to_in `delay` parameter using ActiveSupport::Duration, e.g. `1.day` or `5.hours`"
77
89
  end
78
90
 
79
- Stealth::ScheduledReplyJob.perform_in(delay, current_service, current_user_id, flow, state)
80
- Stealth::Logger.l(topic: "session", message: "User #{current_user_id}: scheduled session step to #{flow}->#{state} in #{delay} seconds")
91
+ Stealth::ScheduledReplyJob.perform_in(delay, current_service, current_session_id, flow, state)
92
+ Stealth::Logger.l(topic: "session", message: "User #{current_session_id}: scheduled session step to #{flow}->#{state} in #{delay} seconds")
81
93
  end
82
94
 
83
95
  def step_to_at(timestamp, session: nil, flow: nil, state: nil)
@@ -87,8 +99,8 @@ module Stealth
87
99
  raise ArgumentError, "Please specify your step_to_at `timestamp` parameter as a DateTime"
88
100
  end
89
101
 
90
- Stealth::ScheduledReplyJob.perform_at(timestamp, current_service, current_user_id, flow, state)
91
- Stealth::Logger.l(topic: "session", message: "User #{current_user_id}: scheduled session step to #{flow}->#{state} at #{timestamp.iso8601}")
102
+ Stealth::ScheduledReplyJob.perform_at(timestamp, current_service, current_session_id, flow, state)
103
+ Stealth::Logger.l(topic: "session", message: "User #{current_session_id}: scheduled session step to #{flow}->#{state} at #{timestamp.iso8601}")
92
104
  end
93
105
 
94
106
  def step_to(session: nil, flow: nil, state: nil)
@@ -104,7 +116,7 @@ module Stealth
104
116
  private
105
117
 
106
118
  def update_session(flow:, state:)
107
- @current_session = Stealth::Session.new(user_id: current_user_id)
119
+ @current_session = Stealth::Session.new(user_id: current_session_id)
108
120
  @progressed = :updated_session
109
121
  @current_session.set(flow: flow, state: state)
110
122
  end
@@ -0,0 +1,64 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module Stealth
5
+ class Controller
6
+
7
+ module DynamicDelay
8
+ extend ActiveSupport::Concern
9
+
10
+ SHORT_DELAY = 3.0
11
+ STANDARD_DELAY = 4.3
12
+ LONG_DELAY = 7.0
13
+
14
+ included do
15
+ def dynamic_delay(service_replies:, position:)
16
+ if position <= 0
17
+ calculate_delay(previous_reply: {})
18
+ else
19
+ calculate_delay(previous_reply: service_replies[position - 1])
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def calculate_delay(previous_reply:)
26
+ case previous_reply['reply_type']
27
+ when 'text'
28
+ calculate_delay_from_text(previous_reply['text'])
29
+ when 'image'
30
+ STANDARD_DELAY
31
+ when 'audio'
32
+ STANDARD_DELAY
33
+ when 'video'
34
+ STANDARD_DELAY
35
+ when 'file'
36
+ STANDARD_DELAY
37
+ when 'cards'
38
+ STANDARD_DELAY
39
+ when 'list'
40
+ STANDARD_DELAY
41
+ when nil
42
+ SHORT_DELAY
43
+ else
44
+ SHORT_DELAY
45
+ end
46
+ end
47
+ end
48
+
49
+ def calculate_delay_from_text(text)
50
+ case text.size
51
+ when 0..55
52
+ SHORT_DELAY
53
+ when 56..140
54
+ STANDARD_DELAY
55
+ when 141..256
56
+ STANDARD_DELAY * 1.5
57
+ else
58
+ LONG_DELAY
59
+ end
60
+ end
61
+ end
62
+
63
+ end
64
+ end