rails_onboarding 0.8.1 → 0.8.2
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
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6a33d58aa73f7c851196a6ed683c0a907eabc60addda69cc841206ef7765201e
|
|
4
|
+
data.tar.gz: 28b37e7d218f6503e256e35292eb71c48a2074bb18f89be4581e417a98365cc0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b1e1fa8588b97a8402ae6f68c0c968e88a0e8229fff82f9f1e79249bfec60b067f465369800241f56bcf63543663d979dcc5dbb14eefabd7982af921695edcaa
|
|
7
|
+
data.tar.gz: eb6c36a63a60df9e93e83713022ffa175d6579fba9cd9b73246f1a82f8c1cc7fa165729b8d5f852564d494e246e7e0b099fcd3a6478bf184cb5a7d1e36ec125a
|
|
@@ -46,18 +46,30 @@ module RailsOnboarding
|
|
|
46
46
|
@analytics_error = e.message
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
+
# Milestones have no table of their own. They are *defined* in the host's
|
|
50
|
+
# initializer (RailsOnboarding.configuration.milestones) and *awarded*
|
|
51
|
+
# onto the user record: `milestones_achieved` holds a serialized array of
|
|
52
|
+
# {"key", "achieved_at"} hashes, with `milestone_points` and
|
|
53
|
+
# `last_milestone_at` beside it.
|
|
54
|
+
#
|
|
55
|
+
# This panel used to query a RailsOnboarding::Milestone model and a
|
|
56
|
+
# rails_onboarding_milestone_achievements join table. Neither has ever
|
|
57
|
+
# existed in the engine, so the `defined?` guard was always false and the
|
|
58
|
+
# whole section returned early - silently, while real achievements piled
|
|
59
|
+
# up on the users table. Guard on the two things actually required
|
|
60
|
+
# instead: milestones being switched on, and a user model that speaks
|
|
61
|
+
# Onboardable.
|
|
49
62
|
def load_milestone_data
|
|
50
|
-
return unless
|
|
63
|
+
return unless RailsOnboarding.configuration.enable_milestones
|
|
64
|
+
return unless user_class.method_defined?(:achieved_milestone_entries)
|
|
51
65
|
|
|
52
|
-
@total_milestones = RailsOnboarding
|
|
66
|
+
@total_milestones = RailsOnboarding.configuration.milestones.size
|
|
53
67
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
@
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
.count
|
|
60
|
-
@top_milestones = top_achieved_milestones
|
|
68
|
+
stats = milestone_achievement_stats
|
|
69
|
+
@milestones_awarded = stats[:awarded]
|
|
70
|
+
@milestone_points_awarded = stats[:points]
|
|
71
|
+
@users_with_milestones = stats[:users]
|
|
72
|
+
@top_milestones = stats[:top]
|
|
61
73
|
rescue StandardError => e
|
|
62
74
|
logger.error "Error loading milestone data: #{e.message}"
|
|
63
75
|
@milestone_error = e.message
|
|
@@ -154,16 +166,78 @@ module RailsOnboarding
|
|
|
154
166
|
trend
|
|
155
167
|
end
|
|
156
168
|
|
|
157
|
-
|
|
158
|
-
|
|
169
|
+
# Roll every user's achievements up into the numbers the panel shows.
|
|
170
|
+
#
|
|
171
|
+
# `milestones_achieved` is a serialized text column, so this cannot be a
|
|
172
|
+
# GROUP BY - the rows have to be loaded and counted in Ruby. Two things
|
|
173
|
+
# keep that honest: only users who hold at least one achievement are
|
|
174
|
+
# loaded, and only the three columns needed are selected. It is still
|
|
175
|
+
# O(users-with-milestones), which is fine into the tens of thousands and
|
|
176
|
+
# is the price of milestones not having a table. An install that outgrows
|
|
177
|
+
# it should denormalise achievements into their own table rather than
|
|
178
|
+
# paginate this.
|
|
179
|
+
#
|
|
180
|
+
# Achievements are counted within the dashboard's selected date range,
|
|
181
|
+
# like every other time-based figure here. Undated achievements are
|
|
182
|
+
# counted regardless: a legacy string entry on a record with no
|
|
183
|
+
# last_milestone_at is still a real award, and dropping it would repeat
|
|
184
|
+
# in miniature exactly the bug this method replaces.
|
|
185
|
+
def milestone_achievement_stats
|
|
186
|
+
counts = Hash.new(0)
|
|
187
|
+
awarded = 0
|
|
188
|
+
points = 0
|
|
189
|
+
users = 0
|
|
190
|
+
|
|
191
|
+
achievement_holders.find_each do |user|
|
|
192
|
+
keys = user.achieved_milestone_entries.filter_map do |key, achieved_at|
|
|
193
|
+
key if achieved_at.nil? || achieved_at >= @start_date
|
|
194
|
+
end
|
|
195
|
+
next if keys.empty?
|
|
196
|
+
|
|
197
|
+
users += 1
|
|
198
|
+
awarded += keys.size
|
|
199
|
+
keys.each do |key|
|
|
200
|
+
counts[key] += 1
|
|
201
|
+
points += RailsOnboarding.configuration.milestone_by_key(key)&.dig(:points).to_i
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
{ awarded: awarded, points: points, users: users, top: top_milestones_from(counts) }
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Users holding at least one achievement. The empty cases are the column's
|
|
209
|
+
# default (NULL) and a serialized empty array, which is what
|
|
210
|
+
# reset_onboarding! leaves behind.
|
|
211
|
+
#
|
|
212
|
+
# The empty-string checks go through raw SQL on purpose: `milestones_achieved`
|
|
213
|
+
# is a serialized attribute, so `where.not(milestones_achieved: "[]")` would
|
|
214
|
+
# hand "[]" to the JSON coder and compare against the string '"[]"' instead.
|
|
215
|
+
# A bare nil is safe - the serialized type passes it straight through.
|
|
216
|
+
def achievement_holders
|
|
217
|
+
column = "#{user_class.quoted_table_name}.milestones_achieved"
|
|
159
218
|
|
|
160
|
-
|
|
161
|
-
.
|
|
162
|
-
.
|
|
163
|
-
.
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
219
|
+
user_class
|
|
220
|
+
.where.not(milestones_achieved: nil)
|
|
221
|
+
.where("#{column} NOT IN (?, ?)", "", "[]")
|
|
222
|
+
.select(:id, :milestones_achieved, :last_milestone_at)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# The five most-awarded milestones, resolved against the configuration for
|
|
226
|
+
# their display copy. A key with no matching configuration entry is one the
|
|
227
|
+
# host has since renamed or removed; show it under its raw key rather than
|
|
228
|
+
# dropping it, so the leftover data is visible and can be cleaned up.
|
|
229
|
+
def top_milestones_from(counts)
|
|
230
|
+
counts.sort_by { |key, count| [ -count, key ] }.first(5).map do |key, count|
|
|
231
|
+
milestone = RailsOnboarding.configuration.milestone_by_key(key)
|
|
232
|
+
|
|
233
|
+
{
|
|
234
|
+
name: key,
|
|
235
|
+
title: milestone&.dig(:title) || key.to_s.humanize,
|
|
236
|
+
icon: milestone&.dig(:icon),
|
|
237
|
+
configured: !milestone.nil?,
|
|
238
|
+
count: count
|
|
239
|
+
}
|
|
240
|
+
end
|
|
167
241
|
end
|
|
168
242
|
|
|
169
243
|
def date_range_start(range)
|
|
@@ -307,6 +307,30 @@ module RailsOnboarding
|
|
|
307
307
|
milestones.map { |m| m.is_a?(Hash) ? m["key"] : m.to_s }
|
|
308
308
|
end
|
|
309
309
|
|
|
310
|
+
# Every achievement as a [key, achieved_at] pair, in the order it was
|
|
311
|
+
# awarded.
|
|
312
|
+
#
|
|
313
|
+
# #achieved_milestones gives the keys and #milestone_achieved_at dates one
|
|
314
|
+
# key at a time; anything that needs both at once (the admin dashboard
|
|
315
|
+
# counting achievements per period, for one) would otherwise re-scan the
|
|
316
|
+
# array once per key and re-implement the two stored formats for itself.
|
|
317
|
+
#
|
|
318
|
+
# +achieved_at+ is nil only for a legacy string entry on a record with no
|
|
319
|
+
# last_milestone_at to fall back on - the achievement is real, its date
|
|
320
|
+
# simply was never stored. Callers filtering by date should decide
|
|
321
|
+
# deliberately what to do with those rather than dropping them by accident.
|
|
322
|
+
#
|
|
323
|
+
# @return [Array<Array(String, Time, nil)>]
|
|
324
|
+
def achieved_milestone_entries
|
|
325
|
+
(milestones_achieved || []).map do |entry|
|
|
326
|
+
if entry.is_a?(Hash)
|
|
327
|
+
[ entry["key"].to_s, parse_achieved_at(entry["achieved_at"]) ]
|
|
328
|
+
else
|
|
329
|
+
[ entry.to_s, last_milestone_at ]
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
310
334
|
def milestone_achieved?(milestone_key)
|
|
311
335
|
achieved_milestones.include?(milestone_key.to_s)
|
|
312
336
|
end
|
|
@@ -654,6 +678,16 @@ module RailsOnboarding
|
|
|
654
678
|
|
|
655
679
|
private
|
|
656
680
|
|
|
681
|
+
# A stored achieved_at, or last_milestone_at when it is missing or
|
|
682
|
+
# unparseable - the same fallback #milestone_achieved_at applies.
|
|
683
|
+
def parse_achieved_at(value)
|
|
684
|
+
return last_milestone_at if value.blank?
|
|
685
|
+
|
|
686
|
+
Time.parse(value.to_s)
|
|
687
|
+
rescue StandardError
|
|
688
|
+
last_milestone_at
|
|
689
|
+
end
|
|
690
|
+
|
|
657
691
|
# Persists +attributes+ (or just re-saves already-assigned attributes if
|
|
658
692
|
# none given), then runs the tracking block only after that succeeds.
|
|
659
693
|
# Analytics tracking (and, transitively, any milestone-achievement it
|
|
@@ -126,19 +126,43 @@
|
|
|
126
126
|
<!-- Bottom Row -->
|
|
127
127
|
<div class="admin-bottom-grid">
|
|
128
128
|
<!-- Milestone Stats -->
|
|
129
|
-
|
|
129
|
+
<%# @total_milestones is assigned only when milestones are enabled and the
|
|
130
|
+
user model speaks Onboardable, so it doubles as the render guard. %>
|
|
131
|
+
<% if @total_milestones %>
|
|
130
132
|
<div class="admin-card">
|
|
131
133
|
<div class="admin-card-header">
|
|
132
|
-
<h3 class="admin-card-title">
|
|
134
|
+
<h3 class="admin-card-title">Milestones</h3>
|
|
135
|
+
<span class="admin-card-action"><%= pluralize(@total_milestones, "configured") %></span>
|
|
133
136
|
</div>
|
|
134
137
|
<div class="admin-card-body">
|
|
135
|
-
|
|
138
|
+
<div class="admin-stats-grid admin-stats-compact">
|
|
139
|
+
<div class="admin-stat-card">
|
|
140
|
+
<div class="admin-stat-content">
|
|
141
|
+
<div class="admin-stat-label">Awarded</div>
|
|
142
|
+
<div class="admin-stat-value"><%= number_with_delimiter(@milestones_awarded) %></div>
|
|
143
|
+
<div class="admin-stat-change">to <%= pluralize(@users_with_milestones, "user") %></div>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
<div class="admin-stat-card">
|
|
147
|
+
<div class="admin-stat-content">
|
|
148
|
+
<div class="admin-stat-label">Points</div>
|
|
149
|
+
<div class="admin-stat-value"><%= number_with_delimiter(@milestone_points_awarded) %></div>
|
|
150
|
+
<div class="admin-stat-change">in selected period</div>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
<% if @top_milestones&.any? %>
|
|
136
156
|
<div class="admin-list">
|
|
137
157
|
<% @top_milestones.each do |milestone| %>
|
|
138
158
|
<div class="admin-list-item">
|
|
139
159
|
<div class="admin-list-content">
|
|
140
|
-
<div class="admin-list-title"
|
|
141
|
-
|
|
160
|
+
<div class="admin-list-title">
|
|
161
|
+
<% if milestone[:icon].present? %><span aria-hidden="true"><%= milestone[:icon] %></span> <% end %><%= milestone[:title] %>
|
|
162
|
+
</div>
|
|
163
|
+
<div class="admin-list-meta">
|
|
164
|
+
<%= milestone[:name] %><% unless milestone[:configured] %> — no longer configured<% end %>
|
|
165
|
+
</div>
|
|
142
166
|
</div>
|
|
143
167
|
<div class="admin-list-value">
|
|
144
168
|
<span class="admin-badge"><%= number_with_delimiter(milestone[:count]) %> achieved</span>
|
|
@@ -147,7 +171,7 @@
|
|
|
147
171
|
<% end %>
|
|
148
172
|
</div>
|
|
149
173
|
<% else %>
|
|
150
|
-
<p class="admin-empty-state">No
|
|
174
|
+
<p class="admin-empty-state">No milestones achieved in this period</p>
|
|
151
175
|
<% end %>
|
|
152
176
|
</div>
|
|
153
177
|
</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.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David Lewis
|
|
@@ -282,7 +282,7 @@ licenses:
|
|
|
282
282
|
metadata:
|
|
283
283
|
allowed_push_host: https://rubygems.org
|
|
284
284
|
homepage_uri: https://github.com/bunnahabhain/rails_onboarding
|
|
285
|
-
source_code_uri: https://github.com/bunnahabhain/rails_onboarding/tree/v0.8.
|
|
285
|
+
source_code_uri: https://github.com/bunnahabhain/rails_onboarding/tree/v0.8.2
|
|
286
286
|
changelog_uri: https://github.com/bunnahabhain/rails_onboarding/blob/master/docs/CHANGELOG.md
|
|
287
287
|
rdoc_options: []
|
|
288
288
|
require_paths:
|