rails_event_viewer 0.3.0 → 0.4.0

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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +42 -0
  3. data/README.md +8 -0
  4. data/app/assets/javascripts/rails_event_viewer/application.js +2 -1
  5. data/app/assets/stylesheets/rails_event_viewer/application.css +1 -1
  6. data/app/controllers/rails_event_viewer/application_controller.rb +7 -16
  7. data/app/controllers/rails_event_viewer/events_controller.rb +16 -29
  8. data/app/controllers/rails_event_viewer/groups_controller.rb +0 -9
  9. data/app/helpers/rails_event_viewer/application_helper.rb +23 -77
  10. data/app/models/rails_event_viewer/entry.rb +0 -26
  11. data/app/models/rails_event_viewer/page.rb +9 -0
  12. data/app/views/layouts/rails_event_viewer/application.html.erb +11 -4
  13. data/app/views/rails_event_viewer/analytics/overview.html.erb +13 -9
  14. data/app/views/rails_event_viewer/dashboard/index.html.erb +16 -15
  15. data/app/views/rails_event_viewer/event_types/index.html.erb +7 -7
  16. data/app/views/rails_event_viewer/event_types/show.html.erb +18 -16
  17. data/app/views/rails_event_viewer/events/_event_row.html.erb +9 -9
  18. data/app/views/rails_event_viewer/events/_filters_sidebar.html.erb +18 -15
  19. data/app/views/rails_event_viewer/events/index.html.erb +10 -10
  20. data/app/views/rails_event_viewer/events/show.html.erb +28 -28
  21. data/app/views/rails_event_viewer/groups/index.html.erb +8 -8
  22. data/app/views/rails_event_viewer/groups/show.html.erb +12 -16
  23. data/app/views/rails_event_viewer/shared/_navigation.html.erb +13 -17
  24. data/app/views/rails_event_viewer/shared/_pagination.html.erb +10 -10
  25. data/app/views/rails_event_viewer/shared/_stat_card.html.erb +5 -5
  26. data/config/routes.rb +1 -5
  27. data/db/migrate/20261002121337_create_rails_event_viewer_entries.rb +0 -1
  28. data/lib/generators/rails_event_viewer/install/templates/create_rails_event_viewer_entries.rb.erb +0 -1
  29. data/lib/rails_event_viewer/adapter.rb +0 -8
  30. data/lib/rails_event_viewer/adapters/active_record.rb +12 -44
  31. data/lib/rails_event_viewer/adapters/concerns/in_memory_filtering.rb +7 -7
  32. data/lib/rails_event_viewer/adapters/memory.rb +0 -4
  33. data/lib/rails_event_viewer/adapters/null.rb +0 -8
  34. data/lib/rails_event_viewer/adapters/redis.rb +6 -20
  35. data/lib/rails_event_viewer/events_relation.rb +1 -1
  36. data/lib/rails_event_viewer/flusher.rb +5 -5
  37. data/lib/rails_event_viewer/logging.rb +1 -11
  38. data/lib/rails_event_viewer/version.rb +1 -1
  39. data/lib/rails_event_viewer.rb +5 -5
  40. data/lib/tasks/rails_event_viewer_tasks.rake +1 -1
  41. metadata +7 -2
  42. data/app/assets/stylesheets/rails_event_viewer/tailwind.css +0 -29
@@ -40,7 +40,7 @@
40
40
  <path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
41
41
  </svg>
42
42
  <div class="ml-3">
43
- <h3 class="text-sm font-medium text-yellow-800">No group keys configured</h3>
43
+ <h2 class="text-sm font-medium text-yellow-800">No group keys configured</h2>
44
44
  <p class="mt-1 text-sm text-yellow-700">
45
45
  Configure group keys in your initializer:
46
46
  <code class="bg-yellow-100 px-1 rounded">config.group_keys = [:request_id, :order_id]</code>
@@ -49,17 +49,17 @@
49
49
  </div>
50
50
  </div>
51
51
  <% elsif @groups.any? %>
52
- <div class="bg-white shadow rounded-lg overflow-hidden">
52
+ <div class="bg-white shadow rounded-lg overflow-x-auto">
53
53
  <table class="min-w-full divide-y divide-gray-200">
54
54
  <thead class="bg-gray-50">
55
55
  <tr>
56
- <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
56
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
57
57
  <%= @selected_key.to_s.titleize %>
58
58
  </th>
59
- <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Events</th>
60
- <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">First Event</th>
61
- <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Last Event</th>
62
- <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Duration</th>
59
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Events</th>
60
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">First Event</th>
61
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Last Event</th>
62
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Duration</th>
63
63
  </tr>
64
64
  </thead>
65
65
  <tbody class="bg-white divide-y divide-gray-200">
@@ -106,7 +106,7 @@
106
106
  <svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
107
107
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
108
108
  </svg>
109
- <h3 class="mt-2 text-sm font-medium text-gray-900">No groups found</h3>
109
+ <h2 class="mt-2 text-sm font-medium text-gray-900">No groups found</h2>
110
110
  <p class="mt-1 text-sm text-gray-500">
111
111
  No events with <code class="bg-gray-100 px-1 rounded"><%= @selected_key %></code> in their <%= @source %> data.
112
112
  </p>
@@ -4,7 +4,7 @@
4
4
  <!-- Header -->
5
5
  <div class="flex items-center justify-between">
6
6
  <div class="flex items-center space-x-4">
7
- <%= link_to rails_event_viewer.groups_path(key: @key, source: @source), class: "text-gray-400 hover:text-gray-600", aria: { label: "Back to groups" } do %>
7
+ <%= link_to rails_event_viewer.groups_path(key: @key, source: @source), class: "text-gray-500 hover:text-gray-700", aria: { label: "Back to groups" } do %>
8
8
  <svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
9
9
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
10
10
  </svg>
@@ -19,15 +19,11 @@
19
19
  </div>
20
20
 
21
21
  <!-- Stats -->
22
- <div class="grid grid-cols-1 gap-5 sm:grid-cols-4">
22
+ <div class="grid grid-cols-1 gap-5 sm:grid-cols-3">
23
23
  <%= render "rails_event_viewer/shared/stat_card",
24
24
  title: "Total Events",
25
25
  value: number_with_delimiter(@total_count),
26
26
  icon: "database" %>
27
- <%= render "rails_event_viewer/shared/stat_card",
28
- title: "Event Types",
29
- value: @event_names.size,
30
- icon: "tag" %>
31
27
  <%= render "rails_event_viewer/shared/stat_card",
32
28
  title: "Duration",
33
29
  value: @duration ? format_duration(@duration) : "-",
@@ -41,7 +37,7 @@
41
37
  <!-- Timeline -->
42
38
  <div class="bg-white shadow rounded-lg overflow-hidden">
43
39
  <div class="px-6 py-4 border-b border-gray-200">
44
- <h3 class="text-lg font-medium text-gray-900">Event Timeline</h3>
40
+ <h2 class="text-lg font-medium text-gray-900">Event Timeline</h2>
45
41
  <% if @first_event_at && @last_event_at %>
46
42
  <p class="text-sm text-gray-500">
47
43
  <%= @first_event_at.strftime("%Y-%m-%d %H:%M:%S") %> to <%= @last_event_at.strftime("%Y-%m-%d %H:%M:%S") %>
@@ -55,13 +51,13 @@
55
51
 
56
52
  <div class="divide-y divide-gray-100">
57
53
  <% @events.each_with_index do |event, index| %>
58
- <a href="<%= rails_event_viewer.event_path(event_id(event)) %>" class="relative pl-16 pr-6 py-4 hover:bg-gray-50 block">
54
+ <a href="<%= rails_event_viewer.event_path(event[:id]) %>" class="relative pl-16 pr-6 py-4 hover:bg-gray-50 block">
59
55
  <div class="absolute left-10 top-6 w-4 h-4 rounded-full border-2 border-indigo-500 bg-white"></div>
60
56
 
61
- <% if @first_event_at && event_occurred_at(event) %>
62
- <% offset = event_occurred_at(event) - @first_event_at %>
57
+ <% if @first_event_at && event[:occurred_at] %>
58
+ <% offset = event[:occurred_at] - @first_event_at %>
63
59
  <div class="absolute left-0 top-4 w-9 text-right">
64
- <span class="text-xs text-gray-400" title="<%= format_duration(offset) %> from start">
60
+ <span class="text-xs text-gray-500" title="<%= format_duration(offset) %> from start">
65
61
  <% if offset < 0.001 %>
66
62
  0
67
63
  <% elsif offset < 1 %>
@@ -79,7 +75,7 @@
79
75
  <div class="flex-1 min-w-0">
80
76
  <!-- Event name badge -->
81
77
  <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-indigo-100 text-indigo-800">
82
- <%= event_name(event) %>
78
+ <%= event[:name] %>
83
79
  </span>
84
80
 
85
81
  <!-- Payload preview -->
@@ -110,11 +106,11 @@
110
106
 
111
107
  <div class="ml-4 flex-shrink-0 text-right">
112
108
  <p class="text-sm text-gray-500">
113
- <%= time_ago_with_title(event_occurred_at(event)) %>
109
+ <%= time_ago_with_title(event[:occurred_at]) %>
114
110
  </p>
115
- <% if event_source_file(event).present? %>
116
- <p class="text-xs text-gray-400 mt-1">
117
- <%= event_short_filepath(event) %>:<%= event_source_line(event) %>
111
+ <% if event[:source_file].present? %>
112
+ <p class="text-xs text-gray-500 mt-1">
113
+ <%= event_short_filepath(event) %>:<%= event[:source_line] %>
118
114
  </p>
119
115
  <% end %>
120
116
  </div>
@@ -1,44 +1,40 @@
1
1
  <nav class="bg-indigo-600">
2
2
  <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
3
- <div class="flex h-16 items-center justify-between">
3
+ <div class="flex min-h-16 flex-wrap items-center justify-between gap-y-2 py-2">
4
4
  <div class="flex items-center">
5
5
  <div class="flex-shrink-0">
6
6
  <%= link_to rails_event_viewer.root_path, class: "text-white text-xl font-bold" do %>
7
7
  Event Viewer
8
8
  <% end %>
9
9
  </div>
10
- <div class="ml-10 flex items-baseline space-x-4">
11
- <%= link_to "Dashboard", rails_event_viewer.root_path,
12
- class: nav_link_class("dashboard") %>
13
- <%= link_to "Events", rails_event_viewer.events_path,
14
- class: nav_link_class("events") %>
15
- <%= link_to "Event Types", rails_event_viewer.event_types_path,
16
- class: nav_link_class("event_types") %>
17
- <%= link_to "Groups", rails_event_viewer.groups_path,
18
- class: nav_link_class("groups") %>
19
- <%= link_to "Analytics", rails_event_viewer.analytics_overview_path,
20
- class: nav_link_class("analytics") %>
10
+ <div class="ml-10 flex flex-wrap items-baseline space-x-4">
11
+ <%= nav_link "Dashboard", rails_event_viewer.root_path, "dashboard" %>
12
+ <%= nav_link "Events", rails_event_viewer.events_path, "events" %>
13
+ <%= nav_link "Event Types", rails_event_viewer.event_types_path, "event_types" %>
14
+ <%= nav_link "Groups", rails_event_viewer.groups_path, "groups" %>
15
+ <% if analytics_supported? %>
16
+ <%= nav_link "Analytics", rails_event_viewer.analytics_overview_path, "analytics" %>
17
+ <% end %>
21
18
  </div>
22
19
  </div>
23
20
 
24
21
  <div class="flex items-center space-x-4">
25
- <!-- Search -->
26
- <%= form_with url: rails_event_viewer.search_events_path, method: :get, class: "relative", data: { turbo_frame: "_top" } do |f| %>
22
+ <%= form_with url: rails_event_viewer.events_path, method: :get, class: "relative" do |f| %>
27
23
  <div class="relative">
28
24
  <div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
29
- <svg class="h-4 w-4 text-indigo-200" fill="none" viewBox="0 0 24 24" stroke="currentColor">
25
+ <svg class="h-4 w-4 text-indigo-200" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
30
26
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
31
27
  </svg>
32
28
  </div>
33
29
  <%= f.search_field :q,
34
30
  value: params[:q],
35
31
  placeholder: "Search events...",
32
+ aria: { label: "Search events" },
36
33
  class: "w-64 rounded-md bg-indigo-500 py-2 pl-10 pr-3 text-sm text-white placeholder-indigo-200 focus:bg-white focus:text-gray-900 focus:placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-white" %>
37
34
  </div>
38
35
  <% end %>
39
36
 
40
- <!-- Timezone indicator -->
41
- <div class="text-indigo-200 text-sm" title="Current timezone">
37
+ <div class="text-indigo-200 text-sm" title="Times are shown in this time zone">
42
38
  <%= Time.zone.name %>
43
39
  </div>
44
40
  </div>
@@ -15,41 +15,41 @@
15
15
  <nav class="flex items-center space-x-1" aria-label="Pagination">
16
16
  <% if pagination.previous %>
17
17
  <%= link_to page_path(pagination.previous), class: "relative inline-flex items-center px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50" do %>
18
- <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
18
+ <svg aria-hidden="true" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
19
19
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
20
20
  </svg>
21
21
  <span class="ml-1">Prev</span>
22
22
  <% end %>
23
23
  <% else %>
24
24
  <span class="relative inline-flex items-center px-3 py-2 text-sm font-medium text-gray-300 bg-white border border-gray-200 rounded-md cursor-not-allowed">
25
- <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
25
+ <svg aria-hidden="true" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
26
26
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
27
27
  </svg>
28
28
  <span class="ml-1">Prev</span>
29
29
  </span>
30
30
  <% end %>
31
31
 
32
- <% (1..pagination.pages).each do |page_num| %>
33
- <% if page_num == pagination.page %>
34
- <span class="relative inline-flex items-center px-3 py-2 text-sm font-medium text-white bg-indigo-600 border border-indigo-600 rounded-md"><%= page_num %></span>
35
- <% elsif page_num == 1 || page_num == pagination.pages || (page_num >= pagination.page - 2 && page_num <= pagination.page + 2) %>
36
- <%= link_to page_num, page_path(page_num), class: "relative inline-flex items-center px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50" %>
37
- <% elsif page_num == pagination.page - 3 || page_num == pagination.page + 3 %>
32
+ <% pagination.window.each do |page_num| %>
33
+ <% if page_num == :gap %>
38
34
  <span class="relative inline-flex items-center px-2 py-2 text-sm font-medium text-gray-700">...</span>
35
+ <% elsif page_num == pagination.page %>
36
+ <span class="relative inline-flex items-center px-3 py-2 text-sm font-medium text-white bg-indigo-600 border border-indigo-600 rounded-md" aria-current="page"><%= page_num %></span>
37
+ <% else %>
38
+ <%= link_to page_num, page_path(page_num), class: "relative inline-flex items-center px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50" %>
39
39
  <% end %>
40
40
  <% end %>
41
41
 
42
42
  <% if pagination.next %>
43
43
  <%= link_to page_path(pagination.next), class: "relative inline-flex items-center px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50" do %>
44
44
  <span class="mr-1">Next</span>
45
- <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
45
+ <svg aria-hidden="true" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
46
46
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
47
47
  </svg>
48
48
  <% end %>
49
49
  <% else %>
50
50
  <span class="relative inline-flex items-center px-3 py-2 text-sm font-medium text-gray-300 bg-white border border-gray-200 rounded-md cursor-not-allowed">
51
51
  <span class="mr-1">Next</span>
52
- <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
52
+ <svg aria-hidden="true" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
53
53
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
54
54
  </svg>
55
55
  </span>
@@ -4,23 +4,23 @@
4
4
  <div class="flex-shrink-0">
5
5
  <% case icon %>
6
6
  <% when "database" %>
7
- <svg class="h-6 w-6 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
7
+ <svg aria-hidden="true" class="h-6 w-6 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
8
8
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4" />
9
9
  </svg>
10
10
  <% when "calendar" %>
11
- <svg class="h-6 w-6 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
11
+ <svg aria-hidden="true" class="h-6 w-6 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
12
12
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
13
13
  </svg>
14
14
  <% when "tag" %>
15
- <svg class="h-6 w-6 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
15
+ <svg aria-hidden="true" class="h-6 w-6 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
16
16
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" />
17
17
  </svg>
18
18
  <% when "clock" %>
19
- <svg class="h-6 w-6 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
19
+ <svg aria-hidden="true" class="h-6 w-6 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
20
20
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
21
21
  </svg>
22
22
  <% else %>
23
- <svg class="h-6 w-6 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
23
+ <svg aria-hidden="true" class="h-6 w-6 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
24
24
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
25
25
  </svg>
26
26
  <% end %>
data/config/routes.rb CHANGED
@@ -1,11 +1,7 @@
1
1
  RailsEventViewer::Engine.routes.draw do
2
2
  root to: "dashboard#index"
3
3
 
4
- resources :events, only: [:index, :show] do
5
- collection do
6
- get :search
7
- end
8
- end
4
+ resources :events, only: [:index, :show]
9
5
 
10
6
  namespace :analytics do
11
7
  get :overview
@@ -18,7 +18,6 @@ class CreateRailsEventViewerEntries < ActiveRecord::Migration[8.1]
18
18
  t.timestamps
19
19
  end
20
20
 
21
- add_index :rails_event_viewer_entries, :name
22
21
  add_index :rails_event_viewer_entries, :occurred_at
23
22
  add_index :rails_event_viewer_entries, [:name, :occurred_at]
24
23
 
@@ -17,7 +17,6 @@ class CreateRailsEventViewerEntries < ActiveRecord::Migration<%= migration_versi
17
17
  t.timestamps
18
18
  end
19
19
 
20
- add_index :rails_event_viewer_entries, :name
21
20
  add_index :rails_event_viewer_entries, :occurred_at
22
21
  add_index :rails_event_viewer_entries, [:name, :occurred_at]
23
22
 
@@ -10,10 +10,6 @@ module RailsEventViewer
10
10
  true
11
11
  end
12
12
 
13
- def supports_retention?
14
- true
15
- end
16
-
17
13
  def write_events(events)
18
14
  raise NotImplementedError, "#{self.class} must implement #write_events"
19
15
  end
@@ -59,10 +55,6 @@ module RailsEventViewer
59
55
  end.sort_by { |s| -s[:count] }
60
56
  end
61
57
 
62
- def distinct_group_values(key, source: :context)
63
- raise NotImplementedError, "#{self.class} must implement #distinct_group_values"
64
- end
65
-
66
58
  def group_instances(key, source: :context, limit: 100)
67
59
  raise NotImplementedError, "#{self.class} must implement #group_instances"
68
60
  end
@@ -3,10 +3,6 @@ module RailsEventViewer
3
3
  class ActiveRecord
4
4
  include Adapter
5
5
 
6
- def initialize(**options)
7
- @options = options
8
- end
9
-
10
6
  def table_exists?
11
7
  Entry.table_exists?
12
8
  rescue ::ActiveRecord::NoDatabaseError
@@ -16,7 +12,6 @@ module RailsEventViewer
16
12
  def write_events(events)
17
13
  return if events.empty?
18
14
 
19
- # Use insert_all for bulk inserts (more efficient)
20
15
  records = events.map do |event|
21
16
  {
22
17
  name: event[:name],
@@ -32,7 +27,7 @@ module RailsEventViewer
32
27
  }
33
28
  end
34
29
 
35
- Entry.insert_all(records)
30
+ Entry.insert_all(records, returning: false)
36
31
  end
37
32
 
38
33
  def fetch_events(relation)
@@ -57,6 +52,10 @@ module RailsEventViewer
57
52
  Entry.where("occurred_at < ?", timestamp).in_batches.delete_all
58
53
  end
59
54
 
55
+ def clear!
56
+ Entry.in_batches.delete_all
57
+ end
58
+
60
59
  def events_over_time(range:, interval:)
61
60
  Entry.where(occurred_at: range).group_by_period(interval, :occurred_at).count
62
61
  end
@@ -84,16 +83,6 @@ module RailsEventViewer
84
83
  end
85
84
  end
86
85
 
87
- def distinct_group_values(key, source: :context)
88
- column = source == :tags ? :tags : :context
89
- json_path = JsonQuery.extract_path(column, key)
90
-
91
- Entry
92
- .where(JsonQuery.extract_path_present(column, key))
93
- .distinct
94
- .pluck(Arel.sql(json_path))
95
- end
96
-
97
86
  def group_instances(key, source: :context, limit: 100)
98
87
  column = source == :tags ? :tags : :context
99
88
  json_path = JsonQuery.extract_path(column, key)
@@ -144,39 +133,18 @@ module RailsEventViewer
144
133
  end
145
134
 
146
135
  def build_scope(relation)
147
- scope = Entry.order(occurred_at: :desc)
148
- scope = apply_name_filters(scope, relation)
149
- scope = apply_tag_filters(scope, relation)
150
- scope = apply_context_filters(scope, relation)
136
+ scope = Entry.order(occurred_at: :desc, id: :desc)
137
+ scope = scope.where(name: relation.names) if relation.names.present?
138
+ scope = apply_json_filters(scope, :tags, relation.tags)
139
+ scope = apply_json_filters(scope, :context, relation.contexts)
151
140
  scope = apply_time_filters(scope, relation)
152
141
  scope = apply_search(scope, relation)
153
142
  scope
154
143
  end
155
144
 
156
- def apply_name_filters(scope, relation)
157
- return scope if relation.names.blank?
158
-
159
- if relation.names.size == 1
160
- scope.where(name: relation.names.first)
161
- else
162
- scope.where(name: relation.names)
163
- end
164
- end
165
-
166
- def apply_tag_filters(scope, relation)
167
- return scope if relation.tags.blank?
168
-
169
- relation.tags.each do |key, value|
170
- scope = scope.where(*JsonQuery.contains(:tags, key, value))
171
- end
172
- scope
173
- end
174
-
175
- def apply_context_filters(scope, relation)
176
- return scope if relation.contexts.blank?
177
-
178
- relation.contexts.each do |key, value|
179
- scope = scope.where(*JsonQuery.contains(:context, key, value))
145
+ def apply_json_filters(scope, column, filters)
146
+ filters.each do |key, value|
147
+ scope = scope.where(*JsonQuery.contains(column, key, value))
180
148
  end
181
149
  scope
182
150
  end
@@ -6,13 +6,6 @@ module RailsEventViewer
6
6
  module InMemoryFiltering
7
7
  extend ActiveSupport::Concern
8
8
 
9
- def distinct_group_values(key, source: :context)
10
- in_memory_events
11
- .map { |e| group_value_for(e, key, source) }
12
- .compact
13
- .uniq
14
- end
15
-
16
9
  def group_instances(key, source: :context, limit: 100)
17
10
  in_memory_events
18
11
  .group_by { |e| group_value_for(e, key, source).to_s }
@@ -41,6 +34,13 @@ module RailsEventViewer
41
34
  (limit ? counts.first(limit) : counts).to_h
42
35
  end
43
36
 
37
+ def event_type_statistics
38
+ statistics = in_memory_events.group_by { |e| e[:name] }.map do |name, events|
39
+ { name: name, count: events.size, last_event_at: events.filter_map { |e| e[:occurred_at] }.max }
40
+ end
41
+ statistics.sort_by { |statistic| -statistic[:count] }
42
+ end
43
+
44
44
  private
45
45
 
46
46
  def events_in(range)
@@ -24,10 +24,6 @@ module RailsEventViewer
24
24
  @max_events = max_events
25
25
  end
26
26
 
27
- def supports_retention?
28
- true
29
- end
30
-
31
27
  def write_events(new_events)
32
28
  return if new_events.empty?
33
29
 
@@ -11,10 +11,6 @@ module RailsEventViewer
11
11
  false
12
12
  end
13
13
 
14
- def supports_retention?
15
- false
16
- end
17
-
18
14
  def write_events(events)
19
15
  nil
20
16
  end
@@ -51,10 +47,6 @@ module RailsEventViewer
51
47
  0
52
48
  end
53
49
 
54
- def distinct_group_values(key, source: :context)
55
- []
56
- end
57
-
58
50
  def group_instances(key, source: :context, limit: 100)
59
51
  []
60
52
  end
@@ -26,10 +26,6 @@ module RailsEventViewer
26
26
  @max_events = max_events
27
27
  end
28
28
 
29
- def supports_analytics?
30
- true
31
- end
32
-
33
29
  def write_events(events)
34
30
  return if events.empty?
35
31
 
@@ -57,7 +53,7 @@ module RailsEventViewer
57
53
 
58
54
  def fetch_events(relation)
59
55
  if relation.filtered?
60
- return fetch_all_filtered(relation).drop(relation.offset_value).take(relation.limit_value)
56
+ return filter_events(in_memory_events, relation).drop(relation.offset_value).take(relation.limit_value)
61
57
  end
62
58
 
63
59
  start_idx = relation.offset_value
@@ -68,7 +64,7 @@ module RailsEventViewer
68
64
 
69
65
  def count_events(relation)
70
66
  if relation.filtered?
71
- fetch_all_filtered(relation).size
67
+ filter_events(in_memory_events, relation).size
72
68
  else
73
69
  @redis.with { |conn| conn.zcard(events_key) }
74
70
  end
@@ -99,7 +95,7 @@ module RailsEventViewer
99
95
  def delete_before(timestamp)
100
96
  score = (timestamp.to_f * 1_000_000).to_i
101
97
  @redis.with do |conn|
102
- deleted = conn.zremrangebyscore(events_key, "-inf", score)
98
+ deleted = conn.zremrangebyscore(events_key, "-inf", "(#{score}")
103
99
  prune_names(conn)
104
100
  deleted
105
101
  end
@@ -155,8 +151,8 @@ module RailsEventViewer
155
151
 
156
152
  def serialize_event(event, timestamp_usec = nil)
157
153
  timestamp_usec ||= (event[:occurred_at].to_f * 1_000_000).to_i
158
- event_with_id = event.merge(id: "#{timestamp_usec}-#{SecureRandom.hex(8)}")
159
- JSON.generate(event_with_id)
154
+ stored = event.merge(id: "#{timestamp_usec}-#{SecureRandom.hex(8)}", occurred_at: event[:occurred_at]&.iso8601(6))
155
+ JSON.generate(stored)
160
156
  end
161
157
 
162
158
  def deserialize_event(json)
@@ -171,18 +167,8 @@ module RailsEventViewer
171
167
  data
172
168
  end
173
169
 
174
- def fetch_all_events
175
- serialized_events = @redis.with { |conn| conn.zrevrange(events_key, 0, -1) }
176
- serialized_events.map { |json| deserialize_event(json) }
177
- end
178
-
179
- def fetch_all_filtered(relation)
180
- events = fetch_all_events
181
- filter_events(events, relation)
182
- end
183
-
184
170
  def in_memory_events
185
- fetch_all_events
171
+ @redis.with { |conn| conn.zrevrange(events_key, 0, -1) }.map { |json| deserialize_event(json) }
186
172
  end
187
173
  end
188
174
  end
@@ -18,7 +18,7 @@ module RailsEventViewer
18
18
  end
19
19
 
20
20
  def with_name(*event_names)
21
- event_names = event_names.flatten.compact
21
+ event_names = event_names.flatten.compact.map(&:to_s)
22
22
  return self if event_names.empty?
23
23
 
24
24
  clone_with { |rel| rel.names.concat(event_names) }
@@ -56,7 +56,7 @@ module RailsEventViewer
56
56
  if @thread&.alive?
57
57
  @flush_requests << true
58
58
  @thread.join(shutdown_timeout)
59
- effective_logger&.warn("[RailsEventViewer] Flusher thread still busy after #{shutdown_timeout}s writing #{@writing} events") if @thread.alive?
59
+ RailsEventViewer.effective_logger.warn("[RailsEventViewer] Flusher thread still busy after #{shutdown_timeout}s writing #{@writing} events") if @thread.alive?
60
60
  end
61
61
 
62
62
  @mutex.synchronize { @thread = nil }
@@ -72,7 +72,7 @@ module RailsEventViewer
72
72
 
73
73
  report_dropped_events
74
74
  leftover = @buffer.size
75
- effective_logger&.warn("[RailsEventViewer] #{leftover} events still buffered at shutdown") if leftover.positive?
75
+ RailsEventViewer.effective_logger.warn("[RailsEventViewer] #{leftover} events still buffered at shutdown") if leftover.positive?
76
76
  end
77
77
 
78
78
  def running?
@@ -126,7 +126,7 @@ module RailsEventViewer
126
126
  end
127
127
  return if dropped.zero?
128
128
 
129
- effective_logger&.warn("[RailsEventViewer] Dropped #{dropped} events because the buffer was full")
129
+ RailsEventViewer.effective_logger.warn("[RailsEventViewer] Dropped #{dropped} events because the buffer was full")
130
130
  end
131
131
 
132
132
  def request_flush
@@ -143,7 +143,7 @@ module RailsEventViewer
143
143
  end
144
144
 
145
145
  def run_loop
146
- effective_logger&.debug("[RailsEventViewer] Flusher thread started (PID: #{Process.pid})")
146
+ RailsEventViewer.effective_logger.debug("[RailsEventViewer] Flusher thread started (PID: #{Process.pid})")
147
147
 
148
148
  until shutdown_requested?
149
149
  begin
@@ -157,7 +157,7 @@ module RailsEventViewer
157
157
  end
158
158
  end
159
159
 
160
- effective_logger&.debug("[RailsEventViewer] Flusher thread stopped (PID: #{Process.pid})")
160
+ RailsEventViewer.effective_logger.debug("[RailsEventViewer] Flusher thread stopped (PID: #{Process.pid})")
161
161
  end
162
162
 
163
163
  def wait_for_flush
@@ -3,17 +3,7 @@ module RailsEventViewer
3
3
  private
4
4
 
5
5
  def log_error(message)
6
- logger = effective_logger
7
-
8
- if logger
9
- logger.error(message)
10
- else
11
- warn message
12
- end
13
- end
14
-
15
- def effective_logger
16
- RailsEventViewer.logger || (defined?(Rails) && Rails.logger)
6
+ RailsEventViewer.effective_logger.error(message)
17
7
  end
18
8
  end
19
9
  end
@@ -1,3 +1,3 @@
1
1
  module RailsEventViewer
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end