railscope 0.1.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 (95) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +5 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +227 -0
  5. data/Rakefile +12 -0
  6. data/app/assets/stylesheets/railscope/application.css +504 -0
  7. data/app/controllers/railscope/api/entries_controller.rb +103 -0
  8. data/app/controllers/railscope/application_controller.rb +12 -0
  9. data/app/controllers/railscope/dashboard_controller.rb +33 -0
  10. data/app/controllers/railscope/entries_controller.rb +29 -0
  11. data/app/helpers/railscope/dashboard_helper.rb +157 -0
  12. data/app/jobs/railscope/application_job.rb +6 -0
  13. data/app/jobs/railscope/purge_job.rb +15 -0
  14. data/app/models/railscope/application_record.rb +12 -0
  15. data/app/models/railscope/entry.rb +51 -0
  16. data/app/views/layouts/railscope/application.html.erb +14 -0
  17. data/app/views/railscope/application/index.html.erb +1 -0
  18. data/app/views/railscope/dashboard/index.html.erb +70 -0
  19. data/app/views/railscope/entries/show.html.erb +93 -0
  20. data/client/.gitignore +1 -0
  21. data/client/index.html +12 -0
  22. data/client/package-lock.json +2735 -0
  23. data/client/package.json +28 -0
  24. data/client/postcss.config.js +6 -0
  25. data/client/src/App.tsx +60 -0
  26. data/client/src/api/client.ts +25 -0
  27. data/client/src/api/entries.ts +36 -0
  28. data/client/src/components/Layout.tsx +17 -0
  29. data/client/src/components/PlaceholderPage.tsx +32 -0
  30. data/client/src/components/Sidebar.tsx +198 -0
  31. data/client/src/components/ui/Badge.tsx +67 -0
  32. data/client/src/components/ui/Card.tsx +38 -0
  33. data/client/src/components/ui/JsonViewer.tsx +80 -0
  34. data/client/src/components/ui/Pagination.tsx +45 -0
  35. data/client/src/components/ui/SearchInput.tsx +70 -0
  36. data/client/src/components/ui/Table.tsx +68 -0
  37. data/client/src/index.css +28 -0
  38. data/client/src/lib/hooks.ts +37 -0
  39. data/client/src/lib/types.ts +61 -0
  40. data/client/src/lib/utils.ts +38 -0
  41. data/client/src/main.tsx +13 -0
  42. data/client/src/screens/cache/Index.tsx +15 -0
  43. data/client/src/screens/client-requests/Index.tsx +15 -0
  44. data/client/src/screens/commands/Index.tsx +133 -0
  45. data/client/src/screens/commands/Show.tsx +395 -0
  46. data/client/src/screens/dumps/Index.tsx +15 -0
  47. data/client/src/screens/events/Index.tsx +15 -0
  48. data/client/src/screens/exceptions/Index.tsx +155 -0
  49. data/client/src/screens/exceptions/Show.tsx +480 -0
  50. data/client/src/screens/gates/Index.tsx +15 -0
  51. data/client/src/screens/jobs/Index.tsx +153 -0
  52. data/client/src/screens/jobs/Show.tsx +529 -0
  53. data/client/src/screens/logs/Index.tsx +15 -0
  54. data/client/src/screens/mail/Index.tsx +15 -0
  55. data/client/src/screens/models/Index.tsx +15 -0
  56. data/client/src/screens/notifications/Index.tsx +15 -0
  57. data/client/src/screens/queries/Index.tsx +159 -0
  58. data/client/src/screens/queries/Show.tsx +346 -0
  59. data/client/src/screens/redis/Index.tsx +15 -0
  60. data/client/src/screens/requests/Index.tsx +123 -0
  61. data/client/src/screens/requests/Show.tsx +395 -0
  62. data/client/src/screens/schedule/Index.tsx +15 -0
  63. data/client/src/screens/views/Index.tsx +141 -0
  64. data/client/src/screens/views/Show.tsx +337 -0
  65. data/client/tailwind.config.js +22 -0
  66. data/client/tsconfig.json +25 -0
  67. data/client/tsconfig.node.json +10 -0
  68. data/client/vite.config.ts +37 -0
  69. data/config/routes.rb +17 -0
  70. data/db/migrate/20260131023242_create_railscope_entries.rb +41 -0
  71. data/lib/generators/railscope/install_generator.rb +33 -0
  72. data/lib/generators/railscope/templates/initializer.rb +34 -0
  73. data/lib/railscope/context.rb +91 -0
  74. data/lib/railscope/engine.rb +85 -0
  75. data/lib/railscope/entry_data.rb +112 -0
  76. data/lib/railscope/filter.rb +113 -0
  77. data/lib/railscope/middleware.rb +162 -0
  78. data/lib/railscope/storage/base.rb +90 -0
  79. data/lib/railscope/storage/database.rb +83 -0
  80. data/lib/railscope/storage/redis_storage.rb +314 -0
  81. data/lib/railscope/subscribers/base_subscriber.rb +52 -0
  82. data/lib/railscope/subscribers/command_subscriber.rb +237 -0
  83. data/lib/railscope/subscribers/exception_subscriber.rb +113 -0
  84. data/lib/railscope/subscribers/job_subscriber.rb +249 -0
  85. data/lib/railscope/subscribers/query_subscriber.rb +130 -0
  86. data/lib/railscope/subscribers/request_subscriber.rb +121 -0
  87. data/lib/railscope/subscribers/view_subscriber.rb +201 -0
  88. data/lib/railscope/version.rb +5 -0
  89. data/lib/railscope.rb +145 -0
  90. data/lib/tasks/railscope_sample.rake +30 -0
  91. data/public/railscope/assets/app.css +1 -0
  92. data/public/railscope/assets/app.js +70 -0
  93. data/public/railscope/assets/index.html +13 -0
  94. data/sig/railscope.rbs +4 -0
  95. metadata +157 -0
@@ -0,0 +1,504 @@
1
+ /* Railscope Dashboard Styles */
2
+
3
+ :root {
4
+ --bg-color: #0d1117;
5
+ --surface-color: #161b22;
6
+ --border-color: #30363d;
7
+ --text-color: #c9d1d9;
8
+ --text-muted: #8b949e;
9
+ --accent-color: #58a6ff;
10
+ --success-color: #3fb950;
11
+ --warning-color: #d29922;
12
+ --error-color: #f85149;
13
+ --purple-color: #a371f7;
14
+ }
15
+
16
+ * {
17
+ box-sizing: border-box;
18
+ }
19
+
20
+ body {
21
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
22
+ background-color: var(--bg-color);
23
+ color: var(--text-color);
24
+ line-height: 1.5;
25
+ margin: 0;
26
+ padding: 0;
27
+ }
28
+
29
+ .railscope-container {
30
+ max-width: 1400px;
31
+ margin: 0 auto;
32
+ padding: 20px;
33
+ }
34
+
35
+ /* Header */
36
+ .railscope-header {
37
+ margin-bottom: 24px;
38
+ }
39
+
40
+ .railscope-header h1 {
41
+ margin: 0;
42
+ font-size: 24px;
43
+ font-weight: 600;
44
+ }
45
+
46
+ .railscope-header .subtitle {
47
+ margin: 4px 0 0;
48
+ color: var(--text-muted);
49
+ font-size: 14px;
50
+ }
51
+
52
+ /* Filters */
53
+ .railscope-filters {
54
+ display: flex;
55
+ gap: 8px;
56
+ margin-bottom: 20px;
57
+ flex-wrap: wrap;
58
+ }
59
+
60
+ .filter-btn {
61
+ padding: 6px 12px;
62
+ background: var(--surface-color);
63
+ border: 1px solid var(--border-color);
64
+ border-radius: 6px;
65
+ color: var(--text-color);
66
+ text-decoration: none;
67
+ font-size: 14px;
68
+ transition: all 0.15s ease;
69
+ }
70
+
71
+ .filter-btn:hover {
72
+ border-color: var(--accent-color);
73
+ }
74
+
75
+ .filter-btn.active {
76
+ background: var(--accent-color);
77
+ border-color: var(--accent-color);
78
+ color: #fff;
79
+ }
80
+
81
+ /* Table */
82
+ .entries-table {
83
+ width: 100%;
84
+ border-collapse: collapse;
85
+ background: var(--surface-color);
86
+ border: 1px solid var(--border-color);
87
+ border-radius: 8px;
88
+ overflow: hidden;
89
+ }
90
+
91
+ .entries-table th,
92
+ .entries-table td {
93
+ padding: 12px 16px;
94
+ text-align: left;
95
+ border-bottom: 1px solid var(--border-color);
96
+ vertical-align: middle;
97
+ }
98
+
99
+ .entries-table th {
100
+ background: rgba(0, 0, 0, 0.2);
101
+ font-weight: 600;
102
+ font-size: 12px;
103
+ text-transform: uppercase;
104
+ color: var(--text-muted);
105
+ }
106
+
107
+ .entries-table tr:last-child td {
108
+ border-bottom: none;
109
+ }
110
+
111
+ .entries-table tr:hover {
112
+ background: rgba(255, 255, 255, 0.02);
113
+ }
114
+
115
+ .entry-link {
116
+ text-decoration: none;
117
+ color: inherit;
118
+ }
119
+
120
+ .entry-link:hover {
121
+ color: var(--accent-color);
122
+ }
123
+
124
+ /* Type Badge */
125
+ .type-badge {
126
+ display: inline-block;
127
+ padding: 3px 8px;
128
+ border-radius: 4px;
129
+ font-size: 12px;
130
+ font-weight: 500;
131
+ }
132
+
133
+ .type-badge.request { background: #1f6feb33; color: #58a6ff; }
134
+ .type-badge.query { background: #a371f733; color: #a371f7; }
135
+ .type-badge.exception { background: #f8514933; color: #f85149; }
136
+ .type-badge.job_enqueue { background: #3fb95033; color: #3fb950; }
137
+ .type-badge.job_perform { background: #3fb95033; color: #3fb950; }
138
+
139
+ /* Entry Summary */
140
+ .entry-summary {
141
+ font-size: 14px;
142
+ }
143
+
144
+ .request-summary .method {
145
+ font-weight: 600;
146
+ color: var(--accent-color);
147
+ }
148
+
149
+ .request-summary .path {
150
+ color: var(--text-color);
151
+ }
152
+
153
+ .request-summary .status {
154
+ padding: 2px 6px;
155
+ border-radius: 3px;
156
+ font-size: 12px;
157
+ font-weight: 500;
158
+ }
159
+
160
+ .request-summary .status.success { background: #3fb95033; color: #3fb950; }
161
+ .request-summary .status.redirect { background: #d2992233; color: #d29922; }
162
+ .request-summary .status.client-error { background: #d2992233; color: #d29922; }
163
+ .request-summary .status.server-error { background: #f8514933; color: #f85149; }
164
+
165
+ .duration {
166
+ color: var(--text-muted);
167
+ font-size: 12px;
168
+ }
169
+
170
+ .query-summary .sql {
171
+ background: rgba(0, 0, 0, 0.3);
172
+ padding: 2px 6px;
173
+ border-radius: 3px;
174
+ font-size: 12px;
175
+ color: var(--text-muted);
176
+ }
177
+
178
+ .exception-summary .exception-class {
179
+ color: var(--error-color);
180
+ font-weight: 500;
181
+ }
182
+
183
+ .exception-summary .exception-message {
184
+ color: var(--text-muted);
185
+ }
186
+
187
+ .job-summary .job-class {
188
+ color: var(--success-color);
189
+ font-weight: 500;
190
+ }
191
+
192
+ .job-summary .queue-name {
193
+ color: var(--text-muted);
194
+ font-size: 12px;
195
+ }
196
+
197
+ /* Tags */
198
+ td.entry-tags {
199
+ max-width: 200px;
200
+ }
201
+
202
+ .entry-tags .tags-wrapper {
203
+ display: flex;
204
+ gap: 4px;
205
+ flex-wrap: wrap;
206
+ align-items: center;
207
+ }
208
+
209
+ .tag {
210
+ display: inline-flex;
211
+ align-items: center;
212
+ padding: 2px 6px;
213
+ background: var(--border-color);
214
+ border-radius: 3px;
215
+ font-size: 11px;
216
+ line-height: 1.4;
217
+ color: var(--text-muted);
218
+ text-decoration: none;
219
+ }
220
+
221
+ .tag:hover {
222
+ background: var(--accent-color);
223
+ color: #fff;
224
+ }
225
+
226
+ /* Time */
227
+ .entry-time {
228
+ color: var(--text-muted);
229
+ font-size: 13px;
230
+ white-space: nowrap;
231
+ }
232
+
233
+ /* Pagination */
234
+ .railscope-pagination {
235
+ display: flex;
236
+ align-items: center;
237
+ justify-content: center;
238
+ gap: 16px;
239
+ margin-top: 20px;
240
+ }
241
+
242
+ .page-btn {
243
+ padding: 8px 16px;
244
+ background: var(--surface-color);
245
+ border: 1px solid var(--border-color);
246
+ border-radius: 6px;
247
+ color: var(--text-color);
248
+ text-decoration: none;
249
+ font-size: 14px;
250
+ }
251
+
252
+ .page-btn:hover {
253
+ border-color: var(--accent-color);
254
+ }
255
+
256
+ .page-info {
257
+ color: var(--text-muted);
258
+ font-size: 14px;
259
+ }
260
+
261
+ /* Empty State */
262
+ .empty-state {
263
+ text-align: center;
264
+ padding: 60px 20px;
265
+ color: var(--text-muted);
266
+ }
267
+
268
+ /* Detail View */
269
+ .breadcrumb {
270
+ margin-bottom: 16px;
271
+ }
272
+
273
+ .back-link {
274
+ color: var(--accent-color);
275
+ text-decoration: none;
276
+ font-size: 14px;
277
+ }
278
+
279
+ .back-link:hover {
280
+ text-decoration: underline;
281
+ }
282
+
283
+ .entry-header {
284
+ display: flex;
285
+ align-items: center;
286
+ gap: 12px;
287
+ }
288
+
289
+ .entry-header h1 {
290
+ font-size: 20px;
291
+ font-weight: 600;
292
+ font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
293
+ }
294
+
295
+ .detail-grid {
296
+ display: grid;
297
+ grid-template-columns: 1fr 350px;
298
+ gap: 24px;
299
+ }
300
+
301
+ @media (max-width: 1000px) {
302
+ .detail-grid {
303
+ grid-template-columns: 1fr;
304
+ }
305
+ }
306
+
307
+ .detail-section {
308
+ background: var(--surface-color);
309
+ border: 1px solid var(--border-color);
310
+ border-radius: 8px;
311
+ padding: 16px;
312
+ margin-bottom: 16px;
313
+ }
314
+
315
+ .detail-section h2 {
316
+ margin: 0 0 12px;
317
+ font-size: 14px;
318
+ font-weight: 600;
319
+ text-transform: uppercase;
320
+ color: var(--text-muted);
321
+ }
322
+
323
+ .detail-meta {
324
+ display: grid;
325
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
326
+ gap: 12px;
327
+ }
328
+
329
+ .meta-item {
330
+ display: flex;
331
+ flex-direction: column;
332
+ gap: 2px;
333
+ }
334
+
335
+ .meta-label {
336
+ font-size: 12px;
337
+ color: var(--text-muted);
338
+ }
339
+
340
+ .meta-value {
341
+ font-size: 14px;
342
+ color: var(--text-color);
343
+ }
344
+
345
+ .meta-value.mono {
346
+ font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
347
+ font-size: 12px;
348
+ }
349
+
350
+ /* JSON Viewer */
351
+ .json-viewer {
352
+ background: rgba(0, 0, 0, 0.2);
353
+ border-radius: 6px;
354
+ padding: 12px;
355
+ overflow-x: auto;
356
+ }
357
+
358
+ .json-row {
359
+ padding: 2px 0;
360
+ font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
361
+ font-size: 13px;
362
+ }
363
+
364
+ .json-key {
365
+ color: var(--accent-color);
366
+ }
367
+
368
+ .json-string {
369
+ color: var(--success-color);
370
+ }
371
+
372
+ .json-string.long {
373
+ display: block;
374
+ margin-top: 4px;
375
+ padding: 8px;
376
+ background: rgba(0, 0, 0, 0.2);
377
+ border-radius: 4px;
378
+ white-space: pre-wrap;
379
+ word-break: break-all;
380
+ }
381
+
382
+ .json-string.long code {
383
+ color: var(--text-muted);
384
+ }
385
+
386
+ .json-number {
387
+ color: var(--warning-color);
388
+ }
389
+
390
+ .json-boolean {
391
+ color: var(--purple-color);
392
+ }
393
+
394
+ .json-null {
395
+ color: var(--text-muted);
396
+ font-style: italic;
397
+ }
398
+
399
+ .json-array-empty,
400
+ .json-object-empty {
401
+ color: var(--text-muted);
402
+ }
403
+
404
+ .json-bracket {
405
+ color: var(--text-muted);
406
+ }
407
+
408
+ .json-array-items {
409
+ padding-left: 16px;
410
+ }
411
+
412
+ /* Backtrace */
413
+ .backtrace {
414
+ background: rgba(0, 0, 0, 0.2);
415
+ border-radius: 6px;
416
+ padding: 8px 0;
417
+ overflow-x: auto;
418
+ max-height: 400px;
419
+ overflow-y: auto;
420
+ }
421
+
422
+ .backtrace-line {
423
+ display: flex;
424
+ gap: 12px;
425
+ padding: 2px 12px;
426
+ font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
427
+ font-size: 12px;
428
+ }
429
+
430
+ .backtrace-line:hover {
431
+ background: rgba(255, 255, 255, 0.02);
432
+ }
433
+
434
+ .backtrace-line .line-number {
435
+ color: var(--text-muted);
436
+ min-width: 24px;
437
+ text-align: right;
438
+ }
439
+
440
+ .backtrace-line code {
441
+ color: var(--text-color);
442
+ white-space: pre;
443
+ }
444
+
445
+ /* Timeline */
446
+ .timeline-info {
447
+ font-size: 12px;
448
+ color: var(--text-muted);
449
+ margin: 0 0 12px;
450
+ }
451
+
452
+ .timeline {
453
+ display: flex;
454
+ flex-direction: column;
455
+ gap: 4px;
456
+ }
457
+
458
+ .timeline-item {
459
+ border-radius: 6px;
460
+ transition: background 0.15s ease;
461
+ }
462
+
463
+ .timeline-item:hover {
464
+ background: rgba(255, 255, 255, 0.02);
465
+ }
466
+
467
+ .timeline-item.current {
468
+ background: rgba(88, 166, 255, 0.1);
469
+ }
470
+
471
+ .timeline-link {
472
+ display: flex;
473
+ align-items: center;
474
+ gap: 8px;
475
+ padding: 8px;
476
+ text-decoration: none;
477
+ color: var(--text-color);
478
+ }
479
+
480
+ .timeline-type {
481
+ flex-shrink: 0;
482
+ }
483
+
484
+ .timeline-summary {
485
+ flex: 1;
486
+ font-size: 12px;
487
+ color: var(--text-muted);
488
+ white-space: nowrap;
489
+ overflow: hidden;
490
+ text-overflow: ellipsis;
491
+ }
492
+
493
+ .timeline-time {
494
+ font-size: 11px;
495
+ color: var(--text-muted);
496
+ flex-shrink: 0;
497
+ }
498
+
499
+ /* Tags wrapper in detail */
500
+ .detail-section .tags-wrapper {
501
+ display: flex;
502
+ gap: 6px;
503
+ flex-wrap: wrap;
504
+ }
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Railscope
4
+ module Api
5
+ class EntriesController < ApplicationController
6
+ skip_forgery_protection
7
+
8
+ def index
9
+ filters = extract_filters
10
+ entries = storage.all(filters: filters, page: current_page, per_page: per_page)
11
+ total_count = storage.count(filters: filters)
12
+
13
+ render json: {
14
+ data: entries.map { |e| serialize_entry(e) },
15
+ meta: {
16
+ current_page: current_page,
17
+ total_pages: (total_count.to_f / per_page).ceil,
18
+ total_count: total_count,
19
+ per_page: per_page
20
+ }
21
+ }
22
+ end
23
+
24
+ def show
25
+ entry = storage.find!(params[:id])
26
+ batch_entries = storage.for_batch(entry.batch_id).reject { |e| e.uuid == entry.uuid }.first(100)
27
+
28
+ render json: {
29
+ data: serialize_entry(entry, include_family_count: true),
30
+ batch: batch_entries.map { |e| serialize_entry(e) }
31
+ }
32
+ end
33
+
34
+ def batch
35
+ entries = storage.for_batch(params[:batch_id])
36
+
37
+ render json: {
38
+ data: entries.map { |e| serialize_entry(e) }
39
+ }
40
+ end
41
+
42
+ def family
43
+ entries = storage.for_family(params[:family_hash], page: current_page, per_page: per_page)
44
+ total_count = storage.family_count(params[:family_hash])
45
+
46
+ render json: {
47
+ data: entries.map { |e| serialize_entry(e) },
48
+ meta: {
49
+ current_page: current_page,
50
+ total_pages: (total_count.to_f / per_page).ceil,
51
+ total_count: total_count,
52
+ per_page: per_page
53
+ }
54
+ }
55
+ end
56
+
57
+ def destroy
58
+ storage.destroy_all!
59
+ render json: { success: true }
60
+ end
61
+
62
+ private
63
+
64
+ def storage
65
+ Railscope.storage
66
+ end
67
+
68
+ def extract_filters
69
+ {}.tap do |filters|
70
+ filters[:type] = params[:type] if params[:type].present?
71
+ filters[:tag] = params[:tag] if params[:tag].present?
72
+ filters[:batch_id] = params[:batch_id] if params[:batch_id].present?
73
+ end
74
+ end
75
+
76
+ def serialize_entry(entry, include_family_count: false)
77
+ result = {
78
+ id: entry.uuid,
79
+ uuid: entry.uuid,
80
+ batch_id: entry.batch_id,
81
+ family_hash: entry.family_hash,
82
+ entry_type: entry.entry_type,
83
+ payload: entry.payload,
84
+ tags: entry.tags,
85
+ occurred_at: entry.occurred_at&.iso8601,
86
+ created_at: entry.created_at&.iso8601
87
+ }
88
+
89
+ result[:family_count] = storage.family_count(entry.family_hash) if include_family_count && entry.family_hash
90
+
91
+ result
92
+ end
93
+
94
+ def current_page
95
+ [params.fetch(:page, 1).to_i, 1].max
96
+ end
97
+
98
+ def per_page
99
+ 25
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Railscope
4
+ class ApplicationController < ActionController::Base
5
+ protect_from_forgery with: :exception
6
+ layout "railscope/application"
7
+
8
+ def index
9
+ render :index
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Railscope
4
+ class DashboardController < ApplicationController
5
+ PER_PAGE = 25
6
+
7
+ def index
8
+ @entries = filtered_entries.recent.limit(PER_PAGE).offset(offset)
9
+ @total_count = filtered_entries.count
10
+ @entry_types = Entry.distinct.pluck(:entry_type).sort
11
+ @current_type = params[:type]
12
+ @current_page = current_page
13
+ @total_pages = (@total_count.to_f / PER_PAGE).ceil
14
+ end
15
+
16
+ private
17
+
18
+ def filtered_entries
19
+ entries = Entry.all
20
+ entries = entries.by_type(params[:type]) if params[:type].present?
21
+ entries = entries.with_tag(params[:tag]) if params[:tag].present?
22
+ entries
23
+ end
24
+
25
+ def current_page
26
+ [params.fetch(:page, 1).to_i, 1].max
27
+ end
28
+
29
+ def offset
30
+ (current_page - 1) * PER_PAGE
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Railscope
4
+ class EntriesController < ApplicationController
5
+ def show
6
+ @entry = storage.find!(params[:id])
7
+ @related_entries = find_related_entries
8
+ end
9
+
10
+ private
11
+
12
+ def storage
13
+ Railscope.storage
14
+ end
15
+
16
+ def find_related_entries
17
+ request_id = @entry.payload["request_id"]
18
+ return [] if request_id.blank?
19
+
20
+ # For database storage, we can query by payload
21
+ # For Redis storage, we use batch_id instead
22
+ if @entry.batch_id.present?
23
+ storage.for_batch(@entry.batch_id).reject { |e| e.uuid == @entry.uuid }
24
+ else
25
+ []
26
+ end
27
+ end
28
+ end
29
+ end