ruby_cms 0.2.0.8 → 0.2.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table.rb +1 -1
- data/app/views/ruby_cms/admin/analytics/index.html.erb +17 -2
- data/app/views/ruby_cms/admin/analytics/partials/_conversions.html.erb +49 -0
- data/app/views/ruby_cms/admin/analytics/partials/_exit_pages.html.erb +39 -0
- data/db/migrate/20260409000001_add_analytics_performance_indexes.rb +29 -0
- data/lib/ruby_cms/version.rb +1 -1
- metadata +18 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 73750814275495274cd4153a97c081ff9b93e63b1afb62714ed9eec06ef9aa76
|
|
4
|
+
data.tar.gz: dfd98b9a461601b9feadbcf55817d23dc62eb8bfacfa3992548d7d623bb91b69
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e8245f92e21637fd81d019629b800774261bf7aa83a8810c54836a5089164be05b039822201413bb8e5c80e90036a4a5b969344069b171d81882b89a1563e0cf
|
|
7
|
+
data.tar.gz: cf0669c97fc3f289a111e76d38a93e8eec8f22839869cfe0bc3256bdf7648eee45e663458499e8d271d5cbe3b3057288862114644a1542c50858d75c242f93d8
|
|
@@ -136,7 +136,7 @@ module RubyCms
|
|
|
136
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
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
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
|
|
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>s.")
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
142
|
|
|
@@ -202,8 +202,23 @@
|
|
|
202
202
|
</div>
|
|
203
203
|
|
|
204
204
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
|
205
|
-
|
|
206
|
-
|
|
205
|
+
<% if lookup_context.exists?("ruby_cms/admin/analytics/partials/exit_pages", [], true) %>
|
|
206
|
+
<%= render "ruby_cms/admin/analytics/partials/exit_pages" %>
|
|
207
|
+
<% else %>
|
|
208
|
+
<div class="rounded-xl border border-border/60 bg-white p-4 shadow-sm">
|
|
209
|
+
<p class="text-sm font-semibold text-foreground"><%= t("ruby_cms.admin.analytics.exit_pages", default: "Exit Pages") %></p>
|
|
210
|
+
<p class="mt-1 text-xs text-muted-foreground"><%= t("ruby_cms.admin.analytics.not_available", default: "Not available in this version.") %></p>
|
|
211
|
+
</div>
|
|
212
|
+
<% end %>
|
|
213
|
+
|
|
214
|
+
<% if lookup_context.exists?("ruby_cms/admin/analytics/partials/conversions", [], true) %>
|
|
215
|
+
<%= render "ruby_cms/admin/analytics/partials/conversions" %>
|
|
216
|
+
<% else %>
|
|
217
|
+
<div class="rounded-xl border border-border/60 bg-white p-4 shadow-sm">
|
|
218
|
+
<p class="text-sm font-semibold text-foreground"><%= t("ruby_cms.admin.analytics.conversions", default: "Conversions") %></p>
|
|
219
|
+
<p class="mt-1 text-xs text-muted-foreground"><%= t("ruby_cms.admin.analytics.not_available", default: "Not available in this version.") %></p>
|
|
220
|
+
</div>
|
|
221
|
+
<% end %>
|
|
207
222
|
</div>
|
|
208
223
|
|
|
209
224
|
<%# ── Suspicious activity ── %>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<div class="rounded-xl border border-border/60 bg-white shadow-sm overflow-hidden">
|
|
2
|
+
<div class="flex items-center justify-between gap-3 px-4 py-3 border-b border-border/40">
|
|
3
|
+
<div class="flex items-center gap-3">
|
|
4
|
+
<div class="rounded-md bg-emerald-50 p-1.5 flex-shrink-0">
|
|
5
|
+
<svg class="w-4 h-4 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
|
6
|
+
</div>
|
|
7
|
+
<div>
|
|
8
|
+
<p class="text-sm font-semibold text-foreground"><%= t("ruby_cms.admin.analytics.conversions", default: "Conversions") %></p>
|
|
9
|
+
<p class="text-xs text-muted-foreground"><%= t("ruby_cms.admin.analytics.conversions_subtitle", default: "Goals tracked via ahoy.track in your app") %></p>
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
12
|
+
<% if @conversions&.dig(:total).to_i > 0 %>
|
|
13
|
+
<span class="inline-flex items-center rounded-full bg-emerald-50 px-2.5 py-1 text-xs font-semibold text-emerald-700 border border-emerald-200 flex-shrink-0">
|
|
14
|
+
<%= number_with_delimiter(@conversions[:total]) %> <%= t("ruby_cms.admin.analytics.conversions_total", default: "total") %>
|
|
15
|
+
</span>
|
|
16
|
+
<% end %>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<% goals = @conversions&.dig(:by_goal) %>
|
|
20
|
+
<% if goals.present? && goals.any? %>
|
|
21
|
+
<% total = goals.values.sum.to_f %>
|
|
22
|
+
<div class="divide-y divide-border/30">
|
|
23
|
+
<% goals.each do |goal, count| %>
|
|
24
|
+
<% pct = total.positive? ? (count / total * 100).round(1) : 0 %>
|
|
25
|
+
<div class="relative flex items-center justify-between gap-3 px-4 py-2.5">
|
|
26
|
+
<div class="absolute inset-y-0 left-0 bg-emerald-500/5" style="width: <%= pct %>%"></div>
|
|
27
|
+
<div class="relative z-10 flex items-center gap-2 min-w-0">
|
|
28
|
+
<div class="w-1.5 h-1.5 rounded-full bg-emerald-400 flex-shrink-0"></div>
|
|
29
|
+
<p class="text-sm text-foreground truncate"><%= goal.to_s.humanize %></p>
|
|
30
|
+
</div>
|
|
31
|
+
<div class="relative z-10 flex items-center gap-2 flex-shrink-0">
|
|
32
|
+
<span class="text-xs text-muted-foreground tabular-nums"><%= pct %>%</span>
|
|
33
|
+
<span class="text-sm font-semibold tabular-nums text-foreground"><%= number_with_delimiter(count) %></span>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
<% end %>
|
|
37
|
+
</div>
|
|
38
|
+
<% else %>
|
|
39
|
+
<div class="flex flex-col items-center justify-center px-4 py-10 gap-3">
|
|
40
|
+
<svg class="w-8 h-8 text-muted-foreground/30" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
|
41
|
+
<div class="text-center">
|
|
42
|
+
<p class="text-sm font-medium text-foreground"><%= t("ruby_cms.admin.analytics.no_conversions", default: "No conversions recorded.") %></p>
|
|
43
|
+
<p class="mt-2 text-xs font-mono bg-muted/60 border border-border/40 rounded-md px-3 py-1.5 inline-block text-muted-foreground">
|
|
44
|
+
ahoy.track "<%= RubyCms::Analytics::Report::EVENT_CONVERSION %>", goal: "contact_form"
|
|
45
|
+
</p>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
<% end %>
|
|
49
|
+
</div>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<div class="rounded-xl border border-border/60 bg-white shadow-sm overflow-hidden">
|
|
2
|
+
<div class="flex items-center gap-3 px-4 py-3 border-b border-border/40">
|
|
3
|
+
<div class="rounded-md bg-amber-50 p-1.5 flex-shrink-0">
|
|
4
|
+
<svg class="w-4 h-4 text-amber-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/></svg>
|
|
5
|
+
</div>
|
|
6
|
+
<div>
|
|
7
|
+
<p class="text-sm font-semibold text-foreground"><%= t("ruby_cms.admin.analytics.exit_pages", default: "Exit Pages") %></p>
|
|
8
|
+
<p class="text-xs text-muted-foreground"><%= t("ruby_cms.admin.analytics.exit_pages_subtitle", default: "Last page viewed per session") %></p>
|
|
9
|
+
</div>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<% if @exit_pages.present? && @exit_pages.any? %>
|
|
13
|
+
<% total = @exit_pages.values.sum.to_f %>
|
|
14
|
+
<div class="divide-y divide-border/30">
|
|
15
|
+
<% @exit_pages.first(8).each_with_index do |(page_name, count), idx| %>
|
|
16
|
+
<% pct = total.positive? ? (count / total * 100).round(1) : 0 %>
|
|
17
|
+
<%= link_to page_details_ruby_cms_admin_analytics_path(page_name:, start_date: @start_date, end_date: @end_date, period: @period),
|
|
18
|
+
class: "group relative flex items-center justify-between gap-3 px-4 py-2.5 hover:bg-muted/40 transition-colors" do %>
|
|
19
|
+
<div class="absolute inset-y-0 left-0 bg-amber-500/5 transition-all" style="width: <%= pct %>%"></div>
|
|
20
|
+
<div class="relative z-10 flex items-center gap-3 min-w-0">
|
|
21
|
+
<span class="text-xs font-medium text-muted-foreground tabular-nums w-5 text-right flex-shrink-0"><%= idx + 1 %></span>
|
|
22
|
+
<p class="text-sm text-foreground truncate group-hover:text-primary transition-colors"><%= page_name.to_s.humanize %></p>
|
|
23
|
+
</div>
|
|
24
|
+
<div class="relative z-10 flex items-center gap-2.5 flex-shrink-0">
|
|
25
|
+
<span class="text-xs text-muted-foreground tabular-nums"><%= pct %>%</span>
|
|
26
|
+
<span class="text-sm font-semibold tabular-nums text-foreground"><%= number_with_delimiter(count) %></span>
|
|
27
|
+
<svg class="w-3.5 h-3.5 text-muted-foreground/40 group-hover:text-primary/60 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
|
|
28
|
+
</div>
|
|
29
|
+
<% end %>
|
|
30
|
+
<% end %>
|
|
31
|
+
</div>
|
|
32
|
+
<% else %>
|
|
33
|
+
<div class="flex flex-col items-center justify-center px-4 py-10 gap-2">
|
|
34
|
+
<svg class="w-8 h-8 text-muted-foreground/30" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/></svg>
|
|
35
|
+
<p class="text-sm font-medium text-foreground"><%= t("ruby_cms.admin.analytics.no_exit_pages", default: "No exit page data available.") %></p>
|
|
36
|
+
<p class="text-xs text-muted-foreground">Track some traffic to see which pages visitors leave from</p>
|
|
37
|
+
</div>
|
|
38
|
+
<% end %>
|
|
39
|
+
</div>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Performance indexes for RubyCMS analytics queries.
|
|
4
|
+
#
|
|
5
|
+
# Without these, every analytics page load does a full table scan on
|
|
6
|
+
# ahoy_events and ahoy_visits for the date range.
|
|
7
|
+
#
|
|
8
|
+
# Impact per index:
|
|
9
|
+
# ahoy_events (name, time) — primary filter for page_view_events + conversion_events
|
|
10
|
+
# ahoy_events (visit_id, time) — exit pages subquery: MAX(time) per visit
|
|
11
|
+
# ahoy_visits (started_at) — primary filter for visits scope
|
|
12
|
+
# ahoy_visits (visitor_token) — new-visitor NOT IN subquery
|
|
13
|
+
class AddAnalyticsPerformanceIndexes < ActiveRecord::Migration[7.1]
|
|
14
|
+
def change
|
|
15
|
+
return unless table_exists?(:ahoy_events) && table_exists?(:ahoy_visits)
|
|
16
|
+
|
|
17
|
+
# Most critical: all page_view_events / conversion_events queries filter on (name, time)
|
|
18
|
+
add_index :ahoy_events, %i[name time], if_not_exists: true
|
|
19
|
+
|
|
20
|
+
# Exit pages subquery groups by visit_id and picks MAX(time)
|
|
21
|
+
add_index :ahoy_events, %i[visit_id time], if_not_exists: true
|
|
22
|
+
|
|
23
|
+
# All visits queries filter on started_at range
|
|
24
|
+
add_index :ahoy_visits, :started_at, if_not_exists: true
|
|
25
|
+
|
|
26
|
+
# New-visitor percentage: NOT IN (SELECT visitor_token WHERE started_at < ?)
|
|
27
|
+
add_index :ahoy_visits, :visitor_token, if_not_exists: true
|
|
28
|
+
end
|
|
29
|
+
end
|
data/lib/ruby_cms/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruby_cms
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.0.
|
|
4
|
+
version: 0.2.0.9
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Codebyjob
|
|
@@ -51,6 +51,20 @@ dependencies:
|
|
|
51
51
|
- - ">="
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
53
|
version: '7.1'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: safely_block
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "<"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '1.0'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "<"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '1.0'
|
|
54
68
|
description: |
|
|
55
69
|
RubyCMS is a Rails engine that provides a CMS solution for managing content
|
|
56
70
|
while developers focus on SaaS product features. It includes:
|
|
@@ -152,7 +166,9 @@ files:
|
|
|
152
166
|
- app/views/ruby_cms/admin/analytics/page_details.html.erb
|
|
153
167
|
- app/views/ruby_cms/admin/analytics/partials/_back_button.html.erb
|
|
154
168
|
- app/views/ruby_cms/admin/analytics/partials/_browser_device.html.erb
|
|
169
|
+
- app/views/ruby_cms/admin/analytics/partials/_conversions.html.erb
|
|
155
170
|
- app/views/ruby_cms/admin/analytics/partials/_daily_activity_chart.html.erb
|
|
171
|
+
- app/views/ruby_cms/admin/analytics/partials/_exit_pages.html.erb
|
|
156
172
|
- app/views/ruby_cms/admin/analytics/partials/_hourly_activity_chart.html.erb
|
|
157
173
|
- app/views/ruby_cms/admin/analytics/partials/_landing_pages.html.erb
|
|
158
174
|
- app/views/ruby_cms/admin/analytics/partials/_os_stats.html.erb
|
|
@@ -208,6 +224,7 @@ files:
|
|
|
208
224
|
- db/migrate/20260211000001_add_ruby_cms_analytics_fields_to_ahoy_events.rb
|
|
209
225
|
- db/migrate/20260212000001_use_unprefixed_cms_tables.rb
|
|
210
226
|
- db/migrate/20260328000001_create_content_block_versions.rb
|
|
227
|
+
- db/migrate/20260409000001_add_analytics_performance_indexes.rb
|
|
211
228
|
- docs/assets/ruby_cms_logo.png
|
|
212
229
|
- exe/ruby_cms
|
|
213
230
|
- lib/generators/ruby_cms/admin_page_generator.rb
|