rails_onboarding 0.6.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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +1351 -0
- data/Rakefile +6 -0
- data/app/assets/javascripts/rails_onboarding/admin/chart_controller.js +25 -0
- data/app/assets/javascripts/rails_onboarding/admin/filter_controller.js +49 -0
- data/app/assets/javascripts/rails_onboarding/admin/flash_controller.js +21 -0
- data/app/assets/javascripts/rails_onboarding/admin/flow_editor_controller.js +86 -0
- data/app/assets/javascripts/rails_onboarding/application.js +231 -0
- data/app/assets/javascripts/rails_onboarding/milestone_celebration_controller.js +177 -0
- data/app/assets/javascripts/rails_onboarding/milestone_dashboard_controller.js +132 -0
- data/app/assets/javascripts/rails_onboarding/milestone_detail_controller.js +34 -0
- data/app/assets/javascripts/rails_onboarding/navigation_controller.js +277 -0
- data/app/assets/javascripts/rails_onboarding/onboarding_controller.js +326 -0
- data/app/assets/javascripts/rails_onboarding/progress_controller.js +203 -0
- data/app/assets/javascripts/rails_onboarding/progressive_disclosure_controller.js +218 -0
- data/app/assets/javascripts/rails_onboarding/tooltip_controller.js +1237 -0
- data/app/assets/javascripts/rails_onboarding/tooltip_scheduler_controller.js +511 -0
- data/app/assets/javascripts/rails_onboarding/tour_controller.js +763 -0
- data/app/assets/stylesheets/rails_onboarding/accessibility.css +536 -0
- data/app/assets/stylesheets/rails_onboarding/admin.css +816 -0
- data/app/assets/stylesheets/rails_onboarding/application.css +1145 -0
- data/app/assets/stylesheets/rails_onboarding/flash_messages.css +237 -0
- data/app/assets/stylesheets/rails_onboarding/milestones.css +545 -0
- data/app/assets/stylesheets/rails_onboarding/mobile.css +856 -0
- data/app/assets/stylesheets/rails_onboarding/progressive_disclosure.css +299 -0
- data/app/assets/stylesheets/rails_onboarding/tooltips.css +468 -0
- data/app/assets/stylesheets/rails_onboarding/tour.css +495 -0
- data/app/assets/stylesheets/rails_onboarding/utilities.css +393 -0
- data/app/controllers/concerns/rails_onboarding/admin_authorization.rb +98 -0
- data/app/controllers/concerns/rails_onboarding/rate_limitable.rb +104 -0
- data/app/controllers/rails_onboarding/ab_tests_controller.rb +257 -0
- data/app/controllers/rails_onboarding/admin/ab_tests_controller.rb +121 -0
- data/app/controllers/rails_onboarding/admin/base_controller.rb +68 -0
- data/app/controllers/rails_onboarding/admin/dashboard_controller.rb +189 -0
- data/app/controllers/rails_onboarding/admin/flows_controller.rb +175 -0
- data/app/controllers/rails_onboarding/admin/users_controller.rb +366 -0
- data/app/controllers/rails_onboarding/application_controller.rb +9 -0
- data/app/controllers/rails_onboarding/milestones_controller.rb +126 -0
- data/app/controllers/rails_onboarding/onboarding_controller.rb +409 -0
- data/app/controllers/rails_onboarding/progressive_features_controller.rb +134 -0
- data/app/controllers/rails_onboarding/templates_controller.rb +162 -0
- data/app/controllers/rails_onboarding/test_sessions_controller.rb +16 -0
- data/app/controllers/rails_onboarding/tooltips_controller.rb +92 -0
- data/app/helpers/rails_onboarding/application_helper.rb +4 -0
- data/app/helpers/rails_onboarding/i18n_helper.rb +137 -0
- data/app/helpers/rails_onboarding/personalization_helper.rb +184 -0
- data/app/helpers/rails_onboarding/templates_helper.rb +253 -0
- data/app/jobs/rails_onboarding/application_job.rb +4 -0
- data/app/mailers/rails_onboarding/application_mailer.rb +6 -0
- data/app/mailers/rails_onboarding/onboarding_mailer.rb +72 -0
- data/app/models/concerns/rails_onboarding/ab_testable.rb +149 -0
- data/app/models/concerns/rails_onboarding/onboardable.rb +649 -0
- data/app/models/concerns/rails_onboarding/personalizable.rb +211 -0
- data/app/models/concerns/rails_onboarding/progressive_disclosure.rb +276 -0
- data/app/models/rails_onboarding/analytics.rb +255 -0
- data/app/models/rails_onboarding/analytics_event.rb +168 -0
- data/app/models/rails_onboarding/application_record.rb +5 -0
- data/app/models/rails_onboarding/flow.rb +41 -0
- data/app/services/rails_onboarding/milestone_service.rb +105 -0
- data/app/views/layouts/rails_onboarding/admin.html.erb +106 -0
- data/app/views/layouts/rails_onboarding/application.html.erb +65 -0
- data/app/views/rails_onboarding/ab_tests/index.html.erb +209 -0
- data/app/views/rails_onboarding/ab_tests/results.html.erb +253 -0
- data/app/views/rails_onboarding/admin/ab_tests/_ab_test_card.html.erb +24 -0
- data/app/views/rails_onboarding/admin/ab_tests/index.html.erb +57 -0
- data/app/views/rails_onboarding/admin/ab_tests/show.html.erb +41 -0
- data/app/views/rails_onboarding/admin/dashboard/index.html.erb +224 -0
- data/app/views/rails_onboarding/admin/flows/_form.html.erb +147 -0
- data/app/views/rails_onboarding/admin/flows/edit.html.erb +7 -0
- data/app/views/rails_onboarding/admin/flows/index.html.erb +125 -0
- data/app/views/rails_onboarding/admin/flows/new.html.erb +7 -0
- data/app/views/rails_onboarding/admin/flows/preview.html.erb +40 -0
- data/app/views/rails_onboarding/admin/flows/show.html.erb +114 -0
- data/app/views/rails_onboarding/admin/users/index.html.erb +180 -0
- data/app/views/rails_onboarding/admin/users/show.html.erb +187 -0
- data/app/views/rails_onboarding/milestones/index.html.erb +64 -0
- data/app/views/rails_onboarding/milestones/show.html.erb +57 -0
- data/app/views/rails_onboarding/onboarding/_custom_footer.html.erb +2 -0
- data/app/views/rails_onboarding/onboarding/_progress_indicator.html.erb +56 -0
- data/app/views/rails_onboarding/onboarding/_step_navigation.html.erb +44 -0
- data/app/views/rails_onboarding/onboarding/step.html.erb +38 -0
- data/app/views/rails_onboarding/onboarding/welcome.html.erb +37 -0
- data/app/views/rails_onboarding/onboarding_mailer/completion_email.html.erb +109 -0
- data/app/views/rails_onboarding/onboarding_mailer/completion_email.text.erb +29 -0
- data/app/views/rails_onboarding/onboarding_mailer/reminder_email.html.erb +99 -0
- data/app/views/rails_onboarding/onboarding_mailer/reminder_email.text.erb +20 -0
- data/app/views/rails_onboarding/onboarding_mailer/step_completed_email.html.erb +105 -0
- data/app/views/rails_onboarding/onboarding_mailer/step_completed_email.text.erb +19 -0
- data/app/views/rails_onboarding/onboarding_mailer/welcome_email.html.erb +74 -0
- data/app/views/rails_onboarding/onboarding_mailer/welcome_email.text.erb +17 -0
- data/app/views/rails_onboarding/shared/_flash.html.erb +25 -0
- data/app/views/rails_onboarding/shared/_milestone_badge.html.erb +22 -0
- data/app/views/rails_onboarding/shared/_milestone_celebration.html.erb +44 -0
- data/app/views/rails_onboarding/shared/_onboarding_banner.html.erb +48 -0
- data/app/views/rails_onboarding/templates/index.html.erb +255 -0
- data/config/importmap.rb +33 -0
- data/config/locales/en.yml +62 -0
- data/config/locales/es.yml +62 -0
- data/config/locales/fr.yml +62 -0
- data/config/routes.rb +79 -0
- data/lib/generators/rails_onboarding/install_generator.rb +124 -0
- data/lib/generators/rails_onboarding/templates/README +120 -0
- data/lib/generators/rails_onboarding/templates/add_analytics_to_rails_onboarding.rb +33 -0
- data/lib/generators/rails_onboarding/templates/add_milestone_tracking_to_users.rb +38 -0
- data/lib/generators/rails_onboarding/templates/add_onboarding_indexes.rb +93 -0
- data/lib/generators/rails_onboarding/templates/add_onboarding_to_users.rb +51 -0
- data/lib/generators/rails_onboarding/templates/add_robustness_fields_to_users.rb.tt +20 -0
- data/lib/generators/rails_onboarding/templates/create_rails_onboarding_flows.rb +18 -0
- data/lib/generators/rails_onboarding/templates/onboarding.css +18 -0
- data/lib/generators/rails_onboarding/templates/rails_onboarding.rb +75 -0
- data/lib/generators/rails_onboarding/update_generator.rb +73 -0
- data/lib/rails_onboarding/api_mode.rb +367 -0
- data/lib/rails_onboarding/backfill.rb +130 -0
- data/lib/rails_onboarding/background_jobs.rb +412 -0
- data/lib/rails_onboarding/caching.rb +180 -0
- data/lib/rails_onboarding/cdn_support.rb +202 -0
- data/lib/rails_onboarding/configuration/ab_testing.rb +24 -0
- data/lib/rails_onboarding/configuration/analytics.rb +29 -0
- data/lib/rails_onboarding/configuration/integrations.rb +31 -0
- data/lib/rails_onboarding/configuration/milestones.rb +92 -0
- data/lib/rails_onboarding/configuration/personalization.rb +25 -0
- data/lib/rails_onboarding/configuration/progressive_disclosure.rb +41 -0
- data/lib/rails_onboarding/configuration/rate_limiting.rb +18 -0
- data/lib/rails_onboarding/configuration/steps.rb +121 -0
- data/lib/rails_onboarding/configuration/templates.rb +85 -0
- data/lib/rails_onboarding/configuration/tooltips.rb +32 -0
- data/lib/rails_onboarding/configuration.rb +133 -0
- data/lib/rails_onboarding/configuration_errors.rb +19 -0
- data/lib/rails_onboarding/configuration_validator.rb +477 -0
- data/lib/rails_onboarding/controller_helpers.rb +153 -0
- data/lib/rails_onboarding/current.rb +13 -0
- data/lib/rails_onboarding/deprecation.rb +64 -0
- data/lib/rails_onboarding/devise_integration.rb +122 -0
- data/lib/rails_onboarding/engine.rb +245 -0
- data/lib/rails_onboarding/error_recovery.rb +173 -0
- data/lib/rails_onboarding/lazy_loading.rb +155 -0
- data/lib/rails_onboarding/multi_tenant.rb +306 -0
- data/lib/rails_onboarding/requirements_validator.rb +315 -0
- data/lib/rails_onboarding/responsive_helper.rb +218 -0
- data/lib/rails_onboarding/session_manager.rb +187 -0
- data/lib/rails_onboarding/skip_logic.rb +159 -0
- data/lib/rails_onboarding/turbo_compatibility.rb +229 -0
- data/lib/rails_onboarding/version.rb +3 -0
- data/lib/rails_onboarding.rb +44 -0
- data/lib/tasks/rails_onboarding_analytics.rake +136 -0
- data/lib/tasks/rails_onboarding_tasks.rake +162 -0
- metadata +303 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
===============================================================================
|
|
2
|
+
|
|
3
|
+
Rails Onboarding has been installed!
|
|
4
|
+
|
|
5
|
+
Next steps:
|
|
6
|
+
|
|
7
|
+
1. Run the migrations:
|
|
8
|
+
bin/rails db:migrate
|
|
9
|
+
|
|
10
|
+
2. Add the Onboardable concern to your User model:
|
|
11
|
+
|
|
12
|
+
# app/models/user.rb
|
|
13
|
+
class User < ApplicationRecord
|
|
14
|
+
include RailsOnboarding::Onboardable
|
|
15
|
+
|
|
16
|
+
# your existing code...
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
3. Review config/initializers/rails_onboarding.rb - in particular, make sure
|
|
20
|
+
redirect_after_completion / redirect_after_skip point at routes that
|
|
21
|
+
actually exist in your app (they default to :root_path).
|
|
22
|
+
|
|
23
|
+
4. Load the CSS and JS in your layout.
|
|
24
|
+
|
|
25
|
+
On Sprockets, rails_onboarding/application.css bundles all of the gem's
|
|
26
|
+
stylesheets itself, so one tag is enough:
|
|
27
|
+
|
|
28
|
+
# app/views/layouts/application.html.erb
|
|
29
|
+
<%= stylesheet_link_tag "rails_onboarding/application" %>
|
|
30
|
+
<%= javascript_include_tag "rails_onboarding/application", defer: true %>
|
|
31
|
+
|
|
32
|
+
On Propshaft (Rails 8's default, including with cssbundling-rails),
|
|
33
|
+
there's no bundling step, so application.css only contains its own base
|
|
34
|
+
styles - you need one tag per gem stylesheet or tooltips/tours/
|
|
35
|
+
milestones/mobile responsiveness/etc. will silently never load:
|
|
36
|
+
|
|
37
|
+
# app/views/layouts/application.html.erb
|
|
38
|
+
<%= stylesheet_link_tag "rails_onboarding/application" %>
|
|
39
|
+
<%= stylesheet_link_tag "rails_onboarding/tooltips" %>
|
|
40
|
+
<%= stylesheet_link_tag "rails_onboarding/utilities" %>
|
|
41
|
+
<%= stylesheet_link_tag "rails_onboarding/accessibility" %>
|
|
42
|
+
<%= stylesheet_link_tag "rails_onboarding/milestones" %>
|
|
43
|
+
<%= stylesheet_link_tag "rails_onboarding/tour" %>
|
|
44
|
+
<%= stylesheet_link_tag "rails_onboarding/flash_messages" %>
|
|
45
|
+
<%= stylesheet_link_tag "rails_onboarding/progressive_disclosure" %>
|
|
46
|
+
<%= stylesheet_link_tag "rails_onboarding/admin" %>
|
|
47
|
+
<%= stylesheet_link_tag "rails_onboarding/mobile" %>
|
|
48
|
+
<%= javascript_include_tag "rails_onboarding/application", defer: true %>
|
|
49
|
+
|
|
50
|
+
(Keep application first and mobile last; skip admin if you don't use the
|
|
51
|
+
admin dashboard.)
|
|
52
|
+
|
|
53
|
+
If you're on Importmap, the tags above are enough to load the tooltip/tour helpers
|
|
54
|
+
(RailsOnboarding.showTooltip, etc.), but the Stimulus controllers still
|
|
55
|
+
need to be registered explicitly - add to app/javascript/controllers/index.js:
|
|
56
|
+
|
|
57
|
+
import OnboardingController from "rails_onboarding/onboarding_controller"
|
|
58
|
+
import ProgressController from "rails_onboarding/progress_controller"
|
|
59
|
+
import NavigationController from "rails_onboarding/navigation_controller"
|
|
60
|
+
import TooltipController from "rails_onboarding/tooltip_controller"
|
|
61
|
+
|
|
62
|
+
application.register("onboarding", OnboardingController)
|
|
63
|
+
application.register("progress", ProgressController)
|
|
64
|
+
application.register("navigation", NavigationController)
|
|
65
|
+
application.register("tooltip", TooltipController)
|
|
66
|
+
|
|
67
|
+
(These four cover the core onboarding flow. See the README's Asset
|
|
68
|
+
Loading section for the full controller list, including milestones,
|
|
69
|
+
tours, and the admin dashboard.)
|
|
70
|
+
|
|
71
|
+
If you're on ESBuild/Webpack/Vite instead, see docs/ESBUILD_SETUP.md
|
|
72
|
+
in the gem's repository (github.com/bunnahabhain/rails_onboarding) for
|
|
73
|
+
how to copy and register the controllers manually.
|
|
74
|
+
|
|
75
|
+
5. Create a view for each step in config.steps (config/initializers/
|
|
76
|
+
rails_onboarding.rb). The default config ships with four steps -
|
|
77
|
+
welcome, profile, first_action, explore - and the gem only provides a
|
|
78
|
+
real view for "welcome"; any step without one falls back to a generic
|
|
79
|
+
"this step is not yet fully configured" placeholder, which is fine for
|
|
80
|
+
development but not something to ship.
|
|
81
|
+
|
|
82
|
+
# app/views/rails_onboarding/onboarding/profile.html.erb
|
|
83
|
+
<div class="onboarding-step">
|
|
84
|
+
<h1>Setup Your Profile</h1>
|
|
85
|
+
|
|
86
|
+
<%= link_to "Continue", next_onboarding_path,
|
|
87
|
+
method: :post,
|
|
88
|
+
class: "btn btn-primary" %>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
Repeat for each step name. See the README's "Customize Step Views"
|
|
92
|
+
section for a full example, including the welcome.html.erb the gem
|
|
93
|
+
ships as a reference.
|
|
94
|
+
|
|
95
|
+
6. Require a `current_user` method in your controllers (from Devise or your
|
|
96
|
+
own auth) - RailsOnboarding reads it to know who's onboarding. Then wire
|
|
97
|
+
up the redirect yourself; the gem gives you the predicate, not the
|
|
98
|
+
before_action:
|
|
99
|
+
|
|
100
|
+
# app/controllers/application_controller.rb
|
|
101
|
+
class ApplicationController < ActionController::Base
|
|
102
|
+
before_action :redirect_to_onboarding_if_needed
|
|
103
|
+
|
|
104
|
+
private
|
|
105
|
+
|
|
106
|
+
def redirect_to_onboarding_if_needed
|
|
107
|
+
redirect_to onboarding_path if needs_onboarding?
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
Skip specific actions/controllers with:
|
|
112
|
+
|
|
113
|
+
skip_onboarding_check only: [:edit, :update] # or except: [...]
|
|
114
|
+
|
|
115
|
+
7. Visit /onboarding to test your setup!
|
|
116
|
+
|
|
117
|
+
For more details, see the README at
|
|
118
|
+
https://github.com/bunnahabhain/rails_onboarding
|
|
119
|
+
|
|
120
|
+
===============================================================================
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
class AddAnalyticsToRailsOnboarding < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
|
2
|
+
def up
|
|
3
|
+
create_table :rails_onboarding_analytics_events do |t|
|
|
4
|
+
t.references :user, polymorphic: true, null: true
|
|
5
|
+
t.string :event_type, null: false
|
|
6
|
+
t.text :properties
|
|
7
|
+
t.string :session_id
|
|
8
|
+
t.datetime :occurred_at, null: false
|
|
9
|
+
|
|
10
|
+
t.timestamps
|
|
11
|
+
|
|
12
|
+
# Indexes for common queries and performance optimization
|
|
13
|
+
t.index :event_type
|
|
14
|
+
t.index :occurred_at
|
|
15
|
+
t.index :session_id
|
|
16
|
+
|
|
17
|
+
# Polymorphic association indexes (optimized for queries on specific user types)
|
|
18
|
+
t.index [:user_type, :user_id], name: 'index_analytics_events_on_user'
|
|
19
|
+
t.index [:user_type, :user_id, :event_type], name: 'index_analytics_events_on_user_and_type'
|
|
20
|
+
t.index [:user_type, :user_id, :occurred_at], name: 'index_analytics_events_on_user_and_date'
|
|
21
|
+
|
|
22
|
+
# Composite indexes for common analytics queries
|
|
23
|
+
t.index [:event_type, :occurred_at], name: 'index_analytics_events_on_type_and_date'
|
|
24
|
+
t.index [:user_id, :session_id], name: 'index_analytics_events_on_user_and_session'
|
|
25
|
+
t.index [:session_id, :occurred_at], name: 'index_analytics_events_on_session_and_date'
|
|
26
|
+
t.index [:event_type, :session_id], name: 'index_analytics_events_on_type_and_session'
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def down
|
|
31
|
+
drop_table :rails_onboarding_analytics_events if table_exists?(:rails_onboarding_analytics_events)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
class AddMilestoneTrackingToUsers < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
|
2
|
+
disable_ddl_transaction!
|
|
3
|
+
|
|
4
|
+
def up
|
|
5
|
+
# No DB-level default: MySQL/MariaDB reject a literal DEFAULT on
|
|
6
|
+
# TEXT/BLOB/JSON columns. Onboardable already treats a nil
|
|
7
|
+
# milestones_achieved as an empty array in Ruby (see onboardable.rb),
|
|
8
|
+
# so there's nothing to work around adapter-by-adapter here.
|
|
9
|
+
add_column :users, :milestones_achieved, :text
|
|
10
|
+
add_column :users, :milestone_points, :integer, default: 0
|
|
11
|
+
add_column :users, :last_milestone_at, :datetime
|
|
12
|
+
|
|
13
|
+
# On PostgreSQL, build concurrently so these don't hold a write lock on a
|
|
14
|
+
# large, live users table; other adapters don't support that algorithm,
|
|
15
|
+
# so fall back to a plain index there.
|
|
16
|
+
add_index :users, :milestone_points, **concurrent_index_options
|
|
17
|
+
add_index :users, :last_milestone_at, **concurrent_index_options
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def down
|
|
21
|
+
remove_index :users, :last_milestone_at if index_exists?(:users, :last_milestone_at)
|
|
22
|
+
remove_index :users, :milestone_points if index_exists?(:users, :milestone_points)
|
|
23
|
+
|
|
24
|
+
remove_column :users, :last_milestone_at if column_exists?(:users, :last_milestone_at)
|
|
25
|
+
remove_column :users, :milestone_points if column_exists?(:users, :milestone_points)
|
|
26
|
+
remove_column :users, :milestones_achieved if column_exists?(:users, :milestones_achieved)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def concurrent_index_options
|
|
32
|
+
postgresql? ? { algorithm: :concurrently } : {}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def postgresql?
|
|
36
|
+
%w[postgresql postgis].include?(ActiveRecord::Base.connection.adapter_name.downcase)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddOnboardingIndexes < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
|
4
|
+
disable_ddl_transaction!
|
|
5
|
+
|
|
6
|
+
def up
|
|
7
|
+
# Add indexes for frequently queried columns.
|
|
8
|
+
# Skip if index already exists. On PostgreSQL, build concurrently so these
|
|
9
|
+
# don't hold a write lock on a large, live users table; other adapters
|
|
10
|
+
# don't support that algorithm, so fall back to a plain index there.
|
|
11
|
+
unless index_exists?(:users, :onboarding_completed)
|
|
12
|
+
add_index :users, :onboarding_completed, **concurrent_index_options
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
unless index_exists?(:users, :onboarding_current_step)
|
|
16
|
+
add_index :users, :onboarding_current_step, **concurrent_index_options
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
unless index_exists?(:users, :onboarding_skipped)
|
|
20
|
+
add_index :users, :onboarding_skipped, **concurrent_index_options
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
unless index_exists?(:users, [:onboarding_completed, :created_at])
|
|
24
|
+
# Composite index for finding users who need onboarding
|
|
25
|
+
add_index :users, [:onboarding_completed, :created_at],
|
|
26
|
+
name: 'index_users_on_onboarding_status_and_created', **concurrent_index_options
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
unless index_exists?(:users, :onboarding_completed_at)
|
|
30
|
+
# Index for analytics queries on completion time
|
|
31
|
+
add_index :users, :onboarding_completed_at, **concurrent_index_options
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
unless index_exists?(:users, :last_milestone_at)
|
|
35
|
+
# Index for milestone queries
|
|
36
|
+
add_index :users, :last_milestone_at, **concurrent_index_options
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Add indexes to analytics_events table if it exists
|
|
40
|
+
if table_exists?(:rails_onboarding_analytics_events)
|
|
41
|
+
# Named distinctly from the analytics migration's own same-columns-prefixed
|
|
42
|
+
# indexes (index_analytics_events_on_user_and_type is [:user_type, :user_id,
|
|
43
|
+
# :event_type]; index_analytics_events_on_type_and_date is [:event_type,
|
|
44
|
+
# :occurred_at]) - reusing those names here for different column sets would
|
|
45
|
+
# raise a duplicate-index-name error on every fresh install.
|
|
46
|
+
unless index_exists?(:rails_onboarding_analytics_events, [:user_id, :event_type])
|
|
47
|
+
add_index :rails_onboarding_analytics_events, [:user_id, :event_type],
|
|
48
|
+
name: 'index_analytics_events_on_user_id_and_event_type', **concurrent_index_options
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
unless index_exists?(:rails_onboarding_analytics_events, [:event_type, :created_at])
|
|
52
|
+
add_index :rails_onboarding_analytics_events, [:event_type, :created_at],
|
|
53
|
+
name: 'index_analytics_events_on_type_and_created_at', **concurrent_index_options
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
unless index_exists?(:rails_onboarding_analytics_events, :session_id)
|
|
57
|
+
add_index :rails_onboarding_analytics_events, :session_id, **concurrent_index_options
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
unless index_exists?(:rails_onboarding_analytics_events, [:user_id, :session_id])
|
|
61
|
+
add_index :rails_onboarding_analytics_events, [:user_id, :session_id],
|
|
62
|
+
name: 'index_analytics_events_on_user_and_session', **concurrent_index_options
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def down
|
|
68
|
+
# Remove indexes in reverse order
|
|
69
|
+
if table_exists?(:rails_onboarding_analytics_events)
|
|
70
|
+
remove_index :rails_onboarding_analytics_events, name: 'index_analytics_events_on_user_and_session' if index_exists?(:rails_onboarding_analytics_events, [:user_id, :session_id], name: 'index_analytics_events_on_user_and_session')
|
|
71
|
+
remove_index :rails_onboarding_analytics_events, :session_id if index_exists?(:rails_onboarding_analytics_events, :session_id)
|
|
72
|
+
remove_index :rails_onboarding_analytics_events, name: 'index_analytics_events_on_type_and_created_at' if index_exists?(:rails_onboarding_analytics_events, [:event_type, :created_at], name: 'index_analytics_events_on_type_and_created_at')
|
|
73
|
+
remove_index :rails_onboarding_analytics_events, name: 'index_analytics_events_on_user_id_and_event_type' if index_exists?(:rails_onboarding_analytics_events, [:user_id, :event_type], name: 'index_analytics_events_on_user_id_and_event_type')
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
remove_index :users, :last_milestone_at if index_exists?(:users, :last_milestone_at)
|
|
77
|
+
remove_index :users, :onboarding_completed_at if index_exists?(:users, :onboarding_completed_at)
|
|
78
|
+
remove_index :users, name: 'index_users_on_onboarding_status_and_created' if index_exists?(:users, [:onboarding_completed, :created_at], name: 'index_users_on_onboarding_status_and_created')
|
|
79
|
+
remove_index :users, :onboarding_skipped if index_exists?(:users, :onboarding_skipped)
|
|
80
|
+
remove_index :users, :onboarding_current_step if index_exists?(:users, :onboarding_current_step)
|
|
81
|
+
remove_index :users, :onboarding_completed if index_exists?(:users, :onboarding_completed)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
def concurrent_index_options
|
|
87
|
+
postgresql? ? { algorithm: :concurrently } : {}
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def postgresql?
|
|
91
|
+
%w[postgresql postgis].include?(ActiveRecord::Base.connection.adapter_name.downcase)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
class AddOnboardingToUsers < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
|
2
|
+
disable_ddl_transaction!
|
|
3
|
+
|
|
4
|
+
def up
|
|
5
|
+
add_column :users, :onboarding_completed, :boolean, default: false, null: false
|
|
6
|
+
add_column :users, :onboarding_completed_at, :datetime
|
|
7
|
+
add_column :users, :onboarding_current_step, :string
|
|
8
|
+
add_column :users, :onboarding_skipped, :boolean, default: false
|
|
9
|
+
|
|
10
|
+
# Use jsonb for PostgreSQL, json for MySQL/MariaDB, text for others
|
|
11
|
+
adapter_name = ActiveRecord::Base.connection.adapter_name.downcase
|
|
12
|
+
case adapter_name
|
|
13
|
+
when "postgresql", "postgis"
|
|
14
|
+
add_column :users, :feature_tooltips_shown, :jsonb, default: {}
|
|
15
|
+
when "mysql2", "trilogy", "mysql"
|
|
16
|
+
add_column :users, :feature_tooltips_shown, :json
|
|
17
|
+
else
|
|
18
|
+
# SQLite and other databases - use text with serialization
|
|
19
|
+
add_column :users, :feature_tooltips_shown, :text, default: "{}"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Performance indexes. On PostgreSQL, build them concurrently so they
|
|
23
|
+
# don't hold a write lock on a large, live users table; other adapters
|
|
24
|
+
# don't support that algorithm, so fall back to a plain index there.
|
|
25
|
+
add_index :users, :onboarding_completed, **concurrent_index_options
|
|
26
|
+
add_index :users, :onboarding_current_step, **concurrent_index_options
|
|
27
|
+
add_index :users, [:onboarding_completed, :created_at], name: 'index_users_on_onboarding_status_and_created', **concurrent_index_options
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def down
|
|
31
|
+
remove_index :users, name: 'index_users_on_onboarding_status_and_created' if index_exists?(:users, [:onboarding_completed, :created_at], name: 'index_users_on_onboarding_status_and_created')
|
|
32
|
+
remove_index :users, :onboarding_current_step if index_exists?(:users, :onboarding_current_step)
|
|
33
|
+
remove_index :users, :onboarding_completed if index_exists?(:users, :onboarding_completed)
|
|
34
|
+
|
|
35
|
+
remove_column :users, :feature_tooltips_shown if column_exists?(:users, :feature_tooltips_shown)
|
|
36
|
+
remove_column :users, :onboarding_skipped if column_exists?(:users, :onboarding_skipped)
|
|
37
|
+
remove_column :users, :onboarding_current_step if column_exists?(:users, :onboarding_current_step)
|
|
38
|
+
remove_column :users, :onboarding_completed_at if column_exists?(:users, :onboarding_completed_at)
|
|
39
|
+
remove_column :users, :onboarding_completed if column_exists?(:users, :onboarding_completed)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def concurrent_index_options
|
|
45
|
+
postgresql? ? { algorithm: :concurrently } : {}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def postgresql?
|
|
49
|
+
%w[postgresql postgis].include?(ActiveRecord::Base.connection.adapter_name.downcase)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
class AddRobustnessFieldsToUsers < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
|
2
|
+
def up
|
|
3
|
+
add_column :users, :onboarding_errors, :text unless column_exists?(:users, :onboarding_errors)
|
|
4
|
+
add_column :users, :onboarding_failed_actions, :text unless column_exists?(:users, :onboarding_failed_actions)
|
|
5
|
+
add_column :users, :onboarding_session_data, :text unless column_exists?(:users, :onboarding_session_data)
|
|
6
|
+
|
|
7
|
+
# Add indexes for performance
|
|
8
|
+
add_index :users, :onboarding_completed unless index_exists?(:users, :onboarding_completed)
|
|
9
|
+
add_index :users, :onboarding_current_step unless index_exists?(:users, :onboarding_current_step)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def down
|
|
13
|
+
remove_index :users, :onboarding_current_step if index_exists?(:users, :onboarding_current_step)
|
|
14
|
+
remove_index :users, :onboarding_completed if index_exists?(:users, :onboarding_completed)
|
|
15
|
+
|
|
16
|
+
remove_column :users, :onboarding_session_data if column_exists?(:users, :onboarding_session_data)
|
|
17
|
+
remove_column :users, :onboarding_failed_actions if column_exists?(:users, :onboarding_failed_actions)
|
|
18
|
+
remove_column :users, :onboarding_errors if column_exists?(:users, :onboarding_errors)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
class CreateRailsOnboardingFlows < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
|
2
|
+
def up
|
|
3
|
+
create_table :rails_onboarding_flows do |t|
|
|
4
|
+
t.string :name, null: false
|
|
5
|
+
t.text :description
|
|
6
|
+
t.text :steps, null: false
|
|
7
|
+
t.boolean :active, null: false, default: false
|
|
8
|
+
|
|
9
|
+
t.timestamps
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
add_index :rails_onboarding_flows, :active
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def down
|
|
16
|
+
drop_table :rails_onboarding_flows if table_exists?(:rails_onboarding_flows)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/* Custom onboarding styles for your application */
|
|
2
|
+
/*
|
|
3
|
+
* This file is for customizing the onboarding appearance
|
|
4
|
+
* to match your application's design system.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/* Example customizations: */
|
|
8
|
+
.onboarding-container {
|
|
9
|
+
/* Override default styles here */
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.onboarding-progress {
|
|
13
|
+
/* Customize progress bar appearance */
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.onboarding-step {
|
|
17
|
+
/* Customize step appearance */
|
|
18
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# RailsOnboarding configuration
|
|
2
|
+
#
|
|
3
|
+
# See https://github.com/bunnahabhain/rails_onboarding for the full option
|
|
4
|
+
# reference, and docs/MILESTONES_GUIDE.md / docs/ANALYTICS_GUIDE.md for the
|
|
5
|
+
# features configured below.
|
|
6
|
+
RailsOnboarding.configure do |config|
|
|
7
|
+
# The class name of your application's user model. Change this if your
|
|
8
|
+
# app uses a different name (e.g. "Account", "Member").
|
|
9
|
+
config.user_class_name = "User"
|
|
10
|
+
|
|
11
|
+
# Where to send users after they finish or skip onboarding. Accepts a
|
|
12
|
+
# route helper name (as a symbol) or a Proc that receives the user and
|
|
13
|
+
# returns a path.
|
|
14
|
+
config.redirect_after_completion = :root_path
|
|
15
|
+
config.redirect_after_skip = :root_path
|
|
16
|
+
|
|
17
|
+
# Feature toggles
|
|
18
|
+
config.enable_tooltips = true
|
|
19
|
+
config.enable_milestones = true
|
|
20
|
+
config.enable_analytics = true
|
|
21
|
+
|
|
22
|
+
# Analytics event retention/session settings. Only relevant when
|
|
23
|
+
# enable_analytics is true. See docs/ANALYTICS_GUIDE.md.
|
|
24
|
+
config.analytics_data_retention_days = 365 # how long to keep analytics events
|
|
25
|
+
config.analytics_session_timeout_minutes = 30 # inactivity gap that starts a new session
|
|
26
|
+
|
|
27
|
+
# Who should be sent through onboarding?
|
|
28
|
+
# :new_users - only users created after the gem was installed (default)
|
|
29
|
+
# :all_users - every user, regardless of when their account was created
|
|
30
|
+
# a Proc - receives the user and returns true/false, e.g.:
|
|
31
|
+
# config.onboarding_required_for = ->(user) { !user.admin? }
|
|
32
|
+
config.onboarding_required_for = :new_users
|
|
33
|
+
|
|
34
|
+
# The steps a user walks through, in order. Each step needs a unique
|
|
35
|
+
# `name` (used internally for progress tracking) and a `title` shown in
|
|
36
|
+
# the UI. Set `skippable: true` to let users jump past a step.
|
|
37
|
+
#
|
|
38
|
+
# Customize this list to match your app's onboarding flow - add, remove,
|
|
39
|
+
# or reorder steps as needed. If you add a step, you'll also need a
|
|
40
|
+
# matching view under app/views/rails_onboarding/onboarding/<name>.html.erb
|
|
41
|
+
# (see the generated views for examples).
|
|
42
|
+
config.steps = [
|
|
43
|
+
{
|
|
44
|
+
name: :welcome,
|
|
45
|
+
title: "Welcome",
|
|
46
|
+
icon: "🎉",
|
|
47
|
+
skippable: true
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: :profile,
|
|
51
|
+
title: "Setup Profile",
|
|
52
|
+
icon: "👤",
|
|
53
|
+
skippable: false
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: :explore,
|
|
57
|
+
title: "Explore Features",
|
|
58
|
+
icon: "🔍",
|
|
59
|
+
skippable: true
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
# Contextual tooltips shown to highlight specific UI elements. The key
|
|
64
|
+
# is the tooltip's identifier (referenced from your views/helpers), and
|
|
65
|
+
# each entry configures the text, delay (ms) before it appears, and
|
|
66
|
+
# position relative to its target element ("top", "bottom", "left",
|
|
67
|
+
# "right"). Only relevant when enable_tooltips is true.
|
|
68
|
+
config.feature_tooltips = {
|
|
69
|
+
"getting_started" => {
|
|
70
|
+
text: "Click here to get started!",
|
|
71
|
+
delay: 1000,
|
|
72
|
+
position: "bottom"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
require "rails/generators/base"
|
|
2
|
+
require "rails_onboarding/version"
|
|
3
|
+
|
|
4
|
+
module RailsOnboarding
|
|
5
|
+
module Generators
|
|
6
|
+
# Re-copies the gem's Stimulus controllers into a bundler-based host app so
|
|
7
|
+
# that a gem upgrade actually reaches the app. esbuild/webpack/vite apps
|
|
8
|
+
# bundle their own copy of these controllers (see docs/ESBUILD_SETUP.md) rather
|
|
9
|
+
# than importing them from the engine the way importmap apps do, so the copy
|
|
10
|
+
# goes stale on every gem bump unless it is re-synced. This generator is that
|
|
11
|
+
# re-sync step - run it after updating the gem:
|
|
12
|
+
#
|
|
13
|
+
# bin/rails generate rails_onboarding:update
|
|
14
|
+
# bin/rails generate rails_onboarding:update --path some/other/dir
|
|
15
|
+
#
|
|
16
|
+
# It copies the controllers flat into app/javascript/controllers (so Stimulus
|
|
17
|
+
# derives the flat identifiers the gem's views use - onboarding, tooltip,
|
|
18
|
+
# progress, ... - not prefixed rails-onboarding--* ones; see docs/ESBUILD_SETUP.md).
|
|
19
|
+
# The gem's application.js is intentionally NOT copied: it would clobber the
|
|
20
|
+
# host's own controllers/application.js entrypoint, and nothing imports it.
|
|
21
|
+
#
|
|
22
|
+
# It overwrites the vendored controllers unconditionally (that is the point -
|
|
23
|
+
# they are meant to mirror the gem, so there is nothing to preserve), but is
|
|
24
|
+
# deliberately scoped to those files ONLY: it never touches your initializer,
|
|
25
|
+
# migrations, rails_onboarding_custom.css, or your onboarding step views.
|
|
26
|
+
# Importmap apps don't need it (they import the controllers from the gem).
|
|
27
|
+
class UpdateGenerator < Rails::Generators::Base
|
|
28
|
+
source_root File.expand_path("../../../app/assets/javascripts/rails_onboarding", __dir__)
|
|
29
|
+
|
|
30
|
+
# Written into the destination so the engine's boot-time staleness check
|
|
31
|
+
# (see Engine.warn_if_controllers_stale) can tell which gem version the
|
|
32
|
+
# copied controllers came from. Keep in sync with that constant.
|
|
33
|
+
VERSION_MARKER = ".rails_onboarding_version".freeze
|
|
34
|
+
|
|
35
|
+
class_option :path, type: :string,
|
|
36
|
+
default: "app/javascript/controllers",
|
|
37
|
+
desc: "Destination directory for the copied Stimulus controllers"
|
|
38
|
+
|
|
39
|
+
def copy_controllers
|
|
40
|
+
controller_paths.each do |rel|
|
|
41
|
+
copy_file rel, File.join(dest_dir, rel), force: true
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def stamp_version
|
|
46
|
+
create_file File.join(dest_dir, VERSION_MARKER),
|
|
47
|
+
"#{RailsOnboarding::VERSION}\n", force: true
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def print_next_steps
|
|
51
|
+
say ""
|
|
52
|
+
say "rails_onboarding: synced #{controller_paths.size} controller(s) to " \
|
|
53
|
+
"#{dest_dir} (v#{RailsOnboarding::VERSION}).", :green
|
|
54
|
+
say "Next, re-register the controllers and rebuild your JS bundle:", :yellow
|
|
55
|
+
say " bin/rails stimulus:manifest:update # if you use stimulus-rails", :yellow
|
|
56
|
+
say " yarn build # or your bundler's build command", :yellow
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def dest_dir
|
|
62
|
+
options[:path]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Every *_controller.js the gem ships (top-level + the admin/ subfolder,
|
|
66
|
+
# which yields admin--* identifiers), as paths relative to source_root.
|
|
67
|
+
# Globbing *_controller.js deliberately excludes application.js.
|
|
68
|
+
def controller_paths
|
|
69
|
+
@controller_paths ||= Dir.glob("**/*_controller.js", base: self.class.source_root).sort
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|