Skip to content

Commit 601ff41

Browse files
author
Job Hammer
committed
v0.2.0.8
1 parent 3057a4e commit 601ff41

33 files changed

Lines changed: 1115 additions & 271 deletions

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,26 @@
11
## [Unreleased]
22

3+
## [0.2.0.8] - 2026-04-09
4+
5+
- Analytics performance: migration adds `ahoy_events (name, time)`, `ahoy_events (visit_id, time)`, `ahoy_visits (started_at)`, `ahoy_visits (visitor_token)` indexes
6+
- Analytics performance: `compute_new_visitor_percentage` uses subquery instead of plucking all historical visitor tokens into Ruby memory
7+
- Analytics performance: `exit_pages_data` uses a single DB join-subquery instead of loading all page view rows into Ruby
8+
9+
## [0.2.0.7] - 2026-04-09
10+
11+
- Analytics: add `EVENT_PAGE_VIEW` / `EVENT_CONVERSION` constants for consistent ahoy.track usage
12+
- Analytics: conversion tracking — `Report` queries `conversion` events and surfaces totals + goal breakdown in dashboard
13+
- Analytics: exit pages — last `page_view` per visit in selected range, displayed as new dashboard section
14+
- Analytics: period-over-period comparison — KPI deltas (↑/↓ %) shown on page views, unique visitors, and sessions stat cards
15+
- Analytics: expanded bot-filtering documentation in install template (`analytics_visit_scope` examples)
16+
- Analytics: register `analytics_max_exit_pages`, `analytics_max_conversions`, `analytics_max_referrers`, `analytics_max_landing_pages`, `analytics_max_utm_sources` in SettingsRegistry
17+
- PageTracking: document conversion tracking convention in concern comments
18+
- Locales: add analytics i18n keys for exit pages, conversions, and period comparison (en + nl)
19+
20+
## [0.2.0.6] - 2026-04-09
21+
22+
- Analytics improvements
23+
324
## [0.2.0.5] - 2026-04-08
425

526
- The host app no longer needs to scan de gem for tailwind

Gemfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
PATH
22
remote: .
33
specs:
4-
ruby_cms (0.2.0.5)
4+
ruby_cms (0.2.0.8)
55
actiontext (>= 7.1)
66
ahoy_matey (>= 5.0)
77
rails (>= 7.1)
@@ -518,7 +518,7 @@ CHECKSUMS
518518
rubocop-rspec (3.9.0) sha256=8fa70a3619408237d789aeecfb9beef40576acc855173e60939d63332fdb55e2
519519
rubocop-rspec_rails (2.32.0) sha256=4a0d641c72f6ebb957534f539d9d0a62c47abd8ce0d0aeee1ef4701e892a9100
520520
ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33
521-
ruby_cms (0.2.0.5)
521+
ruby_cms (0.2.0.8)
522522
rugged (1.9.0) sha256=7faaa912c5888d6e348d20fa31209b6409f1574346b1b80e309dbc7e8d63efac
523523
safely_block (0.5.0) sha256=782c342bc400a79c1233c40cf8c379da6116c6cfd1d7c1f17f0ef2ef11090103
524524
sawyer (0.9.3) sha256=0d0f19298408047037638639fe62f4794483fb04320269169bd41af2bdcf5e41

app/components/ruby_cms/admin/bulk_action_table/bulk_action_table.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,15 @@ def table_data_attributes
128128
data: {
129129
controller: @controller_name,
130130
"#{@controller_name}-csrf-token-value": csrf_token,
131-
"#{@controller_name}-item-name-value": @item_name
131+
"#{@controller_name}-item-name-value": @item_name,
132+
"#{@controller_name}-processing-label-value": t("ruby_cms.admin.bulk_action_table.processing", default: "Processing..."),
133+
"#{@controller_name}-confirm-label-value": t("ruby_cms.admin.bulk_action_table.confirm", default: "Confirm"),
134+
"#{@controller_name}-select-at-least-one-message-value": t("ruby_cms.admin.bulk_action_table.select_at_least_one", default: "Please select at least one item."),
135+
"#{@controller_name}-item-id-not-found-message-value": t("ruby_cms.admin.bulk_action_table.item_id_not_found", default: "Item ID not found for deletion."),
136+
"#{@controller_name}-delete-path-not-found-message-value": t("ruby_cms.admin.bulk_action_table.delete_path_not_found", default: "Delete path not found."),
137+
"#{@controller_name}-action-url-not-configured-message-value": t("ruby_cms.admin.bulk_action_table.action_url_not_configured", default: "Action URL not configured. Please configure an action URL for this page."),
138+
"#{@controller_name}-default-confirm-message-value": t("ruby_cms.admin.bulk_action_table.default_confirm", default: "Are you sure you want to proceed?"),
139+
"#{@controller_name}-generic-action-error-message-value": t("ruby_cms.admin.bulk_action_table.generic_action_error", default: "An error occurred while performing %{action}.")
132140
}
133141
}
134142

app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_actions.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ def view_template
4545
def render_edit_button
4646
link_options = {
4747
href: @edit_path,
48-
class: "inline-flex size-8 items-center justify-center rounded-md text-muted-foreground " \
49-
"hover:bg-muted hover:text-foreground transition-colors"
48+
class: "inline-flex size-8 items-center justify-center rounded-md text-blue-600 " \
49+
"hover:bg-blue-50 hover:text-blue-700 transition-colors"
5050
}
5151
link_options[:data] = { turbo_frame: @turbo_frame } if @turbo_frame
5252

@@ -71,7 +71,7 @@ def render_delete_button
7171
item_id = @item_id || extract_item_id_from_path
7272
button(
7373
type: "button",
74-
class: "inline-flex size-8 items-center justify-center rounded-md text-muted-foreground " \
74+
class: "inline-flex size-8 items-center justify-center rounded-md text-destructive " \
7575
"hover:bg-destructive/10 hover:text-destructive transition-colors",
7676
data: {
7777
action: "click->#{@controller_name}#showIndividualDeleteDialog",

app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_delete_modal.rb

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,10 @@ def render_close_button
7979
data: {
8080
action: "click->#{@controller_name}#closeDialog"
8181
},
82-
aria_label: "Close"
82+
aria_label: t("ruby_cms.admin.bulk_action_table.close", default: "Close")
8383
) do
8484
render_close_icon
85-
span(class: "sr-only") { "Close" }
85+
span(class: "sr-only") { t("ruby_cms.admin.bulk_action_table.close", default: "Close") }
8686
end
8787
end
8888

@@ -123,7 +123,7 @@ def render_header
123123
data: {
124124
"#{@controller_name}-target": "dialogTitle"
125125
}
126-
) { "Delete Selected Items" }
126+
) { t("ruby_cms.admin.bulk_action_table.delete_selected_items", default: "Delete Selected Items") }
127127
end
128128

129129
def render_message
@@ -133,8 +133,8 @@ def render_message
133133
"#{@controller_name}-target": "dialogMessage"
134134
}
135135
) do
136-
p { "Are you sure you want to delete the selected items?" }
137-
p { "This action cannot be undone." }
136+
p { t("ruby_cms.admin.bulk_action_table.are_you_sure_you_want_to_delete_the_selected_items", default: "Are you sure you want to delete the selected items?") }
137+
p { t("ruby_cms.admin.bulk_action_table.this_action_cannot_be_undone", default: "This action cannot be undone.") }
138138
end
139139
end
140140

@@ -154,19 +154,19 @@ def render_cancel_button
154154
data: {
155155
action: "click->#{@controller_name}#closeDialog"
156156
}
157-
) { "Cancel" }
157+
) { t("ruby_cms.admin.bulk_action_table.cancel", default: "Cancel") }
158158
end
159159

160160
def render_confirm_button
161161
button(
162162
type: "button",
163163
class: "inline-flex h-9 items-center justify-center rounded-md bg-destructive px-4 " \
164-
"text-sm font-medium text-destructive-foreground shadow-sm hover:bg-destructive/90 transition-colors",
164+
"text-sm font-medium text-white font-bold shadow-sm hover:bg-destructive/90 transition-colors",
165165
data: {
166166
"#{@controller_name}-target": "dialogConfirmButton",
167167
action: "click->#{@controller_name}#confirmAction"
168168
}
169-
) { "Delete Selected" }
169+
) { t("ruby_cms.admin.bulk_action_table.delete_selected", default: "Delete Selected") }
170170
end
171171
end
172172
end

app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_header_bar.rb

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ def initialize(
3232

3333
def view_template
3434
div(class: "px-5 py-3 border-b border-border/60 bg-white") do
35-
div(class: "flex flex-wrap items-center justify-between gap-4") do
35+
div(class: "flex items-center justify-between gap-3") do
3636
render_title_group if @title.present?
37-
div(class: "flex items-center gap-2 flex-wrap") do
37+
div(class: "ml-auto flex items-center gap-2 overflow-x-auto whitespace-nowrap") do
3838
render_header_filter if @header_filter.present?
3939
render_action_icons
4040
render_search_form
@@ -75,9 +75,15 @@ def render_search_form
7575
form_options = {
7676
url: @search_url,
7777
method: :get,
78-
class: "w-full sm:w-auto"
78+
class: "w-full sm:w-auto",
79+
data: { "#{default_controller_name}-target": "searchForm" }
7980
}
8081
form_options[:data] = { turbo_frame: @turbo_frame } if @turbo_frame.present?
82+
if @turbo_frame.present?
83+
form_options[:data] = form_options[:data].merge(
84+
"#{default_controller_name}-target": "searchForm"
85+
)
86+
end
8187

8288
form_with(**form_options) do
8389
div(class: "relative flex items-center") do
@@ -106,10 +112,10 @@ def render_search_input
106112
type: "search",
107113
name: @search_param,
108114
placeholder: "Search",
109-
class: "h-9 w-full sm:w-72 rounded-md border border-border bg-white pl-9 " \
115+
class: "h-9 w-64 sm:w-72 rounded-md border border-border bg-white pl-9 " \
110116
"pr-3 text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-primary/20",
111117
value: search_value,
112-
data: { action: "input->turbo-frame#submit" }
118+
data: { action: "input->#{default_controller_name}#autoSearch" }
113119
)
114120
end
115121

@@ -153,6 +159,10 @@ def render_icon_svg(icon_path)
153159
d: icon_path)
154160
end
155161
end
162+
163+
def default_controller_name
164+
"ruby-cms--bulk-action-table"
165+
end
156166
end
157167
end
158168
end

app/components/ruby_cms/admin/bulk_action_table/bulk_actions.rb

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ def render_selection_info
5151
def render_selection_left
5252
div(class: "flex items-center gap-2") do
5353
render_selected_count_badge
54-
span(class: "text-sm font-medium text-foreground") { "selected" }
54+
span(class: "text-sm font-medium text-foreground") do
55+
t("ruby_cms.admin.bulk_action_table.selected", default: "selected")
56+
end
5557
end
5658
end
5759

@@ -74,15 +76,15 @@ def render_selection_buttons
7476
"#{@controller_name}-target": "selectAllButton",
7577
action: "click->#{@controller_name}#selectAll"
7678
}
77-
) { "Select all" }
79+
) { t("ruby_cms.admin.bulk_action_table.select_all", default: "Select all") }
7880

7981
button(
8082
type: "button",
8183
class: "text-xs font-medium text-muted-foreground hover:text-foreground transition-colors",
8284
data: {
8385
action: "click->#{@controller_name}#clearSelection"
8486
}
85-
) { "Clear" }
87+
) { t("ruby_cms.admin.bulk_action_table.clear", default: "Clear") }
8688
end
8789

8890
def render_action_buttons
@@ -98,19 +100,24 @@ def render_action_buttons
98100
def render_custom_action_button(config)
99101
label = config[:label] || config[:text] || config[:name]&.humanize || "Button"
100102
action_name = config[:name] || config[:action_name]
103+
icon_path = resolve_icon_path(config, action_name)
101104

102105
button(
103106
type: "button",
104107
class: build_button_class(config),
105108
data: build_button_data_attrs(config, label, action_name)
106-
) { label }
109+
) do
110+
render_button_icon(icon_path)
111+
span { label }
112+
end
107113
end
108114

109115
def build_button_class(config)
110-
base = "inline-flex items-center justify-center rounded-md border border-border " \
111-
"bg-white px-3 py-1.5 text-sm font-medium text-foreground shadow-sm " \
116+
color_class = resolve_button_color_class(config)
117+
base = "inline-flex items-center justify-center gap-1.5 rounded-md border border-border " \
118+
"bg-white px-3 py-1.5 text-sm font-medium shadow-sm " \
112119
"hover:bg-muted transition-colors"
113-
config[:class].present? ? "#{base} #{config[:class]}" : base
120+
[base, color_class, config[:class]].compact.join(" ")
114121
end
115122

116123
def build_button_data_attrs(config, label, action_name)
@@ -132,18 +139,56 @@ def build_button_data_attrs(config, label, action_name)
132139
def render_delete_button
133140
button(
134141
type: "button",
135-
class: "inline-flex items-center justify-center rounded-md border border-destructive/30 " \
142+
class: "inline-flex items-center justify-center gap-1.5 rounded-md border border-destructive/30 " \
136143
"bg-white px-3 py-1.5 text-sm font-medium text-destructive shadow-sm " \
137144
"hover:bg-destructive/10 transition-colors",
138145
data: {
139146
action: "click->#{@controller_name}#showActionDialog",
140147
action_name: "delete",
141-
action_label: "Delete Selected",
142-
action_confirm: "Are you sure you want to delete the selected \
143-
items? This action cannot be undone.",
148+
action_label: t("ruby_cms.admin.bulk_action_table.delete_selected", default: "Delete Selected"),
149+
action_confirm: t("ruby_cms.admin.bulk_action_table.delete_selected_confirm", default: "Are you sure you want to delete the selected items? This action cannot be undone."),
144150
action_url: @bulk_actions_url&.to_s
145151
}
146-
) { "Delete Selected" }
152+
) do
153+
render_button_icon("M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16")
154+
span { t("ruby_cms.admin.bulk_action_table.delete_selected", default: "Delete Selected") }
155+
end
156+
end
157+
158+
def render_button_icon(path_d)
159+
svg(class: "h-3.5 w-3.5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24") do |s|
160+
s.path(stroke_linecap: "round", stroke_linejoin: "round", stroke_width: "2", d: path_d)
161+
end
162+
end
163+
164+
def resolve_button_color_class(config)
165+
color = (config[:color] || config[:tone] || config[:variant]).to_s
166+
return "text-foreground" if color.blank?
167+
168+
{
169+
"blue" => "text-blue-700 border-blue-200 hover:bg-blue-50",
170+
"green" => "text-emerald-700 border-emerald-200 hover:bg-emerald-50",
171+
"emerald" => "text-emerald-700 border-emerald-200 hover:bg-emerald-50",
172+
"orange" => "text-amber-700 border-amber-200 hover:bg-amber-50",
173+
"amber" => "text-amber-700 border-amber-200 hover:bg-amber-50",
174+
"red" => "text-destructive border-destructive/30 hover:bg-destructive/10",
175+
"purple" => "text-violet-700 border-violet-200 hover:bg-violet-50"
176+
}.fetch(color, "text-foreground")
177+
end
178+
179+
def resolve_icon_path(config, action_name)
180+
return config[:icon] if config[:icon].present?
181+
182+
case action_name.to_s
183+
when "publish"
184+
"M5 13l4 4L19 7"
185+
when "unpublish"
186+
"M6 18L18 6M6 6l12 12"
187+
when "archive"
188+
"M20 7l-1 12H5L4 7m16 0H4m3-3h10l1 3H6l1-3z"
189+
else
190+
"M12 4v16m8-8H4"
191+
end
147192
end
148193
end
149194
end

app/controllers/concerns/ruby_cms/page_tracking.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,21 @@ module RubyCms
1212
# Sets @page_name to controller_name by default. Override in actions:
1313
# @page_name = "custom_page_name"
1414
#
15+
# Event naming conventions (keep property keys consistent across the app):
16+
# Page views (tracked automatically):
17+
# ahoy.track RubyCms::Analytics::Report::EVENT_PAGE_VIEW,
18+
# page_name: "home", request_path: request.path
19+
#
20+
# Conversions (tracked by host app controllers/forms):
21+
# ahoy.track RubyCms::Analytics::Report::EVENT_CONVERSION,
22+
# goal: "contact_form", path: request.path
23+
# ahoy.track RubyCms::Analytics::Report::EVENT_CONVERSION,
24+
# goal: "newsletter_signup", path: request.path
25+
#
26+
# Convention for property keys:
27+
# page_view: page_name (String), request_path (String)
28+
# conversion: goal (String, required), path (String, optional)
29+
#
1530
module PageTracking
1631
extend ActiveSupport::Concern
1732

app/controllers/ruby_cms/admin/analytics_controller.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ def index
1717
)
1818
@stats = report.dashboard_stats
1919
@stats.each {|key, value| instance_variable_set(:"@#{key}", value) }
20+
@active_users = active_users_count
2021
end
2122

2223
def page_details
@@ -73,6 +74,17 @@ def validate_date_range
7374
alert: "Invalid date range. Maximum range is #{max_days} days."
7475
end
7576

77+
def active_users_count
78+
Ahoy::Event
79+
.where(name: RubyCms::Analytics::Report::EVENT_PAGE_VIEW)
80+
.where(time: 5.minutes.ago..)
81+
.joins(:visit)
82+
.distinct
83+
.count(:visitor_token)
84+
rescue StandardError
85+
nil
86+
end
87+
7688
def sanitize_period(value)
7789
%w[day week month year].include?(value.to_s) ? value.to_s : nil
7890
end

0 commit comments

Comments
 (0)