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,211 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsOnboarding
|
|
4
|
+
# Concern for personalization capabilities on User models
|
|
5
|
+
# Adapts onboarding flows based on user type, role, or other attributes
|
|
6
|
+
#
|
|
7
|
+
# @example Include in your User model
|
|
8
|
+
# class User < ApplicationRecord
|
|
9
|
+
# include RailsOnboarding::Onboardable
|
|
10
|
+
# include RailsOnboarding::Personalizable
|
|
11
|
+
# end
|
|
12
|
+
#
|
|
13
|
+
# @example Configure personalized flows
|
|
14
|
+
# RailsOnboarding.configure do |config|
|
|
15
|
+
# config.personalization_enabled = true
|
|
16
|
+
# config.user_type_method = :account_type
|
|
17
|
+
# config.personalized_flows = {
|
|
18
|
+
# individual: [
|
|
19
|
+
# { name: :welcome, title: "Welcome", icon: "🎉" },
|
|
20
|
+
# { name: :profile, title: "Setup Profile", icon: "👤" }
|
|
21
|
+
# ],
|
|
22
|
+
# business: [
|
|
23
|
+
# { name: :welcome, title: "Welcome", icon: "🎉" },
|
|
24
|
+
# { name: :company, title: "Company Info", icon: "🏢" },
|
|
25
|
+
# { name: :team, title: "Team Setup", icon: "👥" }
|
|
26
|
+
# ]
|
|
27
|
+
# }
|
|
28
|
+
# end
|
|
29
|
+
module Personalizable
|
|
30
|
+
extend ActiveSupport::Concern
|
|
31
|
+
|
|
32
|
+
included do
|
|
33
|
+
# Callback to set personalized steps on onboarding start
|
|
34
|
+
before_save :apply_personalized_flow, if: -> { will_save_change_to_onboarding_current_step? && onboarding_current_step_was.nil? }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Get the user's type for personalization
|
|
38
|
+
#
|
|
39
|
+
# @return [Symbol, String] The user type
|
|
40
|
+
#
|
|
41
|
+
# @example
|
|
42
|
+
# user.personalization_type
|
|
43
|
+
# # => :business
|
|
44
|
+
def personalization_type
|
|
45
|
+
return nil unless RailsOnboarding.configuration.personalization_enabled
|
|
46
|
+
|
|
47
|
+
method_name = RailsOnboarding.configuration.user_type_method
|
|
48
|
+
return nil unless respond_to?(method_name)
|
|
49
|
+
|
|
50
|
+
send(method_name)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Get the personalized onboarding steps for this user
|
|
54
|
+
#
|
|
55
|
+
# @return [Array<Hash>] Array of step configurations
|
|
56
|
+
#
|
|
57
|
+
# @example
|
|
58
|
+
# user.personalized_steps
|
|
59
|
+
# # => [{name: :welcome, title: "Welcome"}, ...]
|
|
60
|
+
def personalized_steps
|
|
61
|
+
return RailsOnboarding.configuration.steps unless RailsOnboarding.configuration.personalization_enabled
|
|
62
|
+
|
|
63
|
+
user_type = personalization_type
|
|
64
|
+
return RailsOnboarding.configuration.steps unless user_type
|
|
65
|
+
|
|
66
|
+
flow = RailsOnboarding.configuration.personalized_flow(user_type)
|
|
67
|
+
flow || RailsOnboarding.configuration.steps
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Get the total number of steps in the personalized flow
|
|
71
|
+
#
|
|
72
|
+
# @return [Integer] Number of steps
|
|
73
|
+
def personalized_total_steps
|
|
74
|
+
personalized_steps.size
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Get a specific step from the personalized flow
|
|
78
|
+
#
|
|
79
|
+
# @param name [String, Symbol] Step name
|
|
80
|
+
# @return [Hash, nil] Step configuration or nil
|
|
81
|
+
def personalized_step_by_name(name)
|
|
82
|
+
return nil if name.nil?
|
|
83
|
+
personalized_steps.find { |s| s[:name].to_s == name.to_s }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Get the index of a step in the personalized flow
|
|
87
|
+
#
|
|
88
|
+
# @param name [String, Symbol] Step name
|
|
89
|
+
# @return [Integer, nil] Step index or nil
|
|
90
|
+
def personalized_step_index(name)
|
|
91
|
+
return nil if name.nil?
|
|
92
|
+
personalized_steps.find_index { |s| s[:name].to_s == name.to_s }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Get the next step in the personalized flow
|
|
96
|
+
#
|
|
97
|
+
# @return [Hash, nil] Next step configuration or nil
|
|
98
|
+
def personalized_next_step
|
|
99
|
+
return nil if onboarding_current_step.nil?
|
|
100
|
+
|
|
101
|
+
current_index = personalized_step_index(onboarding_current_step)
|
|
102
|
+
return nil if current_index.nil?
|
|
103
|
+
|
|
104
|
+
next_index = current_index + 1
|
|
105
|
+
return nil if next_index >= personalized_total_steps
|
|
106
|
+
|
|
107
|
+
personalized_steps[next_index]
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Get the previous step in the personalized flow
|
|
111
|
+
#
|
|
112
|
+
# @return [Hash, nil] Previous step configuration or nil
|
|
113
|
+
def personalized_previous_step
|
|
114
|
+
return nil if onboarding_current_step.nil?
|
|
115
|
+
|
|
116
|
+
current_index = personalized_step_index(onboarding_current_step)
|
|
117
|
+
return nil if current_index.nil? || current_index.zero?
|
|
118
|
+
|
|
119
|
+
personalized_steps[current_index - 1]
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Check if this is the first step in the personalized flow
|
|
123
|
+
#
|
|
124
|
+
# @return [Boolean]
|
|
125
|
+
def personalized_first_step?
|
|
126
|
+
return false if onboarding_current_step.nil?
|
|
127
|
+
personalized_step_index(onboarding_current_step)&.zero? || false
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Check if this is the last step in the personalized flow
|
|
131
|
+
#
|
|
132
|
+
# @return [Boolean]
|
|
133
|
+
def personalized_last_step?
|
|
134
|
+
return false if onboarding_current_step.nil?
|
|
135
|
+
|
|
136
|
+
current_index = personalized_step_index(onboarding_current_step)
|
|
137
|
+
return false if current_index.nil?
|
|
138
|
+
|
|
139
|
+
current_index == personalized_total_steps - 1
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Get the progress percentage for the personalized flow
|
|
143
|
+
#
|
|
144
|
+
# @return [Integer] Progress as percentage (0-100)
|
|
145
|
+
def personalized_progress_percentage
|
|
146
|
+
return 0 if onboarding_current_step.nil?
|
|
147
|
+
|
|
148
|
+
current_index = personalized_step_index(onboarding_current_step)
|
|
149
|
+
return 0 if current_index.nil?
|
|
150
|
+
|
|
151
|
+
((current_index + 1).to_f / personalized_total_steps * 100).round
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Check if a specific feature should be shown to this user type
|
|
155
|
+
#
|
|
156
|
+
# @param feature_name [String, Symbol] Feature name
|
|
157
|
+
# @return [Boolean] True if feature should be shown
|
|
158
|
+
#
|
|
159
|
+
# @example
|
|
160
|
+
# user.show_personalized_feature?(:team_invite)
|
|
161
|
+
# # => true (for business users)
|
|
162
|
+
def show_personalized_feature?(feature_name)
|
|
163
|
+
return true unless RailsOnboarding.configuration.personalization_enabled
|
|
164
|
+
|
|
165
|
+
user_type = personalization_type
|
|
166
|
+
return true unless user_type
|
|
167
|
+
|
|
168
|
+
# Check if feature is in the personalized steps
|
|
169
|
+
personalized_steps.any? { |step| step[:name].to_s == feature_name.to_s }
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Get recommended next actions based on user type
|
|
173
|
+
#
|
|
174
|
+
# @return [Array<Hash>] Array of recommended actions
|
|
175
|
+
#
|
|
176
|
+
# @example
|
|
177
|
+
# user.personalized_recommendations
|
|
178
|
+
# # => [{title: "Invite Team", description: "...", action: :invite_team}]
|
|
179
|
+
def personalized_recommendations
|
|
180
|
+
return [] unless RailsOnboarding.configuration.personalization_enabled
|
|
181
|
+
return [] unless respond_to?(:personalization_recommendations)
|
|
182
|
+
|
|
183
|
+
send(:personalization_recommendations)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
private
|
|
187
|
+
|
|
188
|
+
# Apply the personalized flow when onboarding starts
|
|
189
|
+
# This is called automatically before save when onboarding_current_step changes from nil
|
|
190
|
+
def apply_personalized_flow
|
|
191
|
+
return unless RailsOnboarding.configuration.personalization_enabled
|
|
192
|
+
return unless personalization_type
|
|
193
|
+
|
|
194
|
+
# Store the personalized flow in a custom attribute if available
|
|
195
|
+
if respond_to?(:personalized_flow_type=)
|
|
196
|
+
self.personalized_flow_type = personalization_type.to_s
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Track the personalization in analytics if available
|
|
200
|
+
if respond_to?(:track_analytics_event)
|
|
201
|
+
track_analytics_event(
|
|
202
|
+
"personalized_flow_applied",
|
|
203
|
+
{
|
|
204
|
+
user_type: personalization_type.to_s,
|
|
205
|
+
flow_steps: personalized_steps.map { |s| s[:name] }.join(",")
|
|
206
|
+
}
|
|
207
|
+
)
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsOnboarding
|
|
4
|
+
# Concern for progressive disclosure of features
|
|
5
|
+
# Shows features gradually over time based on user behavior and progress
|
|
6
|
+
#
|
|
7
|
+
# @example Include in your User model
|
|
8
|
+
# class User < ApplicationRecord
|
|
9
|
+
# include RailsOnboarding::Onboardable
|
|
10
|
+
# include RailsOnboarding::ProgressiveDisclosure
|
|
11
|
+
# end
|
|
12
|
+
#
|
|
13
|
+
# @example Configure progressive features
|
|
14
|
+
# RailsOnboarding.configure do |config|
|
|
15
|
+
# config.progressive_disclosure_enabled = true
|
|
16
|
+
# config.progressive_features = [
|
|
17
|
+
# {
|
|
18
|
+
# key: :advanced_settings,
|
|
19
|
+
# reveal_condition: :time_based,
|
|
20
|
+
# delay: 7.days,
|
|
21
|
+
# title: "Advanced Settings",
|
|
22
|
+
# description: "Now that you're familiar with the basics..."
|
|
23
|
+
# },
|
|
24
|
+
# {
|
|
25
|
+
# key: :team_collaboration,
|
|
26
|
+
# reveal_condition: :action_based,
|
|
27
|
+
# check_method: :has_created_first_project?,
|
|
28
|
+
# title: "Team Collaboration",
|
|
29
|
+
# description: "Ready to invite your team?"
|
|
30
|
+
# }
|
|
31
|
+
# ]
|
|
32
|
+
# end
|
|
33
|
+
module ProgressiveDisclosure
|
|
34
|
+
extend ActiveSupport::Concern
|
|
35
|
+
|
|
36
|
+
included do
|
|
37
|
+
# Store revealed features as JSON
|
|
38
|
+
# Expected column: revealed_features (jsonb or text)
|
|
39
|
+
serialize :revealed_features, coder: JSON unless column_names.include?("revealed_features") && columns_hash["revealed_features"].type == :jsonb
|
|
40
|
+
|
|
41
|
+
# Callback to check and reveal features
|
|
42
|
+
after_save :check_progressive_features, if: :saved_change_to_onboarding_current_step?
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Check if a feature has been revealed to the user
|
|
46
|
+
#
|
|
47
|
+
# @param feature_key [String, Symbol] Feature key
|
|
48
|
+
# @return [Boolean] True if feature is revealed
|
|
49
|
+
#
|
|
50
|
+
# @example
|
|
51
|
+
# user.feature_revealed?(:advanced_settings)
|
|
52
|
+
# # => false
|
|
53
|
+
def feature_revealed?(feature_key)
|
|
54
|
+
return true unless RailsOnboarding.configuration.progressive_disclosure_enabled
|
|
55
|
+
|
|
56
|
+
features = revealed_features || []
|
|
57
|
+
features.include?(feature_key.to_s)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Mark a feature as revealed
|
|
61
|
+
#
|
|
62
|
+
# @param feature_key [String, Symbol] Feature key
|
|
63
|
+
# @param metadata [Hash] Additional metadata about the reveal
|
|
64
|
+
# @return [Boolean] True if successfully marked as revealed
|
|
65
|
+
#
|
|
66
|
+
# @example
|
|
67
|
+
# user.reveal_feature(:advanced_settings, source: :automatic)
|
|
68
|
+
def reveal_feature(feature_key, metadata = {})
|
|
69
|
+
self.revealed_features ||= []
|
|
70
|
+
return false if revealed_features.include?(feature_key.to_s)
|
|
71
|
+
|
|
72
|
+
revealed_features << feature_key.to_s
|
|
73
|
+
|
|
74
|
+
# Track the reveal event
|
|
75
|
+
if respond_to?(:track_analytics_event)
|
|
76
|
+
track_analytics_event(
|
|
77
|
+
"feature_revealed",
|
|
78
|
+
{
|
|
79
|
+
feature_key: feature_key.to_s,
|
|
80
|
+
reveal_time: Time.current
|
|
81
|
+
}.merge(metadata)
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
save if persisted?
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Hide a previously revealed feature
|
|
89
|
+
#
|
|
90
|
+
# @param feature_key [String, Symbol] Feature key
|
|
91
|
+
# @return [Boolean] True if successfully hidden
|
|
92
|
+
def hide_feature(feature_key)
|
|
93
|
+
return false unless revealed_features
|
|
94
|
+
|
|
95
|
+
revealed_features.delete(feature_key.to_s)
|
|
96
|
+
save if persisted?
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Get all revealed features
|
|
100
|
+
#
|
|
101
|
+
# @return [Array<String>] Array of revealed feature keys
|
|
102
|
+
def all_revealed_features
|
|
103
|
+
revealed_features || []
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Get features that are ready to be revealed
|
|
107
|
+
#
|
|
108
|
+
# @return [Array<Hash>] Array of feature configurations ready to reveal
|
|
109
|
+
#
|
|
110
|
+
# @example
|
|
111
|
+
# user.features_ready_to_reveal
|
|
112
|
+
# # => [{key: :advanced_settings, title: "...", ...}]
|
|
113
|
+
def features_ready_to_reveal
|
|
114
|
+
return [] unless RailsOnboarding.configuration.progressive_disclosure_enabled
|
|
115
|
+
|
|
116
|
+
features = RailsOnboarding.configuration.progressive_features || []
|
|
117
|
+
|
|
118
|
+
features.select do |feature|
|
|
119
|
+
# Skip if already revealed
|
|
120
|
+
next false if feature_revealed?(feature[:key])
|
|
121
|
+
|
|
122
|
+
# Check if feature meets reveal conditions
|
|
123
|
+
feature_ready?(feature)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Check if a specific feature is ready to be revealed
|
|
128
|
+
#
|
|
129
|
+
# @param feature [Hash] Feature configuration
|
|
130
|
+
# @return [Boolean] True if feature is ready
|
|
131
|
+
def feature_ready?(feature)
|
|
132
|
+
case feature[:reveal_condition]
|
|
133
|
+
when :time_based
|
|
134
|
+
time_based_ready?(feature)
|
|
135
|
+
when :action_based
|
|
136
|
+
action_based_ready?(feature)
|
|
137
|
+
when :step_based
|
|
138
|
+
step_based_ready?(feature)
|
|
139
|
+
when :milestone_based
|
|
140
|
+
milestone_based_ready?(feature)
|
|
141
|
+
when :engagement_based
|
|
142
|
+
engagement_based_ready?(feature)
|
|
143
|
+
else
|
|
144
|
+
false
|
|
145
|
+
end
|
|
146
|
+
rescue StandardError => e
|
|
147
|
+
Rails.logger.error("Error checking feature readiness: #{e.message}") if defined?(Rails)
|
|
148
|
+
false
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Reveal all features that are ready
|
|
152
|
+
#
|
|
153
|
+
# @return [Array<String>] Array of newly revealed feature keys
|
|
154
|
+
def reveal_ready_features!
|
|
155
|
+
newly_revealed = []
|
|
156
|
+
|
|
157
|
+
features_ready_to_reveal.each do |feature|
|
|
158
|
+
if reveal_feature(feature[:key], source: :automatic, condition: feature[:reveal_condition])
|
|
159
|
+
newly_revealed << feature[:key].to_s
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
newly_revealed
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Get the next feature that will be revealed
|
|
167
|
+
#
|
|
168
|
+
# @return [Hash, nil] Next feature configuration or nil
|
|
169
|
+
def next_feature_to_reveal
|
|
170
|
+
features = RailsOnboarding.configuration.progressive_features || []
|
|
171
|
+
|
|
172
|
+
features
|
|
173
|
+
.reject { |f| feature_revealed?(f[:key]) }
|
|
174
|
+
.min_by { |f| estimated_reveal_time(f) }
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Estimate when a feature will be revealed
|
|
178
|
+
#
|
|
179
|
+
# @param feature [Hash] Feature configuration
|
|
180
|
+
# @return [Time, nil] Estimated reveal time or nil
|
|
181
|
+
def estimated_reveal_time(feature)
|
|
182
|
+
case feature[:reveal_condition]
|
|
183
|
+
when :time_based
|
|
184
|
+
created_at + feature[:delay].seconds
|
|
185
|
+
when :step_based
|
|
186
|
+
nil # Can't estimate step-based reveals
|
|
187
|
+
else
|
|
188
|
+
nil
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Get a count of how many features have been revealed
|
|
193
|
+
#
|
|
194
|
+
# @return [Integer] Count of revealed features
|
|
195
|
+
def revealed_features_count
|
|
196
|
+
all_revealed_features.size
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Get a count of features pending reveal
|
|
200
|
+
#
|
|
201
|
+
# @return [Integer] Count of pending features
|
|
202
|
+
def pending_features_count
|
|
203
|
+
return 0 unless RailsOnboarding.configuration.progressive_disclosure_enabled
|
|
204
|
+
|
|
205
|
+
total_features = RailsOnboarding.configuration.progressive_features&.size || 0
|
|
206
|
+
total_features - revealed_features_count
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
private
|
|
210
|
+
|
|
211
|
+
# Check if time-based feature is ready
|
|
212
|
+
def time_based_ready?(feature)
|
|
213
|
+
return false unless created_at
|
|
214
|
+
|
|
215
|
+
delay = feature[:delay] || 0
|
|
216
|
+
created_at + delay.seconds <= Time.current
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Check if action-based feature is ready
|
|
220
|
+
def action_based_ready?(feature)
|
|
221
|
+
method_name = feature[:check_method]
|
|
222
|
+
return false unless method_name
|
|
223
|
+
return false unless respond_to?(method_name)
|
|
224
|
+
|
|
225
|
+
send(method_name)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Check if step-based feature is ready
|
|
229
|
+
def step_based_ready?(feature)
|
|
230
|
+
return false unless onboarding_current_step
|
|
231
|
+
|
|
232
|
+
after_step = feature[:after_step]
|
|
233
|
+
return false unless after_step
|
|
234
|
+
|
|
235
|
+
current_index = RailsOnboarding.configuration.step_index(onboarding_current_step)
|
|
236
|
+
required_index = RailsOnboarding.configuration.step_index(after_step)
|
|
237
|
+
|
|
238
|
+
return false if current_index.nil? || required_index.nil?
|
|
239
|
+
|
|
240
|
+
current_index >= required_index
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Check if milestone-based feature is ready
|
|
244
|
+
def milestone_based_ready?(feature)
|
|
245
|
+
return false unless respond_to?(:earned_milestones)
|
|
246
|
+
|
|
247
|
+
required_milestone = feature[:required_milestone]
|
|
248
|
+
return false unless required_milestone
|
|
249
|
+
|
|
250
|
+
earned_milestones.include?(required_milestone.to_s)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# Check if engagement-based feature is ready
|
|
254
|
+
def engagement_based_ready?(feature)
|
|
255
|
+
return false unless respond_to?(:analytics_events)
|
|
256
|
+
|
|
257
|
+
min_events = feature[:min_events] || 0
|
|
258
|
+
event_type = feature[:event_type]
|
|
259
|
+
|
|
260
|
+
if event_type
|
|
261
|
+
analytics_events.where(event_type: event_type).count >= min_events
|
|
262
|
+
else
|
|
263
|
+
analytics_events.count >= min_events
|
|
264
|
+
end
|
|
265
|
+
rescue StandardError
|
|
266
|
+
false
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# Callback to check and reveal features after step changes
|
|
270
|
+
def check_progressive_features
|
|
271
|
+
return unless RailsOnboarding.configuration.progressive_disclosure_enabled
|
|
272
|
+
|
|
273
|
+
reveal_ready_features!
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|