demo_mode 3.6.0 → 3.7.1
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.
- checksums.yaml +4 -4
- data/README.md +62 -0
- data/app/controllers/demo_mode/sessions_controller.rb +3 -4
- data/app/jobs/demo_mode/account_generation_job.rb +2 -1
- data/app/jobs/demo_mode/pool_hydration_job.rb +44 -0
- data/app/models/demo_mode/session.rb +57 -8
- data/db/migrate/20260326000000_add_claimed_at_to_demo_mode_sessions.rb +20 -0
- data/db/migrate/20260331000000_rename_successful_to_in_use_in_demo_mode_sessions.rb +11 -0
- data/db/migrate/20260409000000_add_persona_checksum_to_demo_mode_sessions.rb +9 -0
- data/lib/demo_mode/config.rb +9 -5
- data/lib/demo_mode/persona.rb +19 -3
- data/lib/demo_mode/version.rb +1 -1
- data/lib/demo_mode.rb +5 -0
- metadata +20 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 268e07b92704ae904b434f1c2865a8a21ece4adfc51775339aa2f5243a7e4624
|
|
4
|
+
data.tar.gz: fe8974ba779ad030bf120811158e19f767c736c3c4faecf1c59a40dd019c7298
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f0da8528e1c63ea16b3ca1f9c1e3a6fb1731ea4e5e406bb93e5512422e311139a4e27c1fe6fc38ec45dc9b9be10223625ad15455b73765025d31480a4b8f0fe4
|
|
7
|
+
data.tar.gz: 75382c2d121e8308a9155280320ce94e3a7a04e0021161c3145a78ec6b6f8ec31815c15562197031cc55a757791cde01be774d4bc1a8a2312853dd6efa8d1bad
|
data/README.md
CHANGED
|
@@ -48,6 +48,8 @@ To learn more about how we use `demo_mode` at **Betterment**, check out :sparkle
|
|
|
48
48
|
- [Non-User Personas](#non-user-personas)
|
|
49
49
|
- [FactoryBot `sequence` extension](#factorybot-sequence-extension)
|
|
50
50
|
- [Database-backed sequences](#database-backed-sequences)
|
|
51
|
+
- [Persona Pooling](#persona-pooling)
|
|
52
|
+
- [Disabling Personas or Variants](#disabling-personas-or-variants)
|
|
51
53
|
- [Deploying a demo environment to the cloud](#deploying-a-demo-environment-to-the-cloud)
|
|
52
54
|
- [How to avoid breaking your new "demo" env](#how-to-avoid-breaking-your-new-demo-env)
|
|
53
55
|
- [How to Contribute](#how-to-contribute)
|
|
@@ -571,6 +573,66 @@ You can check this setting with:
|
|
|
571
573
|
CleverSequence.enforce_sequences_exist? # => false (default)
|
|
572
574
|
```
|
|
573
575
|
|
|
576
|
+
### Persona Pooling
|
|
577
|
+
|
|
578
|
+
By default, Demo Mode generates persona accounts on-demand when a user clicks the persona picker. This means each click triggers a background job, and the user waits on a loading spinner. With persona pooling, accounts are pre-generated in the background so that sign-in is near-instant.
|
|
579
|
+
|
|
580
|
+
To enable pooling, schedule `DemoMode::PoolHydrationJob` to run periodically — every few minutes is a good starting point:
|
|
581
|
+
|
|
582
|
+
```ruby
|
|
583
|
+
# Enqueue via your scheduler (e.g. Sidekiq-Cron, GoodJob, solid_queue, etc.)
|
|
584
|
+
DemoMode::PoolHydrationJob.perform_later
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
When called without arguments, the job runs in "orchestrator" mode: it checks the current pool depth for every persona+variant combination and enqueues individual hydration jobs for any that fall below the target size. Those leaf jobs each create one pre-generated session, then re-enqueue themselves until the target is reached.
|
|
588
|
+
|
|
589
|
+
```ruby
|
|
590
|
+
DemoMode.configure do
|
|
591
|
+
minimum_pool_size 10
|
|
592
|
+
end
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
When a user selects a persona, Demo Mode atomically claims a pre-generated session from the pool ("pool hit") or falls back to on-demand generation if the pool is empty.
|
|
596
|
+
|
|
597
|
+
**Automatic invalidation:** Pool sessions are tied to a checksum of the persona file. If you change a persona's definition, stale sessions are automatically skipped and fresh ones are generated on the next hydration run — no manual cleanup required.
|
|
598
|
+
|
|
599
|
+
**Monitoring:** Demo Mode emits `ActiveSupport::Notifications` events you can subscribe to:
|
|
600
|
+
|
|
601
|
+
| Event | Emitted when | Notable payload |
|
|
602
|
+
|---|---|---|
|
|
603
|
+
| `demo_mode.pool.depth` | Each orchestration run, per persona+variant | `persona_name`, `variant`, `value` (sessions needed to reach target) |
|
|
604
|
+
| `demo_mode.session.claimed` | Each sign-in | `persona_name`, `variant`, `pool_hit: true/false` |
|
|
605
|
+
|
|
606
|
+
A `pool_hit: false` on `demo_mode.session.claimed` means the pool was empty at sign-in time and generation happened on-demand — a signal to increase `minimum_pool_size` or run hydration more frequently.
|
|
607
|
+
|
|
608
|
+
### Disabling Personas or Variants
|
|
609
|
+
|
|
610
|
+
You can conditionally disable a persona or a variant by providing an `enabled` block. When the block returns false, the persona (or variant) is hidden from the picker UI, excluded from pool hydration, and treated as non-existent for session creation.
|
|
611
|
+
|
|
612
|
+
**Persona-level:**
|
|
613
|
+
|
|
614
|
+
```ruby
|
|
615
|
+
DemoMode.add_persona 'Beta Feature' do
|
|
616
|
+
enabled { FeatureFlags.beta_enabled? }
|
|
617
|
+
features << 'Access to beta'
|
|
618
|
+
sign_in_as { FactoryBot.create(:user, :beta) }
|
|
619
|
+
end
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
**Variant-level:**
|
|
623
|
+
|
|
624
|
+
```ruby
|
|
625
|
+
DemoMode.add_persona 'Investor' do
|
|
626
|
+
variant('default') { sign_in_as { FactoryBot.create(:investor) } }
|
|
627
|
+
variant('accredited') do
|
|
628
|
+
enabled { ENV['ACCREDITED_ENABLED'].present? }
|
|
629
|
+
sign_in_as { FactoryBot.create(:investor, :accredited) }
|
|
630
|
+
end
|
|
631
|
+
end
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
Common use cases include feature-flag-gated personas, environment-specific personas (e.g. only in staging), and temporarily hiding a persona without deleting its definition.
|
|
635
|
+
|
|
574
636
|
## Deploying a demo environment to the cloud
|
|
575
637
|
|
|
576
638
|
This gem truly shines when used to deploy a "demo" version of
|
|
@@ -24,8 +24,7 @@ module DemoMode
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def create
|
|
27
|
-
@session = Session.
|
|
28
|
-
@session.save_and_generate_account_later!(**options_params.to_unsafe_h.deep_symbolize_keys)
|
|
27
|
+
@session = Session.claim_for(**create_params.to_h.symbolize_keys, **options_params.to_h.deep_symbolize_keys)
|
|
29
28
|
@session.reload
|
|
30
29
|
session[:demo_session] = { 'id' => @session.id, 'last_request_at' => Time.zone.now }
|
|
31
30
|
respond_to do |f|
|
|
@@ -51,12 +50,12 @@ module DemoMode
|
|
|
51
50
|
|
|
52
51
|
def render_signinable_json
|
|
53
52
|
if @session.signinable.blank?
|
|
54
|
-
render json: { id: @session.id, processing: @session.processing?, status: @session.status }
|
|
53
|
+
render json: { id: @session.id, processing: @session.processing?, status: @session.status.to_s }
|
|
55
54
|
else
|
|
56
55
|
render json: {
|
|
57
56
|
id: @session.id,
|
|
58
57
|
processing: @session.processing?,
|
|
59
|
-
status: @session.status,
|
|
58
|
+
status: @session.status.to_s,
|
|
60
59
|
username: @session.signinable_username,
|
|
61
60
|
password: @session.signinable_password,
|
|
62
61
|
metadata: @session.signinable_metadata,
|
|
@@ -8,7 +8,8 @@ module DemoMode
|
|
|
8
8
|
raise "Unknown persona: #{session.persona_name}" if persona.blank?
|
|
9
9
|
|
|
10
10
|
signinable = persona.generate!(variant: session.variant, password: session.signinable_password, options: options)
|
|
11
|
-
session.
|
|
11
|
+
new_status = session.claimed_at? ? 'in_use' : 'available'
|
|
12
|
+
session.update!(signinable: signinable, status: new_status, persona_checksum: persona.file_checksum)
|
|
12
13
|
end
|
|
13
14
|
rescue StandardError => e
|
|
14
15
|
session.update!(status: 'failed')
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DemoMode
|
|
4
|
+
class PoolHydrationJob < DemoMode.base_job_name.constantize
|
|
5
|
+
def perform(persona_name: nil, variant: nil, count: nil)
|
|
6
|
+
if persona_name && variant
|
|
7
|
+
hydrate(persona_name, variant, count)
|
|
8
|
+
else
|
|
9
|
+
orchestrate(count)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def orchestrate(count)
|
|
16
|
+
target = count || DemoMode.minimum_pool_size
|
|
17
|
+
|
|
18
|
+
DemoMode.personas.each do |persona|
|
|
19
|
+
persona.variants.each_key do |v|
|
|
20
|
+
available = DemoMode::Session.available_for(persona.name, v).count
|
|
21
|
+
ActiveSupport::Notifications.instrument('demo_mode.pool.depth',
|
|
22
|
+
persona_name: persona.name, variant: v, value: target - available)
|
|
23
|
+
next if available >= target
|
|
24
|
+
|
|
25
|
+
PoolHydrationJob.perform_later(persona_name: persona.name, variant: v, count: count)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def hydrate(persona_name, variant, count)
|
|
31
|
+
return unless DemoMode.personas.any? { |p| p.name.to_s == persona_name.to_s && p.variants.key?(variant) }
|
|
32
|
+
|
|
33
|
+
target = count || DemoMode.minimum_pool_size
|
|
34
|
+
return if DemoMode::Session.available_for(persona_name, variant).count >= target
|
|
35
|
+
|
|
36
|
+
DemoMode::Session.new(persona_name: persona_name, variant: variant, pool_session: true)
|
|
37
|
+
.save_and_generate_account!
|
|
38
|
+
|
|
39
|
+
if DemoMode::Session.available_for(persona_name, variant).count < target
|
|
40
|
+
PoolHydrationJob.perform_later(persona_name: persona_name, variant: variant, count: count)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -2,17 +2,38 @@
|
|
|
2
2
|
|
|
3
3
|
module DemoMode
|
|
4
4
|
class Session < ActiveRecord::Base
|
|
5
|
-
|
|
5
|
+
include ::SteadyState
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
DEFAULT_VARIANT = 'default'
|
|
8
|
+
|
|
9
|
+
attribute :variant, default: DEFAULT_VARIANT
|
|
10
|
+
|
|
11
|
+
attr_accessor :pool_session
|
|
12
|
+
|
|
13
|
+
steady_state :status do
|
|
14
|
+
state 'processing', default: true
|
|
15
|
+
state 'available', from: 'processing'
|
|
16
|
+
state 'in_use', from: %w(processing available)
|
|
17
|
+
state 'failed', from: 'processing'
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
scope :unclaimed, -> { where(claimed_at: nil) }
|
|
21
|
+
scope :claimed, -> { where.not(claimed_at: nil) }
|
|
22
|
+
scope :available_for, ->(persona_name, variant) {
|
|
23
|
+
persona = DemoMode.personas.find { |p| p.name.to_s == persona_name.to_s }
|
|
24
|
+
available.unclaimed.where(persona_name: persona_name, variant: variant, persona_checksum: persona&.file_checksum)
|
|
25
|
+
}
|
|
8
26
|
|
|
9
27
|
validates :persona_name, :variant, presence: true
|
|
10
28
|
validates :persona, presence: { message: :required }, on: :create, if: :persona_name?
|
|
11
|
-
|
|
29
|
+
validates :claimed_at, absence: true, if: :available?
|
|
30
|
+
validates :claimed_at, presence: true, if: :in_use?
|
|
31
|
+
validate :terminal_status_requires_signinable
|
|
12
32
|
|
|
13
33
|
belongs_to :signinable, polymorphic: true, optional: true
|
|
14
34
|
|
|
15
35
|
before_create :set_password!
|
|
36
|
+
before_create :claim_if_not_pooled!
|
|
16
37
|
|
|
17
38
|
delegate :begin_demo,
|
|
18
39
|
:custom_sign_in?,
|
|
@@ -26,12 +47,36 @@ module DemoMode
|
|
|
26
47
|
end
|
|
27
48
|
|
|
28
49
|
def signinable_metadata
|
|
29
|
-
|
|
50
|
+
available? || in_use? ? metadata.call(self) : {}
|
|
30
51
|
end
|
|
31
52
|
|
|
32
53
|
# Heads up: finding a persona is not guaranteed (e.g. past sessions)
|
|
33
54
|
def persona
|
|
34
|
-
DemoMode.personas.find { |p| p.name.to_s == persona_name.to_s }
|
|
55
|
+
DemoMode.personas.find { |p| p.name.to_s == persona_name.to_s && p.variants.key?(variant) }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.claim_for(persona_name:, variant: DEFAULT_VARIANT, **generation_opts)
|
|
59
|
+
pool_hit = false
|
|
60
|
+
session = transaction do
|
|
61
|
+
existing = available_for(persona_name, variant).lock.first
|
|
62
|
+
pool_hit = existing.present?
|
|
63
|
+
(existing || new(persona_name: persona_name, variant: variant)).tap do |s|
|
|
64
|
+
s.claim!
|
|
65
|
+
AccountGenerationJob.perform_later(s, **generation_opts) if s.signinable.blank?
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
ActiveSupport::Notifications.instrument('demo_mode.session.claimed',
|
|
69
|
+
persona_name: persona_name, variant: variant, pool_hit: pool_hit)
|
|
70
|
+
session
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def claim!
|
|
74
|
+
if new_record?
|
|
75
|
+
self.claimed_at = Time.zone.now
|
|
76
|
+
save!
|
|
77
|
+
else
|
|
78
|
+
lock!.update!(claimed_at: Time.zone.now, status: 'in_use')
|
|
79
|
+
end
|
|
35
80
|
end
|
|
36
81
|
|
|
37
82
|
def save_and_generate_account!(**options)
|
|
@@ -54,9 +99,13 @@ module DemoMode
|
|
|
54
99
|
self.signinable_password ||= DemoMode.current_password
|
|
55
100
|
end
|
|
56
101
|
|
|
57
|
-
def
|
|
58
|
-
|
|
59
|
-
|
|
102
|
+
def claim_if_not_pooled!
|
|
103
|
+
self.claimed_at ||= Time.zone.now unless pool_session
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def terminal_status_requires_signinable
|
|
107
|
+
if (available? || in_use?) && signinable.blank?
|
|
108
|
+
errors.add(:status, 'cannot be available or in_use if signinable is not present')
|
|
60
109
|
end
|
|
61
110
|
end
|
|
62
111
|
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddClaimedAtToDemoModeSessions < ActiveRecord::Migration[5.1]
|
|
4
|
+
disable_ddl_transaction!
|
|
5
|
+
|
|
6
|
+
def change
|
|
7
|
+
add_column :demo_mode_sessions, :claimed_at, :datetime
|
|
8
|
+
|
|
9
|
+
reversible do |dir|
|
|
10
|
+
dir.up { safety_assured { execute "UPDATE demo_mode_sessions SET claimed_at = created_at" } }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
safety_assured do
|
|
14
|
+
add_index :demo_mode_sessions,
|
|
15
|
+
%i(persona_name variant status claimed_at),
|
|
16
|
+
name: :index_demo_mode_sessions_on_pool_lookup,
|
|
17
|
+
algorithm: :concurrently
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class RenameSuccessfulToInUseInDemoModeSessions < ActiveRecord::Migration[5.1]
|
|
4
|
+
def up
|
|
5
|
+
safety_assured { execute "UPDATE demo_mode_sessions SET status = 'in_use' WHERE status = 'successful'" }
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def down
|
|
9
|
+
safety_assured { execute "UPDATE demo_mode_sessions SET status = 'successful' WHERE status = 'in_use'" }
|
|
10
|
+
end
|
|
11
|
+
end
|
data/lib/demo_mode/config.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'digest'
|
|
3
4
|
require_relative 'concerns/configurable'
|
|
4
5
|
|
|
5
6
|
module DemoMode
|
|
@@ -14,6 +15,7 @@ module DemoMode
|
|
|
14
15
|
configurable_value(:personas_path) { 'config/personas' }
|
|
15
16
|
configurable_value(:session_timeout) { 30.minutes }
|
|
16
17
|
configurable_value(:log_level) { :debug }
|
|
18
|
+
configurable_value(:minimum_pool_size) { 5 }
|
|
17
19
|
configurable_boolean(:display_credentials)
|
|
18
20
|
configurations << :stylesheets
|
|
19
21
|
configurations << :logo
|
|
@@ -21,7 +23,6 @@ module DemoMode
|
|
|
21
23
|
configurations << :icon
|
|
22
24
|
configurations << :password
|
|
23
25
|
configurations << :around_persona_generation
|
|
24
|
-
configurations << :personas
|
|
25
26
|
configurations << :sign_up_path
|
|
26
27
|
configurations << :sign_in_path
|
|
27
28
|
|
|
@@ -120,13 +121,16 @@ module DemoMode
|
|
|
120
121
|
private
|
|
121
122
|
|
|
122
123
|
def auto_load_personas!
|
|
123
|
-
Rails.root.glob("#{personas_path}/**/*.rb").sort.each do |
|
|
124
|
-
raise <<~ERROR if File.readlines(
|
|
125
|
-
This file does not define a persona: #{
|
|
124
|
+
Rails.root.glob("#{personas_path}/**/*.rb").sort.each do |persona_file|
|
|
125
|
+
raise <<~ERROR if File.readlines(persona_file).grep(/DemoMode\.add_persona/).empty?
|
|
126
|
+
This file does not define a persona: #{persona_file}\n
|
|
126
127
|
Please use `DemoMode.add_persona`
|
|
127
128
|
ERROR
|
|
128
129
|
|
|
129
|
-
|
|
130
|
+
checksum = Digest::SHA256.hexdigest(File.read(persona_file))
|
|
131
|
+
before_count = @personas.length
|
|
132
|
+
load(persona_file)
|
|
133
|
+
@personas[before_count..].each { |p| p.file_checksum = checksum }
|
|
130
134
|
end
|
|
131
135
|
end
|
|
132
136
|
end
|
data/lib/demo_mode/persona.rb
CHANGED
|
@@ -6,7 +6,7 @@ module DemoMode
|
|
|
6
6
|
class Persona
|
|
7
7
|
include ActiveModel::Model
|
|
8
8
|
|
|
9
|
-
attr_accessor :name
|
|
9
|
+
attr_accessor :name, :file_checksum
|
|
10
10
|
|
|
11
11
|
validates :name, presence: true
|
|
12
12
|
validate :persona_must_have_at_least_one_feature
|
|
@@ -53,13 +53,13 @@ module DemoMode
|
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
def variant(name, &block)
|
|
56
|
-
variants[name] = Variant.new(name: name).tap do |v|
|
|
56
|
+
(@variants ||= {}.with_indifferent_access)[name] = Variant.new(name: name).tap do |v|
|
|
57
57
|
v.instance_eval(&block)
|
|
58
58
|
end
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
def variants
|
|
62
|
-
@variants ||= {}.with_indifferent_access
|
|
62
|
+
(@variants ||= {}.with_indifferent_access).select { |_, v| v.enabled? }
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
def generate!(variant: :default, password: nil, options: {})
|
|
@@ -89,6 +89,14 @@ module DemoMode
|
|
|
89
89
|
true
|
|
90
90
|
end
|
|
91
91
|
|
|
92
|
+
def enabled(&block)
|
|
93
|
+
@enabled_condition = block
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def enabled?
|
|
97
|
+
@enabled_condition ? @enabled_condition.call : true
|
|
98
|
+
end
|
|
99
|
+
|
|
92
100
|
def callout(callout = true) # rubocop:disable Style/OptionalBooleanParameter
|
|
93
101
|
@callout = callout
|
|
94
102
|
end
|
|
@@ -138,6 +146,14 @@ module DemoMode
|
|
|
138
146
|
@signinable_generator = signinable_generator
|
|
139
147
|
end
|
|
140
148
|
|
|
149
|
+
def enabled(&block)
|
|
150
|
+
@enabled_condition = block
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def enabled?
|
|
154
|
+
@enabled_condition ? @enabled_condition.call : true
|
|
155
|
+
end
|
|
156
|
+
|
|
141
157
|
def title
|
|
142
158
|
name.is_a?(Symbol) ? name.to_s.titleize : name.to_s
|
|
143
159
|
end
|
data/lib/demo_mode/version.rb
CHANGED
data/lib/demo_mode.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'rails'
|
|
4
|
+
require 'steady_state'
|
|
4
5
|
require 'demo_mode/version'
|
|
5
6
|
require 'demo_mode/clever_sequence'
|
|
6
7
|
require 'demo_mode/config'
|
|
@@ -35,6 +36,10 @@ module DemoMode
|
|
|
35
36
|
configuration.persona(name, &)
|
|
36
37
|
end
|
|
37
38
|
|
|
39
|
+
def personas
|
|
40
|
+
configuration.personas.select(&:enabled?)
|
|
41
|
+
end
|
|
42
|
+
|
|
38
43
|
def callout_personas
|
|
39
44
|
personas.select(&:callout?)
|
|
40
45
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: demo_mode
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.7.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nathan Griffith
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-04-17 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: actionpack
|
|
@@ -124,6 +124,20 @@ dependencies:
|
|
|
124
124
|
- - "<"
|
|
125
125
|
- !ruby/object:Gem::Version
|
|
126
126
|
version: '8.2'
|
|
127
|
+
- !ruby/object:Gem::Dependency
|
|
128
|
+
name: steady_state
|
|
129
|
+
requirement: !ruby/object:Gem::Requirement
|
|
130
|
+
requirements:
|
|
131
|
+
- - ">="
|
|
132
|
+
- !ruby/object:Gem::Version
|
|
133
|
+
version: '0'
|
|
134
|
+
type: :runtime
|
|
135
|
+
prerelease: false
|
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
137
|
+
requirements:
|
|
138
|
+
- - ">="
|
|
139
|
+
- !ruby/object:Gem::Version
|
|
140
|
+
version: '0'
|
|
127
141
|
- !ruby/object:Gem::Dependency
|
|
128
142
|
name: actionmailer
|
|
129
143
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -327,6 +341,7 @@ files:
|
|
|
327
341
|
- app/controllers/demo_mode/application_controller.rb
|
|
328
342
|
- app/controllers/demo_mode/sessions_controller.rb
|
|
329
343
|
- app/jobs/demo_mode/account_generation_job.rb
|
|
344
|
+
- app/jobs/demo_mode/pool_hydration_job.rb
|
|
330
345
|
- app/models/demo_mode/session.rb
|
|
331
346
|
- app/views/demo_mode/sessions/_variant_dropdown.html.erb
|
|
332
347
|
- app/views/demo_mode/sessions/new.html.erb
|
|
@@ -337,6 +352,9 @@ files:
|
|
|
337
352
|
- db/migrate/20201111000000_add_demo_mode_sessions_variant.rb
|
|
338
353
|
- db/migrate/20210505000000_add_demo_mode_sessions_password.rb
|
|
339
354
|
- db/migrate/20250210222933_add_demo_mode_sessions_status.rb
|
|
355
|
+
- db/migrate/20260326000000_add_claimed_at_to_demo_mode_sessions.rb
|
|
356
|
+
- db/migrate/20260331000000_rename_successful_to_in_use_in_demo_mode_sessions.rb
|
|
357
|
+
- db/migrate/20260409000000_add_persona_checksum_to_demo_mode_sessions.rb
|
|
340
358
|
- lib/demo_mode.rb
|
|
341
359
|
- lib/demo_mode/clever_sequence.rb
|
|
342
360
|
- lib/demo_mode/clever_sequence/in_memory_backend.rb
|