rails_onboarding 0.8.13 → 0.8.15
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/app/assets/stylesheets/rails_onboarding/flash_messages.css +34 -7
- data/app/models/concerns/rails_onboarding/ab_testable.rb +8 -1
- data/app/models/concerns/rails_onboarding/onboardable.rb +17 -40
- data/app/models/concerns/rails_onboarding/progressive_disclosure.rb +8 -1
- data/app/models/concerns/rails_onboarding/schema_guard.rb +38 -0
- data/app/views/layouts/rails_onboarding/application.html.erb +5 -0
- data/app/views/rails_onboarding/shared/_flash.html.erb +23 -22
- data/lib/rails_onboarding/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: df859b74947576d40a44a8a6ce1e37ceb1c9b92297875ac06faa3926f19489cc
|
|
4
|
+
data.tar.gz: 360a0d59bece71c30df19f549bea51cba322b7d0abf0e657d2e77d8b4b9e9d3b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0fce05257f5c3f4af31cd191dc961ea151e2b753988758e53315e9914f6db67d4d22f1759ae81562b60b8bbe177a2cd3b59ddeb0f840f42ff46b0834f32b63fe
|
|
7
|
+
data.tar.gz: 83404bc6ac100510474a792a1ff245dbb09f53166df47325117b4f3551cacdeca9bf60dc54550d6c07794c8f371a93f9c2779dc1c48d6d1ffae01d0c4a9ef16e
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
max-width: 500px;
|
|
9
9
|
width: 100%;
|
|
10
10
|
pointer-events: none;
|
|
11
|
+
/* Outside .onboarding-container, which is where the engine sets its font, so it would otherwise
|
|
12
|
+
take the browser's serif default. */
|
|
13
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
.onboarding-flash {
|
|
@@ -29,25 +32,46 @@
|
|
|
29
32
|
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
|
|
30
33
|
}
|
|
31
34
|
|
|
32
|
-
/*
|
|
35
|
+
/* Each message is an open <dialog> (see shared/_flash), so it closes without JavaScript. Undo
|
|
36
|
+
what the browser gives a dialog - centred, absolutely placed, bordered all round - leaving the
|
|
37
|
+
coloured left border from the type rules below. */
|
|
38
|
+
dialog.onboarding-flash {
|
|
39
|
+
inset: auto;
|
|
40
|
+
margin: 0 0 12px;
|
|
41
|
+
width: auto;
|
|
42
|
+
max-width: none;
|
|
43
|
+
max-height: none;
|
|
44
|
+
border-top: 0;
|
|
45
|
+
border-right: 0;
|
|
46
|
+
border-bottom: 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* The close form is only there for method="dialog"; its button is the flex item. */
|
|
50
|
+
.onboarding-flash form {
|
|
51
|
+
display: contents;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* Flash types. Each pair is at least 4.5:1 (WCAG AA): white on the 500-weight greens, ambers and
|
|
55
|
+
reds these used was 2-4:1, unreadable on amber in particular. Amber keeps its colour and takes
|
|
56
|
+
dark text instead. */
|
|
33
57
|
.flash-notice,
|
|
34
58
|
.flash-success {
|
|
35
|
-
background-color: #
|
|
59
|
+
background-color: #047857;
|
|
36
60
|
color: white;
|
|
37
|
-
border-left: 4px solid #
|
|
61
|
+
border-left: 4px solid #065f46;
|
|
38
62
|
}
|
|
39
63
|
|
|
40
64
|
.flash-alert,
|
|
41
65
|
.flash-warning {
|
|
42
66
|
background-color: #f59e0b;
|
|
43
|
-
color:
|
|
44
|
-
border-left: 4px solid #
|
|
67
|
+
color: #451a03;
|
|
68
|
+
border-left: 4px solid #b45309;
|
|
45
69
|
}
|
|
46
70
|
|
|
47
71
|
.flash-error {
|
|
48
|
-
background-color: #
|
|
72
|
+
background-color: #b91c1c;
|
|
49
73
|
color: white;
|
|
50
|
-
border-left: 4px solid #
|
|
74
|
+
border-left: 4px solid #7f1d1d;
|
|
51
75
|
}
|
|
52
76
|
|
|
53
77
|
.flash-info {
|
|
@@ -152,6 +176,9 @@
|
|
|
152
176
|
right: 10px;
|
|
153
177
|
left: 10px;
|
|
154
178
|
max-width: none;
|
|
179
|
+
/* Not 100%: with both left and right set, a width overrides right, and the message ran 10px
|
|
180
|
+
off the screen. */
|
|
181
|
+
width: auto;
|
|
155
182
|
}
|
|
156
183
|
|
|
157
184
|
.onboarding-flash {
|
|
@@ -26,7 +26,14 @@ module RailsOnboarding
|
|
|
26
26
|
included do
|
|
27
27
|
# Store A/B test assignments as JSON
|
|
28
28
|
# Expected column: ab_test_assignments (jsonb or text)
|
|
29
|
-
|
|
29
|
+
# column_names and columns_hash both query the database, and this runs
|
|
30
|
+
# when the host model is loaded -- possibly before the table exists.
|
|
31
|
+
# See RailsOnboarding::SchemaGuard.
|
|
32
|
+
if RailsOnboarding::SchemaGuard.columns_queryable?(self)
|
|
33
|
+
unless column_names.include?("ab_test_assignments") && columns_hash["ab_test_assignments"].type == :jsonb
|
|
34
|
+
serialize :ab_test_assignments, coder: JSON
|
|
35
|
+
end
|
|
36
|
+
end
|
|
30
37
|
|
|
31
38
|
# Callbacks
|
|
32
39
|
after_initialize :assign_ab_test_variants, if: :new_record?
|
|
@@ -7,43 +7,10 @@ module RailsOnboarding
|
|
|
7
7
|
MAX_MILESTONES_ACHIEVED = 500
|
|
8
8
|
MAX_JSON_SIZE_BYTES = 65_535 # ~64KB for TEXT columns
|
|
9
9
|
|
|
10
|
-
# Whether the host model's
|
|
11
|
-
#
|
|
12
|
-
# `columns_hash` and `column_names` both query the database, and the
|
|
13
|
-
# `included do` block below runs the moment the host model is loaded --
|
|
14
|
-
# which can be long before that table exists. A host that references its
|
|
15
|
-
# user model from an initializer hits this immediately: the common OmniAuth
|
|
16
|
-
# `:identity` setup passes `model: User`, so the model loads during boot,
|
|
17
|
-
# the include queries `columns_hash`, and against an empty database the
|
|
18
|
-
# whole process dies with "Table 'users' doesn't exist".
|
|
19
|
-
#
|
|
20
|
-
# That is circular and unrecoverable: loading the schema requires booting
|
|
21
|
-
# the app, and booting the app requires the schema. It stops `db:prepare`
|
|
22
|
-
# bootstrapping a genuinely new database, a restore into an empty schema,
|
|
23
|
-
# and CI against a fresh database service.
|
|
24
|
-
#
|
|
25
|
-
# A missing table, a missing database and an absent connection all mean the
|
|
26
|
-
# same thing here -- the column types cannot be known yet -- so degrade
|
|
27
|
-
# instead of raising. Nothing is lost: a process that boots against an empty
|
|
28
|
-
# database is bootstrapping it, not serving from it, and the next process
|
|
29
|
-
# configures everything normally once the schema is in place.
|
|
30
|
-
#
|
|
31
|
-
# Only ever answers false when the table is positively known to be absent.
|
|
32
|
-
# This concern is deliberately includable into plain classes that are not
|
|
33
|
-
# ActiveRecord models -- hence the `respond_to?(:has_many)` and
|
|
34
|
-
# `respond_to?(:validate)` guards below -- and such a class has no
|
|
35
|
-
# `table_exists?` while still answering `columns_hash`. Saying "not
|
|
36
|
-
# available" for those would silently stop configuring them, so anything
|
|
37
|
-
# that cannot be asked is treated as queryable and left to the callers'
|
|
38
|
-
# own guards, exactly as before this check existed.
|
|
10
|
+
# Whether the host model's columns can be read yet. See
|
|
11
|
+
# RailsOnboarding::SchemaGuard, which three concerns share.
|
|
39
12
|
def self.columns_queryable?(model)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
model.table_exists?
|
|
43
|
-
rescue ActiveRecord::NoDatabaseError,
|
|
44
|
-
ActiveRecord::ConnectionNotEstablished,
|
|
45
|
-
ActiveRecord::StatementInvalid
|
|
46
|
-
false
|
|
13
|
+
SchemaGuard.columns_queryable?(model)
|
|
47
14
|
end
|
|
48
15
|
|
|
49
16
|
included do
|
|
@@ -62,8 +29,8 @@ module RailsOnboarding
|
|
|
62
29
|
|
|
63
30
|
# Fix for Rails 8: Use the new serialize syntax.
|
|
64
31
|
# Guarded because columns_hash queries the database -- see
|
|
65
|
-
#
|
|
66
|
-
if RailsOnboarding::
|
|
32
|
+
# RailsOnboarding::SchemaGuard.
|
|
33
|
+
if RailsOnboarding::SchemaGuard.columns_queryable?(self)
|
|
67
34
|
if columns_hash["feature_tooltips_shown"]&.type == :text
|
|
68
35
|
serialize :feature_tooltips_shown, coder: JSON
|
|
69
36
|
end
|
|
@@ -99,7 +66,7 @@ module RailsOnboarding
|
|
|
99
66
|
# than raising on a column that isn't there.
|
|
100
67
|
def onboarding_replay_supported?
|
|
101
68
|
return false unless respond_to?(:column_names)
|
|
102
|
-
return false unless RailsOnboarding::
|
|
69
|
+
return false unless RailsOnboarding::SchemaGuard.columns_queryable?(self)
|
|
103
70
|
|
|
104
71
|
column_names.include?("onboarding_replay_started_at") &&
|
|
105
72
|
column_names.include?("onboarding_replay_steps")
|
|
@@ -763,8 +730,18 @@ module RailsOnboarding
|
|
|
763
730
|
{ onboarding_replay_started_at: nil, onboarding_replay_steps: [] }
|
|
764
731
|
end
|
|
765
732
|
|
|
733
|
+
# column_names queries the database like the rest, so it takes the same
|
|
734
|
+
# guard -- but purely for symmetry: this one cannot actually be reached
|
|
735
|
+
# with the table missing. It is an instance method, and ActiveRecord cannot
|
|
736
|
+
# instantiate a model whose table is absent (`new` raises while building
|
|
737
|
+
# attribute types), so the guard can never fire. Kept so every column query
|
|
738
|
+
# in this file reads the same way, and it costs nothing: table_exists?
|
|
739
|
+
# reads the schema cache.
|
|
766
740
|
def onboarding_column?(name)
|
|
767
|
-
self.class.respond_to?(:column_names)
|
|
741
|
+
return false unless self.class.respond_to?(:column_names)
|
|
742
|
+
return false unless RailsOnboarding::SchemaGuard.columns_queryable?(self.class)
|
|
743
|
+
|
|
744
|
+
self.class.column_names.include?(name)
|
|
768
745
|
end
|
|
769
746
|
|
|
770
747
|
def persist_and_track!(attributes = nil)
|
|
@@ -36,7 +36,14 @@ module RailsOnboarding
|
|
|
36
36
|
included do
|
|
37
37
|
# Store revealed features as JSON
|
|
38
38
|
# Expected column: revealed_features (jsonb or text)
|
|
39
|
-
|
|
39
|
+
# column_names and columns_hash both query the database, and this runs
|
|
40
|
+
# when the host model is loaded -- possibly before the table exists.
|
|
41
|
+
# See RailsOnboarding::SchemaGuard.
|
|
42
|
+
if RailsOnboarding::SchemaGuard.columns_queryable?(self)
|
|
43
|
+
unless column_names.include?("revealed_features") && columns_hash["revealed_features"].type == :jsonb
|
|
44
|
+
serialize :revealed_features, coder: JSON
|
|
45
|
+
end
|
|
46
|
+
end
|
|
40
47
|
|
|
41
48
|
# Callback to check and reveal features
|
|
42
49
|
after_save :check_progressive_features, if: :saved_change_to_onboarding_current_step?
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module RailsOnboarding
|
|
2
|
+
# Whether a host model's columns can be read yet.
|
|
3
|
+
#
|
|
4
|
+
# Three concerns inspect columns the moment they are included -- to decide
|
|
5
|
+
# whether to configure JSON serialization -- and `columns_hash` and
|
|
6
|
+
# `column_names` both query the database. That runs when the host model is
|
|
7
|
+
# loaded, which can be long before the table exists.
|
|
8
|
+
#
|
|
9
|
+
# A host that references its user model from an initializer hits this
|
|
10
|
+
# immediately. The common OmniAuth `:identity` setup passes `model: User`, so
|
|
11
|
+
# the model loads during boot and the process dies with
|
|
12
|
+
# `Table 'users' doesn't exist`. That is circular and unrecoverable: loading
|
|
13
|
+
# the schema requires booting the app, and booting the app requires the
|
|
14
|
+
# schema. It stops `db:prepare` bootstrapping a genuinely new database, a
|
|
15
|
+
# restore into an empty schema, and CI against a fresh database service.
|
|
16
|
+
module SchemaGuard
|
|
17
|
+
module_function
|
|
18
|
+
|
|
19
|
+
# Only ever answers false when the table is positively known to be absent.
|
|
20
|
+
#
|
|
21
|
+
# These concerns are deliberately includable into plain classes that are not
|
|
22
|
+
# ActiveRecord models -- hence the `respond_to?(:has_many)` and
|
|
23
|
+
# `respond_to?(:validate)` guards in Onboardable -- and such a class answers
|
|
24
|
+
# `columns_hash` while having no `table_exists?`. Vetoing those would
|
|
25
|
+
# silently stop configuring them, so anything that cannot be asked is
|
|
26
|
+
# treated as queryable and left to the callers' own guards, exactly as
|
|
27
|
+
# before this check existed.
|
|
28
|
+
def columns_queryable?(model)
|
|
29
|
+
return true unless model.respond_to?(:table_exists?)
|
|
30
|
+
|
|
31
|
+
model.table_exists?
|
|
32
|
+
rescue ActiveRecord::NoDatabaseError,
|
|
33
|
+
ActiveRecord::ConnectionNotEstablished,
|
|
34
|
+
ActiveRecord::StatementInvalid
|
|
35
|
+
false
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -51,6 +51,11 @@
|
|
|
51
51
|
</head>
|
|
52
52
|
<body>
|
|
53
53
|
|
|
54
|
+
<%# Without this, a message on the way to onboarding was lost: a host's redirect_to with an alert,
|
|
55
|
+
the engine's own redirect_to onboarding_path, alert:, and the Turbo Stream errors, which replace
|
|
56
|
+
#flash-messages and found nothing to replace. Fixed-position, so it sits over the page. %>
|
|
57
|
+
<%= render "rails_onboarding/shared/flash" %>
|
|
58
|
+
|
|
54
59
|
<%# .onboarding-container/.onboarding-content provide the page-level
|
|
55
60
|
min-height/centering/responsive padding that the stylesheets (see
|
|
56
61
|
application.css and mobile.css) already define for every onboarding
|
|
@@ -1,25 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
<span class="flash-icon">✓</span>
|
|
5
|
-
<span class="flash-message"><%= notice %></span>
|
|
6
|
-
<button class="flash-close" onclick="this.parentElement.remove()" aria-label="Dismiss">×</button>
|
|
7
|
-
</div>
|
|
8
|
-
<% end %>
|
|
1
|
+
<%# The flash, on every page of the engine layout, and the target the onboarding controller's
|
|
2
|
+
Turbo Stream errors replace (#flash-messages). Each message comes from a local when one is
|
|
3
|
+
given - those streams pass the message they want shown - and from the flash otherwise.
|
|
9
4
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
5
|
+
Each message is an open <dialog> closed by a form with method="dialog", which the browser
|
|
6
|
+
handles itself: no JavaScript, so nothing for a strict Content Security Policy to refuse. The
|
|
7
|
+
close buttons were onclick= attributes, which such a policy blocks without a word. Turbo
|
|
8
|
+
leaves method="dialog" forms alone. %>
|
|
9
|
+
<% messages = {
|
|
10
|
+
notice: local_assigns.fetch(:notice) { flash[:notice] },
|
|
11
|
+
alert: local_assigns.fetch(:alert) { flash[:alert] },
|
|
12
|
+
error: local_assigns.fetch(:error) { flash[:error] }
|
|
13
|
+
} %>
|
|
14
|
+
<% icons = { notice: "✓", alert: "⚠", error: "✕" } %>
|
|
15
|
+
<div id="flash-messages" class="flash-container">
|
|
16
|
+
<% messages.each do |type, message| %>
|
|
17
|
+
<% next if message.blank? %>
|
|
18
|
+
<dialog open class="onboarding-flash flash-<%= type %>">
|
|
19
|
+
<span class="flash-icon" aria-hidden="true"><%= icons[type] %></span>
|
|
20
|
+
<span class="flash-message" role="alert"><%= message %></span>
|
|
21
|
+
<form method="dialog">
|
|
22
|
+
<button class="flash-close" aria-label="Dismiss">×</button>
|
|
23
|
+
</form>
|
|
24
|
+
</dialog>
|
|
24
25
|
<% end %>
|
|
25
26
|
</div>
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails_onboarding
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.8.
|
|
4
|
+
version: 0.8.15
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David Lewis
|
|
@@ -183,6 +183,7 @@ files:
|
|
|
183
183
|
- app/models/concerns/rails_onboarding/onboardable.rb
|
|
184
184
|
- app/models/concerns/rails_onboarding/personalizable.rb
|
|
185
185
|
- app/models/concerns/rails_onboarding/progressive_disclosure.rb
|
|
186
|
+
- app/models/concerns/rails_onboarding/schema_guard.rb
|
|
186
187
|
- app/models/rails_onboarding/analytics.rb
|
|
187
188
|
- app/models/rails_onboarding/analytics_event.rb
|
|
188
189
|
- app/models/rails_onboarding/application_record.rb
|
|
@@ -282,7 +283,7 @@ licenses:
|
|
|
282
283
|
metadata:
|
|
283
284
|
allowed_push_host: https://rubygems.org
|
|
284
285
|
homepage_uri: https://github.com/bunnahabhain/rails_onboarding
|
|
285
|
-
source_code_uri: https://github.com/bunnahabhain/rails_onboarding/tree/v0.8.
|
|
286
|
+
source_code_uri: https://github.com/bunnahabhain/rails_onboarding/tree/v0.8.15
|
|
286
287
|
changelog_uri: https://github.com/bunnahabhain/rails_onboarding/blob/master/docs/CHANGELOG.md
|
|
287
288
|
rdoc_options: []
|
|
288
289
|
require_paths:
|
|
@@ -298,7 +299,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
298
299
|
- !ruby/object:Gem::Version
|
|
299
300
|
version: '0'
|
|
300
301
|
requirements: []
|
|
301
|
-
rubygems_version: 4.0.
|
|
302
|
+
rubygems_version: 4.0.22
|
|
302
303
|
specification_version: 4
|
|
303
304
|
summary: Flexible onboarding engine for Rails applications
|
|
304
305
|
test_files: []
|