demo_mode 4.0.1 → 4.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.
- checksums.yaml +4 -4
- data/README.md +41 -0
- data/app/views/demo_mode/sessions/_grouped_personas.html.erb +8 -0
- data/app/views/demo_mode/sessions/_persona_table.html.erb +28 -0
- data/app/views/demo_mode/sessions/_ungrouped_personas.html.erb +5 -0
- data/app/views/demo_mode/sessions/new.html.erb +7 -30
- data/lib/demo_mode/config.rb +21 -1
- data/lib/demo_mode/grouper.rb +45 -0
- data/lib/demo_mode/persona.rb +18 -1
- data/lib/demo_mode/version.rb +1 -1
- data/lib/demo_mode.rb +13 -0
- data/public/demo_mode/assets/demo_mode.css +10 -0
- data/public/demo_mode/assets/demo_mode.js +23 -7
- metadata +7 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 76f5606300169245566aec753daa16508d60cbaee8509a096234aa1a2ffd8cce
|
|
4
|
+
data.tar.gz: adffd7d7b4e688cf1c15ac1113d64f354a35dce5aa5754b694b7df3246636f32
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cc599fe84994691f54ecfe49af4e1b7f6e92561be78c26e0a8cf9e240ff1944e9d3c0a2825f75bd1a7b0e08ee5ac3cfac86009432c342354bfcf801b4dfa6272
|
|
7
|
+
data.tar.gz: b866a7902a4068fbc65400bda015908e8eae0a7c12141499cd24eedbbba8800b0de978da58c8f2fae0f1b9b6ba839dd28818d0e4f87291837e5584a317b8375a
|
data/README.md
CHANGED
|
@@ -39,6 +39,7 @@ To learn more about how we use `demo_mode` at **Betterment**, check out :sparkle
|
|
|
39
39
|
- [Installation](#installation)
|
|
40
40
|
- [App-Specific Setup](#app-specific-setup)
|
|
41
41
|
- [Defining Personas](#defining-personas)
|
|
42
|
+
- [Grouping Personas](#grouping-personas)
|
|
42
43
|
- [Customizing the Design](#customizing-the-design)
|
|
43
44
|
- [Optional Features](#optional-features)
|
|
44
45
|
- [The "Sign Up" Link](#the-sign-up-link)
|
|
@@ -273,6 +274,46 @@ end
|
|
|
273
274
|
If defined, the non-variant `sign_in_as` will show up as "default" in the
|
|
274
275
|
dropdown.
|
|
275
276
|
|
|
277
|
+
### Grouping Personas
|
|
278
|
+
|
|
279
|
+
When an app has many personas, the single table can become hard to scan. To
|
|
280
|
+
improve the end-user experience, you can organize your persona files into subfolders
|
|
281
|
+
(e.g. `config/personas/orders...`, `config/personas/advanced/...`) and then enable
|
|
282
|
+
the following setting to collapse those personas into labeled groupings:
|
|
283
|
+
|
|
284
|
+
```ruby
|
|
285
|
+
DemoMode.configure do
|
|
286
|
+
group_by_folder true
|
|
287
|
+
end
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Alternatively, you can set a group name directly on any persona:
|
|
291
|
+
|
|
292
|
+
```ruby
|
|
293
|
+
DemoMode.add_persona do
|
|
294
|
+
group 'Playwright testing'
|
|
295
|
+
# ...
|
|
296
|
+
end
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
To directly control page ordering, use the `groups` config:
|
|
300
|
+
|
|
301
|
+
```ruby
|
|
302
|
+
DemoMode.configure do
|
|
303
|
+
# Specify group order directly:
|
|
304
|
+
groups %w(transactions activity locked)
|
|
305
|
+
|
|
306
|
+
# ...or specify friendly labels along with the order:
|
|
307
|
+
groups(
|
|
308
|
+
'orders' => 'Orders',
|
|
309
|
+
'orders/checkout' => 'Checkout',
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
# If you prefer, move ungrouped personas to the bottom:
|
|
313
|
+
groups %w(inbound outbound), ungrouped_first: false
|
|
314
|
+
end
|
|
315
|
+
```
|
|
316
|
+
|
|
276
317
|
## Customizing the Design
|
|
277
318
|
|
|
278
319
|
To supply your own branding, you can override the logo
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<table class="persona-table">
|
|
2
|
+
<thead>
|
|
3
|
+
<tr>
|
|
4
|
+
<th></th>
|
|
5
|
+
<th>Persona name</th>
|
|
6
|
+
<th>Variant</th>
|
|
7
|
+
<th>Features</th>
|
|
8
|
+
</tr>
|
|
9
|
+
</thead>
|
|
10
|
+
<tbody>
|
|
11
|
+
<% personas.each do |persona| %>
|
|
12
|
+
<tr class="<%= persona.css_class %>">
|
|
13
|
+
<td>
|
|
14
|
+
<%= form_for @session, html: { id: persona.name.to_s.underscore } do |f| %>
|
|
15
|
+
<%= f.hidden_field :persona_name, value: persona.name %>
|
|
16
|
+
<%= f.submit 'Sign In', class: 'secondary small' %>
|
|
17
|
+
<% @f = f %>
|
|
18
|
+
<% end %>
|
|
19
|
+
</td>
|
|
20
|
+
<td><%= persona.name.to_s.titleize %></td>
|
|
21
|
+
<td>
|
|
22
|
+
<%= render 'variant_dropdown', f: @f, persona: persona %>
|
|
23
|
+
</td>
|
|
24
|
+
<td><%= persona.features.join(", ") %></td>
|
|
25
|
+
</tr>
|
|
26
|
+
<% end %>
|
|
27
|
+
</tbody>
|
|
28
|
+
</table>
|
|
@@ -23,35 +23,12 @@
|
|
|
23
23
|
<section>
|
|
24
24
|
<input type="text" placeholder="Search..." data-table="persona-table" data-behavior="table-filter" />
|
|
25
25
|
</section>
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
<th>Features</th>
|
|
34
|
-
</tr>
|
|
35
|
-
</thead>
|
|
36
|
-
<tbody>
|
|
37
|
-
<% DemoMode.standard_personas.each do |persona| %>
|
|
38
|
-
<tr class="<%= persona.css_class %>">
|
|
39
|
-
<td>
|
|
40
|
-
<%= form_for @session, html: { id: persona.name.to_s.underscore } do |f| %>
|
|
41
|
-
<%= f.hidden_field :persona_name, value: persona.name %>
|
|
42
|
-
<%= f.submit 'Sign In', class: 'secondary small' %>
|
|
43
|
-
<% @f = f %>
|
|
44
|
-
<% end %>
|
|
45
|
-
</td>
|
|
46
|
-
<td><%= persona.name.to_s.titleize %></td>
|
|
47
|
-
<td>
|
|
48
|
-
<%= render 'variant_dropdown', f: @f, persona: persona %>
|
|
49
|
-
</td>
|
|
50
|
-
<td><%= persona.features.join(", ") %></td>
|
|
51
|
-
</tr>
|
|
52
|
-
<% end %>
|
|
53
|
-
</tbody>
|
|
54
|
-
</table>
|
|
55
|
-
</section>
|
|
26
|
+
<% if DemoMode.ungrouped_first? %>
|
|
27
|
+
<%= render 'ungrouped_personas', personas: DemoMode.ungrouped_personas %>
|
|
28
|
+
<%= render 'grouped_personas', named_groups: DemoMode.personas_by_group %>
|
|
29
|
+
<% else %>
|
|
30
|
+
<%= render 'grouped_personas', named_groups: DemoMode.personas_by_group %>
|
|
31
|
+
<%= render 'ungrouped_personas', personas: DemoMode.ungrouped_personas %>
|
|
32
|
+
<% end %>
|
|
56
33
|
</article>
|
|
57
34
|
<% end %>
|
data/lib/demo_mode/config.rb
CHANGED
|
@@ -17,6 +17,9 @@ module DemoMode
|
|
|
17
17
|
configurable_value(:log_level) { :debug }
|
|
18
18
|
configurable_value(:minimum_pool_size) { 5 }
|
|
19
19
|
configurable_boolean(:display_credentials)
|
|
20
|
+
configurable_boolean(:group_by_folder)
|
|
21
|
+
configurations << :groups
|
|
22
|
+
configurations << :ungrouped_first?
|
|
20
23
|
configurations << :stylesheets
|
|
21
24
|
configurations << :logo
|
|
22
25
|
configurations << :loader
|
|
@@ -30,6 +33,15 @@ module DemoMode
|
|
|
30
33
|
Rails.application.class.module_parent.name
|
|
31
34
|
end
|
|
32
35
|
|
|
36
|
+
def groups(value = nil, **kwargs)
|
|
37
|
+
configure_groups(value, **kwargs) if value || kwargs.any?
|
|
38
|
+
@groups || []
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def ungrouped_first?
|
|
42
|
+
@ungrouped_first.nil? ? true : @ungrouped_first
|
|
43
|
+
end
|
|
44
|
+
|
|
33
45
|
def stylesheets
|
|
34
46
|
@stylesheets ||= [
|
|
35
47
|
"/demo_mode/assets/vendor/normalize-v8.0.1.css",
|
|
@@ -120,6 +132,11 @@ module DemoMode
|
|
|
120
132
|
|
|
121
133
|
private
|
|
122
134
|
|
|
135
|
+
def configure_groups(value, **kwargs)
|
|
136
|
+
@ungrouped_first = kwargs.delete(:ungrouped_first)
|
|
137
|
+
@groups = kwargs.any? ? kwargs : value
|
|
138
|
+
end
|
|
139
|
+
|
|
123
140
|
def auto_load_personas!
|
|
124
141
|
Rails.root.glob("#{personas_path}/**/*.rb").sort.each do |persona_file|
|
|
125
142
|
raise <<~ERROR if File.readlines(persona_file).grep(/DemoMode\.add_persona/).empty?
|
|
@@ -130,7 +147,10 @@ module DemoMode
|
|
|
130
147
|
checksum = Digest::SHA256.hexdigest(File.read(persona_file))
|
|
131
148
|
before_count = @personas.length
|
|
132
149
|
load(persona_file)
|
|
133
|
-
@personas[before_count..].each
|
|
150
|
+
@personas[before_count..].each do |p|
|
|
151
|
+
p.file_checksum = checksum
|
|
152
|
+
p.file_path = persona_file.to_s
|
|
153
|
+
end
|
|
134
154
|
end
|
|
135
155
|
end
|
|
136
156
|
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DemoMode
|
|
4
|
+
class Grouper
|
|
5
|
+
def initialize(personas, groups:)
|
|
6
|
+
@personas = personas
|
|
7
|
+
@groups = groups
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def named
|
|
11
|
+
buckets.except(nil).sort_by { |group, _| sort_key(group) }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def named_groups
|
|
15
|
+
named.map { |group, personas| [name_for(group), personas] }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def ungrouped
|
|
19
|
+
buckets.fetch(nil, [])
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def name_for(group)
|
|
23
|
+
return group unless groups.is_a?(Hash)
|
|
24
|
+
|
|
25
|
+
groups.stringify_keys[group.to_s] || group
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
attr_reader :personas, :groups
|
|
31
|
+
|
|
32
|
+
def buckets
|
|
33
|
+
personas.group_by(&:group)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def sort_key(group)
|
|
37
|
+
rank = group_keys.index(group.to_s)
|
|
38
|
+
[rank || Float::INFINITY, group.to_s]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def group_keys
|
|
42
|
+
(groups.is_a?(Hash) ? groups.keys : groups).map(&:to_s)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
data/lib/demo_mode/persona.rb
CHANGED
|
@@ -6,11 +6,19 @@ module DemoMode
|
|
|
6
6
|
class Persona
|
|
7
7
|
include ActiveModel::Model
|
|
8
8
|
|
|
9
|
-
attr_accessor :name, :file_checksum
|
|
9
|
+
attr_accessor :name, :file_checksum, :file_path
|
|
10
10
|
|
|
11
11
|
validates :name, presence: true
|
|
12
12
|
validate :persona_must_have_at_least_one_feature
|
|
13
13
|
|
|
14
|
+
def group(name = nil)
|
|
15
|
+
if name
|
|
16
|
+
@group = name
|
|
17
|
+
else
|
|
18
|
+
@group || folder_group
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
14
22
|
def icon(name_or_path = nil, &block)
|
|
15
23
|
if block
|
|
16
24
|
@icon = block
|
|
@@ -141,6 +149,15 @@ module DemoMode
|
|
|
141
149
|
|
|
142
150
|
private
|
|
143
151
|
|
|
152
|
+
def folder_group
|
|
153
|
+
return unless DemoMode.group_by_folder?
|
|
154
|
+
return unless file_path
|
|
155
|
+
|
|
156
|
+
relative = Pathname.new(file_path).relative_path_from(Rails.root.join(DemoMode.personas_path))
|
|
157
|
+
folder = relative.dirname.to_s
|
|
158
|
+
folder unless folder == '.'
|
|
159
|
+
end
|
|
160
|
+
|
|
144
161
|
def persona_must_have_at_least_one_feature
|
|
145
162
|
errors.add(:base, <<~ERR) unless features.count >= 1
|
|
146
163
|
Persona must have at least one feature.
|
data/lib/demo_mode/version.rb
CHANGED
data/lib/demo_mode.rb
CHANGED
|
@@ -6,6 +6,7 @@ require 'demo_mode/version'
|
|
|
6
6
|
require 'demo_mode/clever_sequence'
|
|
7
7
|
require 'demo_mode/config'
|
|
8
8
|
require 'demo_mode/engine'
|
|
9
|
+
require 'demo_mode/grouper'
|
|
9
10
|
require 'demo_mode/persona'
|
|
10
11
|
|
|
11
12
|
module DemoMode
|
|
@@ -48,6 +49,14 @@ module DemoMode
|
|
|
48
49
|
personas - callout_personas
|
|
49
50
|
end
|
|
50
51
|
|
|
52
|
+
def ungrouped_personas
|
|
53
|
+
grouper.ungrouped
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def personas_by_group
|
|
57
|
+
grouper.named_groups
|
|
58
|
+
end
|
|
59
|
+
|
|
51
60
|
def current_password
|
|
52
61
|
Thread.current[:_demo_mode_password] ||= password.call
|
|
53
62
|
end
|
|
@@ -70,6 +79,10 @@ module DemoMode
|
|
|
70
79
|
|
|
71
80
|
private
|
|
72
81
|
|
|
82
|
+
def grouper
|
|
83
|
+
Grouper.new(standard_personas, groups: groups)
|
|
84
|
+
end
|
|
85
|
+
|
|
73
86
|
def configuration
|
|
74
87
|
@configuration ||= Config.new
|
|
75
88
|
end
|
|
@@ -55,14 +55,25 @@
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
function updateTable() {
|
|
58
|
-
var
|
|
59
|
-
if (
|
|
58
|
+
var tables = document.querySelectorAll(`.${input.dataset.table}`);
|
|
59
|
+
if (tables.length === 0) {
|
|
60
|
+
alert("TableFilter cannot find its table");
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
Array.prototype.forEach.call(tables, function (table) {
|
|
60
64
|
Array.prototype.forEach.call(table.tBodies, function (tbody) {
|
|
61
65
|
Array.prototype.forEach.call(tbody.rows, filter);
|
|
62
66
|
});
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
});
|
|
68
|
+
revealGroupsWithMatches();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function revealGroupsWithMatches() {
|
|
72
|
+
var groups = document.querySelectorAll("details.persona-group");
|
|
73
|
+
Array.prototype.forEach.call(groups, function (group) {
|
|
74
|
+
var hasMatch = !!group.querySelector("tbody tr[data-matches]");
|
|
75
|
+
group.open = inputValue !== "" && hasMatch;
|
|
76
|
+
});
|
|
66
77
|
}
|
|
67
78
|
|
|
68
79
|
function updateHistory() {
|
|
@@ -75,8 +86,13 @@
|
|
|
75
86
|
|
|
76
87
|
function filter(row) {
|
|
77
88
|
var text = row.textContent.toLowerCase().replace(/[^0-9a-zA-Z ]/g, "");
|
|
78
|
-
|
|
79
|
-
|
|
89
|
+
var matches = text.indexOf(inputValue) !== -1;
|
|
90
|
+
row.style.display = matches ? "table-row" : "none";
|
|
91
|
+
if (matches) {
|
|
92
|
+
row.setAttribute("data-matches", "");
|
|
93
|
+
} else {
|
|
94
|
+
row.removeAttribute("data-matches");
|
|
95
|
+
}
|
|
80
96
|
}
|
|
81
97
|
|
|
82
98
|
function debounce(func, threshold) {
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: demo_mode
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 4.0
|
|
4
|
+
version: 4.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nathan Griffith
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: actionpack
|
|
@@ -343,6 +342,9 @@ files:
|
|
|
343
342
|
- app/jobs/demo_mode/account_generation_job.rb
|
|
344
343
|
- app/jobs/demo_mode/pool_hydration_job.rb
|
|
345
344
|
- app/models/demo_mode/session.rb
|
|
345
|
+
- app/views/demo_mode/sessions/_grouped_personas.html.erb
|
|
346
|
+
- app/views/demo_mode/sessions/_persona_table.html.erb
|
|
347
|
+
- app/views/demo_mode/sessions/_ungrouped_personas.html.erb
|
|
346
348
|
- app/views/demo_mode/sessions/_variant_dropdown.html.erb
|
|
347
349
|
- app/views/demo_mode/sessions/new.html.erb
|
|
348
350
|
- app/views/demo_mode/sessions/show.html.erb
|
|
@@ -366,6 +368,7 @@ files:
|
|
|
366
368
|
- lib/demo_mode/config.rb
|
|
367
369
|
- lib/demo_mode/engine.rb
|
|
368
370
|
- lib/demo_mode/factory_bot_ext.rb
|
|
371
|
+
- lib/demo_mode/grouper.rb
|
|
369
372
|
- lib/demo_mode/persona.rb
|
|
370
373
|
- lib/demo_mode/tasks.rb
|
|
371
374
|
- lib/demo_mode/version.rb
|
|
@@ -388,7 +391,6 @@ licenses:
|
|
|
388
391
|
metadata:
|
|
389
392
|
rubygems_mfa_required: 'true'
|
|
390
393
|
changelog_uri: https://github.com/Betterment/demo_mode/blob/main/CHANGELOG.md
|
|
391
|
-
post_install_message:
|
|
392
394
|
rdoc_options: []
|
|
393
395
|
require_paths:
|
|
394
396
|
- lib
|
|
@@ -403,8 +405,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
403
405
|
- !ruby/object:Gem::Version
|
|
404
406
|
version: '0'
|
|
405
407
|
requirements: []
|
|
406
|
-
rubygems_version:
|
|
407
|
-
signing_key:
|
|
408
|
+
rubygems_version: 4.0.21
|
|
408
409
|
specification_version: 4
|
|
409
410
|
summary: A configurable demo mode for your Rails app.
|
|
410
411
|
test_files: []
|