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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b2871a67fff389ef55e7c894100dcfbe9b7eeb4ccbec079986d64f6c768c9f87
4
- data.tar.gz: 7658b768eed4dbc05b88312e3b25d85ccee84180441d55f91023af3c797be32c
3
+ metadata.gz: 73750814275495274cd4153a97c081ff9b93e63b1afb62714ed9eec06ef9aa76
4
+ data.tar.gz: dfd98b9a461601b9feadbcf55817d23dc62eb8bfacfa3992548d7d623bb91b69
5
5
  SHA512:
6
- metadata.gz: 25b55ee8d4aa7f9a9ef0b3d861cc1238c1c97b1550703e06336c11df8ac7aa0552bcddf93c90af3dca456ee9149647501c9a37ea71c858181d70f992dd19b33d
7
- data.tar.gz: 2d9aa367ab09809bde3781a654b88fa3d7b9c7eed9bf37899977c18ab57d69e7e5695f60db91c0358365a978c228129525055a1653828007178051ab22404760
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 %{action}.")
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
- <%= render "ruby_cms/admin/analytics/partials/exit_pages" %>
206
- <%= render "ruby_cms/admin/analytics/partials/conversions" %>
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RubyCms
4
- VERSION = "0.2.0.8"
4
+ VERSION = "0.2.0.9"
5
5
  end
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.8
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