solid_queue_web 0.5.5 → 0.7.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/README.md +28 -8
  3. data/Rakefile +2 -2
  4. data/app/assets/stylesheets/solid_queue_web/_01_base.css +41 -0
  5. data/app/assets/stylesheets/solid_queue_web/_02_layout.css +105 -0
  6. data/app/assets/stylesheets/solid_queue_web/_03_stats.css +49 -0
  7. data/app/assets/stylesheets/solid_queue_web/_04_table.css +52 -0
  8. data/app/assets/stylesheets/solid_queue_web/_05_badges.css +27 -0
  9. data/app/assets/stylesheets/solid_queue_web/_06_buttons.css +38 -0
  10. data/app/assets/stylesheets/solid_queue_web/_07_forms.css +103 -0
  11. data/app/assets/stylesheets/solid_queue_web/_08_detail.css +84 -0
  12. data/app/assets/stylesheets/solid_queue_web/_09_pagination.css +27 -0
  13. data/app/assets/stylesheets/solid_queue_web/_10_responsive.css +73 -0
  14. data/app/assets/stylesheets/solid_queue_web/_11_throughput.css +68 -0
  15. data/app/assets/stylesheets/solid_queue_web/application.css +1 -548
  16. data/app/controllers/solid_queue_web/application_controller.rb +2 -0
  17. data/app/controllers/solid_queue_web/dashboard_controller.rb +12 -0
  18. data/app/controllers/solid_queue_web/failed_jobs/selections_controller.rb +27 -0
  19. data/app/controllers/solid_queue_web/failed_jobs_controller.rb +5 -3
  20. data/app/controllers/solid_queue_web/history_controller.rb +16 -0
  21. data/app/controllers/solid_queue_web/jobs/selections_controller.rb +21 -0
  22. data/app/controllers/solid_queue_web/jobs_controller.rb +13 -8
  23. data/app/controllers/solid_queue_web/queues/jobs_controller.rb +1 -1
  24. data/app/controllers/solid_queue_web/queues_controller.rb +15 -0
  25. data/app/controllers/solid_queue_web/search_controller.rb +23 -0
  26. data/app/helpers/solid_queue_web/application_helper.rb +15 -1
  27. data/app/javascript/solid_queue_web/application.js +4 -0
  28. data/app/javascript/solid_queue_web/refresh_controller.js +52 -0
  29. data/app/javascript/solid_queue_web/search_controller.js +5 -0
  30. data/app/javascript/solid_queue_web/selection_controller.js +42 -0
  31. data/app/views/layouts/solid_queue_web/application.html.erb +2 -0
  32. data/app/views/solid_queue_web/dashboard/index.html.erb +41 -1
  33. data/app/views/solid_queue_web/failed_jobs/index.html.erb +93 -49
  34. data/app/views/solid_queue_web/history/index.html.erb +67 -0
  35. data/app/views/solid_queue_web/jobs/index.html.erb +115 -49
  36. data/app/views/solid_queue_web/processes/index.html.erb +3 -1
  37. data/app/views/solid_queue_web/queues/index.html.erb +15 -1
  38. data/app/views/solid_queue_web/search/index.html.erb +64 -0
  39. data/config/importmap.rb +2 -0
  40. data/config/routes.rb +16 -6
  41. data/lib/solid_queue_web/version.rb +1 -1
  42. metadata +20 -1
@@ -0,0 +1,68 @@
1
+ .sqd-throughput__summary {
2
+ display: flex;
3
+ gap: 1.25rem;
4
+ font-size: 13px;
5
+ color: var(--muted);
6
+ }
7
+
8
+ .sqd-throughput__summary strong {
9
+ color: var(--text);
10
+ font-weight: 600;
11
+ }
12
+
13
+ .sqd-sparkline {
14
+ display: flex;
15
+ align-items: flex-end;
16
+ gap: 3px;
17
+ height: 80px;
18
+ padding: 0.75rem 1.25rem 0;
19
+ }
20
+
21
+ .sqd-sparkline__col {
22
+ flex: 1;
23
+ display: flex;
24
+ flex-direction: column;
25
+ align-items: center;
26
+ gap: 3px;
27
+ height: 100%;
28
+ }
29
+
30
+ .sqd-sparkline__bar-wrap {
31
+ flex: 1;
32
+ width: 100%;
33
+ display: flex;
34
+ align-items: flex-end;
35
+ }
36
+
37
+ .sqd-sparkline__bar {
38
+ width: 100%;
39
+ background: var(--primary);
40
+ border-radius: 2px 2px 0 0;
41
+ min-height: 2px;
42
+ opacity: 0.75;
43
+ transition: opacity 0.15s;
44
+ }
45
+
46
+ .sqd-sparkline__bar:hover {
47
+ opacity: 1;
48
+ }
49
+
50
+ .sqd-sparkline__tick {
51
+ font-size: 10px;
52
+ color: var(--muted);
53
+ white-space: nowrap;
54
+ min-height: 14px;
55
+ line-height: 14px;
56
+ }
57
+
58
+ .sqd-sparkline__empty {
59
+ flex: 1;
60
+ display: flex;
61
+ align-items: center;
62
+ justify-content: center;
63
+ color: var(--muted);
64
+ font-size: 13px;
65
+ padding: 1rem 1.25rem;
66
+ }
67
+
68
+ .sqd-stat--done .sqd-stat__value { color: var(--success); }
@@ -1,550 +1,3 @@
1
1
  /*
2
2
  *= require_self
3
- */
4
-
5
- *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
6
-
7
- .sqd-sr-only {
8
- position: absolute;
9
- width: 1px;
10
- height: 1px;
11
- padding: 0;
12
- margin: -1px;
13
- overflow: hidden;
14
- clip: rect(0, 0, 0, 0);
15
- white-space: nowrap;
16
- border: 0;
17
- }
18
-
19
- :focus-visible {
20
- outline: 2px solid var(--primary);
21
- outline-offset: 2px;
22
- border-radius: 2px;
23
- }
24
-
25
- :root {
26
- --bg: #f8f9fa;
27
- --surface: #ffffff;
28
- --border: #dee2e6;
29
- --text: #212529;
30
- --muted: #6c757d;
31
- --primary: #0d6efd;
32
- --danger: #dc3545;
33
- --warning: #fd7e14;
34
- --success: #198754;
35
- --info: #0dcaf0;
36
- --purple: #6f42c1;
37
- }
38
-
39
- body {
40
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
41
- font-size: 14px;
42
- background: var(--bg);
43
- color: var(--text);
44
- line-height: 1.5;
45
- }
46
-
47
- /* Layout */
48
- .sqd-header {
49
- background: var(--surface);
50
- border-bottom: 1px solid var(--border);
51
- }
52
-
53
- .sqd-header__inner {
54
- max-width: 1200px;
55
- margin: 0 auto;
56
- padding: 0 1.5rem;
57
- display: flex;
58
- align-items: center;
59
- gap: 2rem;
60
- height: 56px;
61
- }
62
-
63
- .sqd-header__title {
64
- font-size: 16px;
65
- font-weight: 600;
66
- color: var(--text);
67
- text-decoration: none;
68
- }
69
-
70
- .sqd-nav {
71
- display: flex;
72
- gap: 0.25rem;
73
- list-style: none;
74
- }
75
-
76
- .sqd-nav a {
77
- display: block;
78
- padding: 0.35rem 0.75rem;
79
- border-radius: 6px;
80
- color: var(--muted);
81
- text-decoration: none;
82
- font-size: 13px;
83
- font-weight: 500;
84
- transition: background 0.1s, color 0.1s;
85
- }
86
-
87
- .sqd-nav a:hover,
88
- .sqd-nav a.active {
89
- background: var(--bg);
90
- color: var(--text);
91
- }
92
-
93
- .sqd-nav-toggle {
94
- display: none;
95
- flex-direction: column;
96
- justify-content: center;
97
- gap: 5px;
98
- width: 36px;
99
- height: 36px;
100
- padding: 6px;
101
- margin-left: auto;
102
- background: none;
103
- border: 1px solid var(--border);
104
- border-radius: 5px;
105
- cursor: pointer;
106
- }
107
-
108
- .sqd-nav-toggle span {
109
- display: block;
110
- height: 2px;
111
- background: var(--text);
112
- border-radius: 1px;
113
- }
114
-
115
- .sqd-main {
116
- max-width: 1200px;
117
- margin: 0 auto;
118
- padding: 2rem 1.5rem;
119
- }
120
-
121
- .sqd-page-title {
122
- font-size: 20px;
123
- font-weight: 600;
124
- margin-bottom: 0;
125
- }
126
-
127
- .sqd-page-header {
128
- display: flex;
129
- align-items: center;
130
- justify-content: space-between;
131
- margin-bottom: 1.5rem;
132
- }
133
-
134
- .sqd-actions {
135
- display: flex;
136
- gap: 0.5rem;
137
- }
138
-
139
- /* Flash notices */
140
- .sqd-flash {
141
- padding: 0.75rem 1rem;
142
- border-radius: 6px;
143
- margin-bottom: 1rem;
144
- font-size: 13px;
145
- }
146
- .sqd-flash--notice { background: #d1e7dd; color: #0f5132; border: 1px solid #badbcc; }
147
- .sqd-flash--alert { background: #f8d7da; color: #842029; border: 1px solid #f5c2c7; }
148
-
149
- /* Stat cards */
150
- .sqd-stats {
151
- display: grid;
152
- grid-template-columns: repeat(auto-fill, minmax(128px, 1fr));
153
- gap: 1rem;
154
- margin-bottom: 2rem;
155
- }
156
-
157
- .sqd-stat {
158
- background: var(--surface);
159
- border: 1px solid var(--border);
160
- border-radius: 8px;
161
- padding: 1.25rem 1rem;
162
- text-align: center;
163
- }
164
-
165
- .sqd-stat__value {
166
- font-size: 28px;
167
- font-weight: 700;
168
- line-height: 1;
169
- margin-bottom: 0.25rem;
170
- }
171
-
172
- .sqd-stat__label {
173
- font-size: 12px;
174
- color: var(--muted);
175
- text-transform: uppercase;
176
- letter-spacing: 0.05em;
177
- }
178
-
179
- .sqd-stat--ready .sqd-stat__value { color: var(--success); }
180
- .sqd-stat--scheduled .sqd-stat__value { color: var(--info); }
181
- .sqd-stat--claimed .sqd-stat__value { color: var(--primary); }
182
- .sqd-stat--failed .sqd-stat__value { color: var(--danger); }
183
- .sqd-stat--blocked .sqd-stat__value { color: var(--warning); }
184
- .sqd-stat--queues .sqd-stat__value { color: var(--purple); }
185
- .sqd-stat--processes .sqd-stat__value { color: var(--muted); }
186
- .sqd-stat--recurring .sqd-stat__value { color: var(--info); }
187
-
188
- .sqd-stat--link {
189
- display: block;
190
- text-decoration: none;
191
- color: inherit;
192
- transition: border-color 0.15s, box-shadow 0.15s, transform 0.15s;
193
- }
194
- .sqd-stat--link:hover {
195
- border-color: var(--primary);
196
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
197
- transform: translateY(-2px);
198
- }
199
-
200
- /* Tables */
201
- .sqd-card {
202
- background: var(--surface);
203
- border: 1px solid var(--border);
204
- border-radius: 8px;
205
- overflow: hidden;
206
- }
207
-
208
- .sqd-card__header {
209
- padding: 0.875rem 1rem;
210
- border-bottom: 1px solid var(--border);
211
- display: flex;
212
- align-items: center;
213
- justify-content: space-between;
214
- gap: 1rem;
215
- }
216
-
217
- .sqd-card__title {
218
- font-size: 14px;
219
- font-weight: 600;
220
- }
221
-
222
- table {
223
- width: 100%;
224
- border-collapse: collapse;
225
- }
226
-
227
- th {
228
- padding: 0.625rem 1rem;
229
- text-align: left;
230
- font-size: 12px;
231
- font-weight: 600;
232
- color: var(--muted);
233
- text-transform: uppercase;
234
- letter-spacing: 0.05em;
235
- border-bottom: 1px solid var(--border);
236
- white-space: nowrap;
237
- }
238
-
239
- td {
240
- padding: 0.75rem 1rem;
241
- border-bottom: 1px solid var(--border);
242
- vertical-align: middle;
243
- }
244
-
245
- tr:last-child td { border-bottom: none; }
246
- tbody tr:hover { background: var(--bg); }
247
-
248
- .sqd-empty {
249
- text-align: center;
250
- padding: 3rem 1rem;
251
- color: var(--muted);
252
- }
253
-
254
- /* Badges */
255
- .sqd-badge {
256
- display: inline-block;
257
- padding: 0.2em 0.55em;
258
- border-radius: 4px;
259
- font-size: 11px;
260
- font-weight: 600;
261
- line-height: 1;
262
- text-transform: uppercase;
263
- letter-spacing: 0.04em;
264
- }
265
-
266
- .sqd-badge--ready { background: #d1e7dd; color: #0f5132; }
267
- .sqd-badge--scheduled { background: #cff4fc; color: #055160; }
268
- .sqd-badge--claimed { background: #cfe2ff; color: #084298; }
269
- .sqd-badge--failed { background: #f8d7da; color: #842029; }
270
- .sqd-badge--blocked { background: #fff3cd; color: #664d03; }
271
- .sqd-badge--static { background: #d1e7dd; color: #0f5132; }
272
- .sqd-badge--dynamic { background: #e0d7f5; color: #4a2c8a; }
273
- .sqd-badge--paused { background: #e2e3e5; color: #41464b; }
274
- .sqd-badge--running { background: #d1e7dd; color: #0f5132; }
275
- .sqd-badge--supervisor { background: #e0d7f5; color: #4a2c8a; }
276
- .sqd-badge--worker { background: #d1e7dd; color: #0f5132; }
277
- .sqd-badge--dispatcher { background: #cff4fc; color: #055160; }
278
-
279
- .sqd-process-meta { font-size: 12px; color: var(--muted); }
280
- .sqd-process-meta span + span::before { content: " · "; }
281
- .sqd-muted-text { color: var(--muted); font-size: 13px; }
282
-
283
- /* Buttons */
284
- .sqd-btn {
285
- display: inline-flex;
286
- align-items: center;
287
- padding: 0.35rem 0.75rem;
288
- border-radius: 5px;
289
- font-size: 12px;
290
- font-weight: 500;
291
- text-decoration: none;
292
- border: 1px solid transparent;
293
- cursor: pointer;
294
- transition: opacity 0.15s;
295
- }
296
- .sqd-btn:hover { opacity: 0.85; }
297
- .sqd-btn--primary { background: var(--primary); color: #fff; border-color: var(--primary); }
298
- .sqd-btn--danger { background: var(--danger); color: #fff; border-color: var(--danger); }
299
- .sqd-btn--muted { background: var(--surface); color: var(--text); border-color: var(--border); }
300
- .sqd-btn--sm { padding: 0.2rem 0.55rem; font-size: 11px; }
301
-
302
- .sqd-row-actions { white-space: nowrap; text-align: right; width: 1%; }
303
- .sqd-row-actions form { display: inline; margin-left: 0.25rem; }
304
-
305
- /* Search */
306
- .sqd-search {
307
- display: flex;
308
- gap: 0.5rem;
309
- align-items: center;
310
- margin-bottom: 1rem;
311
- }
312
-
313
- .sqd-search__input {
314
- width: 280px;
315
- padding: 0.35rem 0.75rem;
316
- border: 1px solid var(--border);
317
- border-radius: 5px;
318
- font-size: 13px;
319
- background: var(--surface);
320
- color: var(--text);
321
- line-height: 1.5;
322
- }
323
-
324
- .sqd-search__input:focus {
325
- outline: 2px solid var(--primary);
326
- outline-offset: -1px;
327
- border-color: var(--primary);
328
- }
329
-
330
- @media (max-width: 640px) {
331
- .sqd-search { flex-wrap: wrap; }
332
- .sqd-search__input { width: 100%; }
333
- }
334
-
335
- /* Filters */
336
- .sqd-filters {
337
- display: flex;
338
- gap: 0.5rem;
339
- flex-wrap: wrap;
340
- margin-bottom: 1rem;
341
- }
342
-
343
- .sqd-filters a {
344
- padding: 0.35rem 0.875rem;
345
- border-radius: 20px;
346
- font-size: 12px;
347
- font-weight: 500;
348
- text-decoration: none;
349
- border: 1px solid var(--border);
350
- color: var(--muted);
351
- background: var(--surface);
352
- transition: all 0.1s;
353
- }
354
-
355
- .sqd-filters a:hover,
356
- .sqd-filters a.active {
357
- background: var(--primary);
358
- border-color: var(--primary);
359
- color: #fff;
360
- }
361
-
362
- /* Code / monospace */
363
- .sqd-mono {
364
- font-family: "SFMono-Regular", Menlo, Monaco, Consolas, monospace;
365
- font-size: 12px;
366
- }
367
-
368
- .sqd-error-msg {
369
- color: var(--danger);
370
- font-size: 12px;
371
- }
372
-
373
- .sqd-truncate {
374
- max-width: 320px;
375
- overflow: hidden;
376
- text-overflow: ellipsis;
377
- white-space: nowrap;
378
- }
379
-
380
- /* Pagination (pagy v43 series-nav) */
381
- nav.pagy {
382
- display: flex;
383
- justify-content: center;
384
- gap: 0.25rem;
385
- padding: 1rem;
386
- list-style: none;
387
- }
388
-
389
- nav.pagy a {
390
- display: inline-flex;
391
- align-items: center;
392
- justify-content: center;
393
- min-width: 32px;
394
- height: 32px;
395
- padding: 0 0.5rem;
396
- border-radius: 5px;
397
- font-size: 13px;
398
- text-decoration: none;
399
- border: 1px solid var(--border);
400
- color: var(--text);
401
- background: var(--surface);
402
- }
403
-
404
- nav.pagy a:hover:not([aria-disabled="true"]) { background: var(--bg); }
405
- nav.pagy a[aria-current="page"] { background: var(--primary); color: #fff; border-color: var(--primary); }
406
- nav.pagy a[role="separator"],
407
- nav.pagy a[aria-disabled="true"] { color: var(--muted); cursor: default; }
408
-
409
- /* Job detail page */
410
- .sqd-breadcrumb {
411
- font-size: 12px;
412
- color: var(--muted);
413
- margin-bottom: 0.25rem;
414
- }
415
-
416
- .sqd-breadcrumb a { color: var(--muted); text-decoration: none; }
417
- .sqd-breadcrumb a:hover { color: var(--text); }
418
-
419
- .sqd-detail-grid {
420
- display: grid;
421
- grid-template-columns: 1fr 1fr;
422
- gap: 1.5rem;
423
- }
424
-
425
- .sqd-grid-2 {
426
- display: grid;
427
- grid-template-columns: 1fr 1fr;
428
- gap: 1rem;
429
- }
430
-
431
- @media (max-width: 768px) {
432
- .sqd-detail-grid { grid-template-columns: 1fr; }
433
- .sqd-grid-2 { grid-template-columns: 1fr; }
434
- }
435
-
436
- @media (max-width: 640px) {
437
- .sqd-main {
438
- padding: 1.5rem 1rem;
439
- }
440
-
441
- .sqd-page-header {
442
- flex-direction: column;
443
- align-items: flex-start;
444
- gap: 0.75rem;
445
- }
446
-
447
- .sqd-card {
448
- overflow-x: auto;
449
- }
450
-
451
- .sqd-card__header {
452
- flex-wrap: wrap;
453
- }
454
-
455
- .sqd-stats {
456
- grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
457
- }
458
-
459
- .sqd-truncate {
460
- max-width: 160px;
461
- }
462
- }
463
-
464
- @media (max-width: 576px) {
465
- .sqd-header {
466
- position: relative;
467
- }
468
-
469
- .sqd-header__inner {
470
- padding: 0 1rem;
471
- }
472
-
473
- .sqd-nav-toggle {
474
- display: flex;
475
- }
476
-
477
- .sqd-nav-wrapper {
478
- display: none;
479
- position: absolute;
480
- top: 100%;
481
- left: 0;
482
- right: 0;
483
- background: var(--surface);
484
- border-bottom: 1px solid var(--border);
485
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
486
- z-index: 50;
487
- padding: 0.5rem;
488
- }
489
-
490
- .sqd-nav-wrapper.sqd-nav--open {
491
- display: block;
492
- }
493
-
494
- .sqd-nav {
495
- flex-direction: column;
496
- gap: 0.25rem;
497
- }
498
-
499
- .sqd-nav a {
500
- padding: 0.5rem 0.75rem;
501
- font-size: 14px;
502
- }
503
- }
504
-
505
- .sqd-detail-section { padding: 1.25rem; }
506
-
507
- .sqd-section-title {
508
- font-size: 13px;
509
- font-weight: 600;
510
- text-transform: uppercase;
511
- letter-spacing: 0.05em;
512
- color: var(--muted);
513
- margin-bottom: 1rem;
514
- }
515
-
516
- .sqd-section-title--danger { color: var(--danger); }
517
-
518
- .sqd-dl {
519
- display: grid;
520
- grid-template-columns: auto 1fr;
521
- gap: 0.5rem 1.5rem;
522
- font-size: 13px;
523
- }
524
-
525
- .sqd-dl dt { color: var(--muted); white-space: nowrap; }
526
- .sqd-dl dd { word-break: break-all; }
527
-
528
- .sqd-pre {
529
- font-family: monospace;
530
- font-size: 12px;
531
- background: var(--bg);
532
- border: 1px solid var(--border);
533
- border-radius: 5px;
534
- padding: 0.75rem;
535
- overflow-x: auto;
536
- white-space: pre-wrap;
537
- word-break: break-word;
538
- max-height: 400px;
539
- overflow-y: auto;
540
- }
541
-
542
- .sqd-pre--muted { color: var(--muted); }
543
-
544
- .sqd-error-header {
545
- font-size: 13px;
546
- padding: 0.5rem 0.75rem;
547
- background: #f8d7da;
548
- color: #842029;
549
- border-radius: 5px;
550
- }
3
+ */
@@ -2,6 +2,8 @@ module SolidQueueWeb
2
2
  class ApplicationController < ActionController::Base
3
3
  include Pagy::Method
4
4
 
5
+ PERIOD_DURATIONS = { "1h" => 1.hour, "24h" => 24.hours, "7d" => 7.days }.freeze
6
+
5
7
  before_action :authenticate!
6
8
 
7
9
  private
@@ -11,6 +11,18 @@ module SolidQueueWeb
11
11
  processes: SolidQueue::Process.count,
12
12
  recurring: SolidQueue::RecurringTask.count
13
13
  }
14
+
15
+ now = Time.current
16
+ finished_times = SolidQueue::Job.where(finished_at: 24.hours.ago..now).pluck(:finished_at)
17
+ @throughput = {
18
+ completed_1h: finished_times.count { |t| t >= 1.hour.ago },
19
+ completed_24h: finished_times.size
20
+ }
21
+ @sparkline = 12.times.map do |i|
22
+ from = (12 - i).hours.ago
23
+ to = i == 11 ? now : (11 - i).hours.ago
24
+ finished_times.count { |t| t >= from && t < to }
25
+ end
14
26
  end
15
27
  end
16
28
  end
@@ -0,0 +1,27 @@
1
+ module SolidQueueWeb
2
+ module FailedJobs
3
+ class SelectionsController < ApplicationController
4
+ def create
5
+ ids = Array(params[:ids]).map(&:to_i).reject(&:zero?)
6
+ executions = SolidQueue::FailedExecution.where(id: ids)
7
+ jobs = executions.includes(:job).map(&:job)
8
+ SolidQueue::FailedExecution.retry_all(jobs)
9
+ redirect_to failed_jobs_path,
10
+ notice: "#{jobs.size} #{"job".pluralize(jobs.size)} queued for retry."
11
+ rescue => e
12
+ redirect_to failed_jobs_path, alert: "Could not retry jobs: #{e.message}"
13
+ end
14
+
15
+ def destroy
16
+ ids = Array(params[:ids]).map(&:to_i).reject(&:zero?)
17
+ executions = SolidQueue::FailedExecution.where(id: ids)
18
+ jobs = executions.includes(:job).map(&:job)
19
+ SolidQueue::FailedExecution.discard_all_from_jobs(jobs)
20
+ redirect_to failed_jobs_path,
21
+ notice: "#{jobs.size} #{"job".pluralize(jobs.size)} discarded."
22
+ rescue => e
23
+ redirect_to failed_jobs_path, alert: "Could not discard jobs: #{e.message}"
24
+ end
25
+ end
26
+ end
27
+ end
@@ -1,6 +1,6 @@
1
1
  module SolidQueueWeb
2
2
  class FailedJobsController < ApplicationController
3
- before_action :set_filter_params, only: [ :index, :retry_all, :discard_all ]
3
+ before_action :set_filter_params, only: [:index, :retry_all, :discard_all]
4
4
 
5
5
  def index
6
6
  @pagy, @failed_jobs = pagy(filtered_scope.order(created_at: :desc))
@@ -25,14 +25,14 @@ module SolidQueueWeb
25
25
  def retry_all
26
26
  jobs = filtered_scope.map(&:job)
27
27
  SolidQueue::FailedExecution.retry_all(jobs)
28
- redirect_to failed_jobs_path(queue: @queue, q: @search),
28
+ redirect_to failed_jobs_path(queue: @queue, q: @search, period: @period),
29
29
  notice: "#{jobs.size} #{"job".pluralize(jobs.size)} queued for retry."
30
30
  end
31
31
 
32
32
  def discard_all
33
33
  jobs = filtered_scope.map(&:job)
34
34
  SolidQueue::FailedExecution.discard_all_from_jobs(jobs)
35
- redirect_to failed_jobs_path(queue: @queue, q: @search),
35
+ redirect_to failed_jobs_path(queue: @queue, q: @search, period: @period),
36
36
  notice: "#{jobs.size} #{"job".pluralize(jobs.size)} discarded."
37
37
  end
38
38
 
@@ -41,12 +41,14 @@ module SolidQueueWeb
41
41
  def set_filter_params
42
42
  @queue = params[:queue].presence
43
43
  @search = params[:q].presence
44
+ @period = params[:period].presence_in(PERIOD_DURATIONS.keys)
44
45
  end
45
46
 
46
47
  def filtered_scope
47
48
  scope = SolidQueue::FailedExecution.includes(:job)
48
49
  scope = scope.references(:job).where(solid_queue_jobs: { queue_name: @queue }) if @queue.present?
49
50
  scope = scope.references(:job).where("solid_queue_jobs.class_name LIKE ?", "%#{@search}%") if @search.present?
51
+ scope = scope.references(:job).where("solid_queue_jobs.created_at >= ?", PERIOD_DURATIONS[@period].ago) if @period.present?
50
52
  scope
51
53
  end
52
54
  end