sidekiq-status 3.0.3 → 4.0.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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/.devcontainer/Dockerfile +2 -0
  3. data/.devcontainer/README.md +57 -0
  4. data/.devcontainer/devcontainer.json +55 -0
  5. data/.devcontainer/docker-compose.yml +19 -0
  6. data/.github/workflows/ci.yaml +9 -6
  7. data/Appraisals +14 -6
  8. data/CHANGELOG.md +12 -0
  9. data/Dockerfile +5 -0
  10. data/README.md +756 -41
  11. data/Rakefile +153 -0
  12. data/docker-compose.yml +15 -0
  13. data/gemfiles/{sidekiq_6.1.gemfile → sidekiq_7.0.gemfile} +1 -1
  14. data/gemfiles/sidekiq_7.3.gemfile +7 -0
  15. data/gemfiles/sidekiq_8.0.gemfile +7 -0
  16. data/gemfiles/{sidekiq_6.x.gemfile → sidekiq_8.x.gemfile} +1 -1
  17. data/lib/sidekiq-status/client_middleware.rb +4 -3
  18. data/lib/sidekiq-status/helpers.rb +94 -0
  19. data/lib/sidekiq-status/server_middleware.rb +6 -21
  20. data/lib/sidekiq-status/storage.rb +12 -3
  21. data/lib/sidekiq-status/version.rb +1 -1
  22. data/lib/sidekiq-status/web.rb +67 -93
  23. data/lib/sidekiq-status/worker.rb +6 -10
  24. data/lib/sidekiq-status.rb +21 -5
  25. data/sidekiq-status.gemspec +7 -1
  26. data/spec/environment.rb +12 -1
  27. data/spec/lib/sidekiq-status/client_middleware_spec.rb +8 -0
  28. data/spec/lib/sidekiq-status/server_middleware_spec.rb +13 -0
  29. data/spec/lib/sidekiq-status/web_spec.rb +72 -3
  30. data/spec/lib/sidekiq-status/worker_spec.rb +3 -3
  31. data/spec/lib/sidekiq-status_spec.rb +20 -3
  32. data/spec/spec_helper.rb +3 -8
  33. data/spec/support/test_jobs.rb +11 -0
  34. data/spec/test_environment.rb +1 -0
  35. data/web/assets/statuses.css +124 -0
  36. data/web/assets/statuses.js +24 -0
  37. data/web/views/status.erb +131 -93
  38. data/web/views/status_not_found.erb +1 -1
  39. data/web/views/statuses.erb +23 -79
  40. metadata +93 -14
data/README.md CHANGED
@@ -4,7 +4,29 @@
4
4
 
5
5
  Sidekiq-status is an extension to [Sidekiq](https://github.com/mperham/sidekiq) that tracks information about your Sidekiq and provides a UI to that purpose. It was inspired by [resque-status](https://github.com/quirkey/resque-status).
6
6
 
7
- Requires Ruby 2.6+ and Sidekiq 6.0+ or newer.
7
+ Supports Ruby 3.2+ and Sidekiq 7.0+ or newer.
8
+
9
+ ## Table of Contents
10
+
11
+ - [Installation](#installation)
12
+ - [Migration Guides](#migration-guides)
13
+ - [Migrating to Version 4.x from 3.x](#migrating-to-version-4x-from-3x)
14
+ - [Migrating to Version 3.x from 2.x](#migrating-to-version-3x-from-2x)
15
+ - [Setup Checklist](#setup-checklist)
16
+ - [Configuration](#configuration)
17
+ - [Expiration Times](#expiration-times)
18
+ - [Retrieving Status](#retrieving-status)
19
+ - [ActiveJob Support](#activejob-support)
20
+ - [Tracking Progress and Storing Data](#tracking-progress-and-storing-data)
21
+ - [Stopping a Running Job](#stopping-a-running-job)
22
+ - [Unscheduling Jobs](#unscheduling)
23
+ - [Deleting Job Status](#deleting-job-status-by-job-id)
24
+ - [Sidekiq Web Integration](#sidekiq-web-integration)
25
+ - [Testing](#testing)
26
+ - [Development Environment](#development-environment)
27
+ - [Testing with Appraisal](#testing-with-appraisal)
28
+ - [Troubleshooting](#troubleshooting)
29
+ - [Contributing](#contributing)
8
30
 
9
31
  ## Installation
10
32
 
@@ -20,6 +42,14 @@ Or install it yourself as:
20
42
  gem install sidekiq-status
21
43
  ```
22
44
 
45
+ ## Migration Guides
46
+
47
+ ### Migrating to Version 4.x from 3.x
48
+
49
+ Version 4.0.0 adds support for Ruby 3.3, 3.4 and Sidekiq 8.x, but drops support for Sidekiq 6.x and Ruby versions that are now end-of-life (specifically, Ruby 2.7.x - Ruby 3.1.x).
50
+
51
+ Version 4.0.0 introduces a breaking change in the way job timestamps are stored in Redis, and also renames `#working_at` to `#updated_at`. Additionally, this version includes major UI improvements with enhanced progress bars and better web interface styling.
52
+
23
53
  ### Migrating to Version 3.x from 2.x
24
54
 
25
55
  Version 3.0.0 adds support for Sidekiq 7.x, but drops support for Sidekiq 5.x. **You should be able to upgrade cleanly from version 2.x to 3.x provided you are running Sidekiq 6.x or newer.**
@@ -109,21 +139,74 @@ The default expiration time is 30 minutes.
109
139
 
110
140
  ### Retrieving Status
111
141
 
112
- You may query for job status any time up to expiration:
142
+ Query for job status at any time up to expiration:
113
143
 
114
- ``` ruby
144
+ ```ruby
115
145
  job_id = MyJob.perform_async(*args)
116
- # :queued, :working, :complete, :failed or :interrupted, nil after expiry (30 minutes)
117
- status = Sidekiq::Status::status(job_id)
118
- Sidekiq::Status::queued? job_id
119
- Sidekiq::Status::working? job_id
120
- Sidekiq::Status::retrying? job_id
121
- Sidekiq::Status::complete? job_id
122
- Sidekiq::Status::failed? job_id
123
- Sidekiq::Status::interrupted? job_id
146
+ ```
147
+
148
+ #### Basic Status Queries
149
+
150
+ ```ruby
151
+ # Get current status as symbol
152
+ status = Sidekiq::Status.status(job_id)
153
+ # Returns: :queued, :working, :retrying, :complete, :failed, :stopped, :interrupted, or nil after expiry
154
+
155
+ # Check specific status with boolean methods
156
+ Sidekiq::Status.queued?(job_id) # true if job is queued
157
+ Sidekiq::Status.working?(job_id) # true if job is currently running
158
+ Sidekiq::Status.retrying?(job_id) # true if job is retrying after failure
159
+ Sidekiq::Status.complete?(job_id) # true if job completed successfully
160
+ Sidekiq::Status.failed?(job_id) # true if job failed permanently
161
+ Sidekiq::Status.interrupted?(job_id) # true if job was interrupted
162
+ Sidekiq::Status.stopped?(job_id) # true if job was manually stopped
163
+ ```
164
+
165
+ #### Progress and Completion
124
166
 
167
+ ```ruby
168
+ # Get progress information
169
+ Sidekiq::Status.at(job_id) # Current progress (e.g., 42)
170
+ Sidekiq::Status.total(job_id) # Total items to process (e.g., 100)
171
+ Sidekiq::Status.pct_complete(job_id) # Percentage complete (e.g., 42)
172
+ Sidekiq::Status.message(job_id) # Current status message
173
+ ```
174
+
175
+ #### Timing Information
176
+
177
+ ```ruby
178
+ # Get timing data (returns Unix timestamps as integers, or nil)
179
+ Sidekiq::Status.enqueued_at(job_id) # When job was enqueued
180
+ Sidekiq::Status.started_at(job_id) # When job started processing
181
+ Sidekiq::Status.updated_at(job_id) # Last update time
182
+ Sidekiq::Status.ended_at(job_id) # When job finished
183
+
184
+ # Estimated time to completion (in seconds, or nil)
185
+ Sidekiq::Status.eta(job_id) # Based on current progress rate
186
+ ```
187
+
188
+ #### Custom Data Retrieval
189
+
190
+ ```ruby
191
+ # Get specific custom field
192
+ Sidekiq::Status.get(job_id, :field_name) # Returns string or nil
193
+
194
+ # Get all job data as hash
195
+ data = Sidekiq::Status.get_all(job_id)
196
+ # Returns: {
197
+ # "status" => "working",
198
+ # "updated_at" => "1640995200",
199
+ # "enqueued_at" => "1640995100",
200
+ # "started_at" => "1640995150",
201
+ # "at" => "42",
202
+ # "total" => "100",
203
+ # "pct_complete" => "42",
204
+ # "message" => "Processing...",
205
+ # "custom_field" => "custom_value"
206
+ # }
125
207
  ```
126
- Important: If you try any of the above status method after the expiration time, the result will be `nil` or `false`.
208
+
209
+ **Important:** All status methods return `nil` or `false` after the expiration time.
127
210
 
128
211
  ### ActiveJob Support
129
212
 
@@ -147,41 +230,166 @@ end
147
230
 
148
231
  ### Tracking Progress and Storing Data
149
232
 
150
- sidekiq-status comes with a feature that allows you to track the progress of a job, as well as store and retrieve any custom data related to a job.
233
+ Sidekiq-status provides comprehensive progress tracking and custom data storage capabilities for jobs that include the `Sidekiq::Status::Worker` module.
151
234
 
152
- ``` ruby
235
+ #### Setting Progress
236
+
237
+ ```ruby
153
238
  class MyJob
154
239
  include Sidekiq::Worker
155
- include Sidekiq::Status::Worker # Important!
240
+ include Sidekiq::Status::Worker # Required for progress tracking
156
241
 
157
242
  def perform(*args)
158
- # your code goes here
243
+ # Set total number of items to process
244
+ total 100
245
+
246
+ # Update progress throughout your job
247
+ (1..100).each do |i|
248
+ # Do some work here...
249
+ sleep 0.1
250
+
251
+ # Update progress with optional message
252
+ at i, "Processing item #{i}"
253
+ # This automatically calculates percentage: i/100 * 100
254
+ end
255
+ end
256
+ end
257
+ ```
159
258
 
160
- # the common idiom to track progress of your task
161
- total 100 # by default
162
- at 5, "Almost done" # 5/100 = 5 % completion
259
+ #### Storing and Retrieving Custom Data
163
260
 
164
- # a way to associate data with your job
165
- store vino: 'veritas'
261
+ ```ruby
262
+ class MyJob
263
+ include Sidekiq::Worker
264
+ include Sidekiq::Status::Worker
166
265
 
167
- # a way of retrieving stored data
168
- # remember that retrieved data is always String|nil
169
- vino = retrieve :vino
266
+ def perform(user_id, options = {})
267
+ # Store custom data associated with this job
268
+ store user_id: user_id
269
+ store options: options.to_json
270
+ store phase: 'initialization'
271
+
272
+ # Store multiple fields at once
273
+ store(
274
+ current_batch: 1,
275
+ batch_size: 50,
276
+ errors_count: 0
277
+ )
278
+
279
+ # Retrieve stored data (always returns String or nil)
280
+ stored_user_id = retrieve(:user_id)
281
+ stored_options = JSON.parse(retrieve(:options) || '{}')
282
+
283
+ # Update progress and custom data together
284
+ 50.times do |i|
285
+ # Do work...
286
+
287
+ # Update progress with custom data
288
+ at i, "Processing batch #{i}"
289
+ store current_item: i, last_processed_at: Time.now.to_s
290
+ end
291
+
292
+ # Mark different phases
293
+ store phase: 'cleanup'
294
+ at 100, "Job completed successfully"
170
295
  end
171
296
  end
172
297
 
173
- job_id = MyJob.perform_async(*args)
174
- data = Sidekiq::Status::get_all job_id
175
- data # => {status: 'complete', update_time: 1360006573, vino: 'veritas'}
176
- Sidekiq::Status::get job_id, :vino #=> 'veritas'
177
- Sidekiq::Status::at job_id #=> 5
178
- Sidekiq::Status::total job_id #=> 100
179
- Sidekiq::Status::message job_id #=> "Almost done"
180
- Sidekiq::Status::pct_complete job_id #=> 5
181
- Sidekiq::Status::working_at job_id #=> 2718
182
- Sidekiq::Status::update_time job_id #=> 2819
298
+ # From outside the job, retrieve custom data
299
+ job_id = MyJob.perform_async(123, { priority: 'high' })
300
+
301
+ # Get specific fields
302
+ user_id = Sidekiq::Status.get(job_id, :user_id) #=> "123"
303
+ phase = Sidekiq::Status.get(job_id, :phase) #=> "cleanup"
304
+ errors = Sidekiq::Status.get(job_id, :errors_count) #=> "0"
305
+
306
+ # Get all job data including progress and custom fields
307
+ all_data = Sidekiq::Status.get_all(job_id)
308
+ puts all_data['phase'] #=> "cleanup"
309
+ puts all_data['current_batch'] #=> "1"
310
+ puts all_data['pct_complete'] #=> "100"
311
+ ```
312
+
313
+ #### Progress Tracking Patterns
314
+
315
+ ```ruby
316
+ class DataImportJob
317
+ include Sidekiq::Worker
318
+ include Sidekiq::Status::Worker
319
+
320
+ def perform(file_path)
321
+ # Example: Processing a CSV file
322
+ csv_data = CSV.read(file_path)
323
+
324
+ # Set total based on data size
325
+ total csv_data.size
326
+
327
+ csv_data.each_with_index do |row, index|
328
+ begin
329
+ # Process the row
330
+ process_row(row)
331
+
332
+ # Update progress
333
+ at index + 1, "Processed row #{index + 1} of #{csv_data.size}"
334
+
335
+ # Store running statistics
336
+ store(
337
+ processed_count: index + 1,
338
+ last_processed_id: row['id'],
339
+ success_rate: calculate_success_rate
340
+ )
341
+
342
+ rescue => e
343
+ # Log error but continue processing
344
+ error_count = (retrieve(:error_count) || '0').to_i + 1
345
+ store error_count: error_count, last_error: e.message
346
+ end
347
+ end
348
+ end
349
+ end
350
+
351
+ # Monitor progress from outside
352
+ job_id = DataImportJob.perform_async('data.csv')
353
+
354
+ # Check progress periodically
355
+ while !Sidekiq::Status.complete?(job_id) && !Sidekiq::Status.failed?(job_id)
356
+ progress = Sidekiq::Status.pct_complete(job_id)
357
+ message = Sidekiq::Status.message(job_id)
358
+ errors = Sidekiq::Status.get(job_id, :error_count) || '0'
359
+
360
+ puts "Progress: #{progress}% - #{message} (#{errors} errors)"
361
+ sleep 1
362
+ end
363
+ ```
364
+
365
+ #### External Progress Updates
366
+
367
+ You can also update job progress from outside the worker:
368
+
369
+ ```ruby
370
+ # Update progress for any job by ID
371
+ job_id = MyJob.perform_async
372
+ Sidekiq::Status.store_for_id(job_id, {
373
+ external_update: Time.now.to_s,
374
+ updated_by: 'external_system'
375
+ })
376
+ ```
377
+
378
+ ### Stopping a running job
379
+
380
+ You can ask a job to stop execution by calling `.stop!` with its job ID. The
381
+ next time the job calls `.at` it will raise
382
+ `Sidekiq::Status::Worker::Stopped`. It will not attempt to retry.
383
+
384
+ ```ruby
385
+ job_id = MyJob.perform_async
386
+ Sidekiq::Status.stop! job_id #=> true
387
+ Sidekiq::Status.status job_id #=> :stopped
183
388
  ```
184
389
 
390
+ Note this will not kill a running job that is stuck. The job must call `.at`
391
+ for it to be stopped in this way.
392
+
185
393
  ### Unscheduling
186
394
 
187
395
  ```ruby
@@ -210,25 +418,80 @@ Sidekiq::Status.delete(bad_job_id) #=> 0
210
418
 
211
419
  ### Sidekiq Web Integration
212
420
 
213
- This gem provides an extension to Sidekiq's web interface with an index at `/statuses`.
421
+ This gem provides a comprehensive extension to Sidekiq's web interface that allows you to monitor job statuses, progress, and custom data in real-time.
422
+
423
+ #### Features
424
+
425
+ - **Job Status Dashboard** at `/statuses` - View all tracked jobs
426
+ - **Individual Job Details** at `/statuses/:job_id` - Detailed job information
427
+ - **Real-time Progress Bars** - Visual progress indicators
428
+ - **Custom Data Display** - View all stored job metadata
429
+ - **Job Control Actions** - Stop, retry, or delete jobs
430
+ - **Responsive Design** - Works on desktop and mobile
431
+ - **Dark Mode Support** - Integrates with Sidekiq's theme
214
432
 
215
433
  ![Sidekiq Status Web](web/sidekiq-status-web.png)
216
434
 
217
- Information for an individual job may be found at `/statuses/:job_id`.
435
+ The main statuses page shows:
436
+ - Job ID and worker class
437
+ - Current status with color coding
438
+ - Progress bar with percentage complete
439
+ - Elapsed time and ETA
440
+ - Last updated timestamp
441
+ - Custom actions (stop, retry, delete)
218
442
 
219
443
  ![Sidekiq Status Web](web/sidekiq-status-single-web.png)
220
444
 
221
- Note: _only jobs that include `Sidekiq::Status::Worker`_ will be reported in the web interface.
445
+ The individual job page provides:
446
+ - Complete job metadata
447
+ - Custom data fields
448
+ - Detailed timing information
449
+ - Full progress history
450
+ - Error messages (if failed)
222
451
 
223
452
  #### Adding the Web Interface
224
453
 
225
- To use, setup the Sidekiq Web interface according to Sidekiq documentation and add the `Sidekiq::Status::Web` require:
454
+ To enable the web interface, require the web module after setting up Sidekiq Web:
226
455
 
227
- ``` ruby
456
+ ```ruby
228
457
  require 'sidekiq/web'
229
458
  require 'sidekiq-status/web'
459
+
460
+ # In Rails, add to config/routes.rb:
461
+ mount Sidekiq::Web => '/sidekiq'
462
+ ```
463
+
464
+ #### Configuration Options
465
+
466
+ Customize the web interface behavior:
467
+
468
+ ```ruby
469
+ # Configure pagination (default: 25 per page)
470
+ Sidekiq::Status::Web.default_per_page = 50
471
+ Sidekiq::Status::Web.per_page_opts = [25, 50, 100, 200]
472
+
473
+ # The web interface will show these options in a dropdown
230
474
  ```
231
475
 
476
+ #### Web Interface Security
477
+
478
+ Since job data may contain sensitive information, secure the web interface:
479
+
480
+ ```ruby
481
+ # Example with HTTP Basic Auth
482
+ Sidekiq::Web.use Rack::Auth::Basic do |username, password|
483
+ ActiveSupport::SecurityUtils.secure_compare(username, ENV['SIDEKIQ_USERNAME']) &&
484
+ ActiveSupport::SecurityUtils.secure_compare(password, ENV['SIDEKIQ_PASSWORD'])
485
+ end
486
+
487
+ # Example with devise (Rails)
488
+ authenticate :user, lambda { |u| u.admin? } do
489
+ mount Sidekiq::Web => '/sidekiq'
490
+ end
491
+ ```
492
+
493
+ **Note:** Only jobs that include `Sidekiq::Status::Worker` will appear in the web interface.
494
+
232
495
  ### Testing
233
496
 
234
497
  Drawing analogy from [sidekiq testing by inlining](https://github.com/mperham/sidekiq/wiki/Testing#testing-workers-inline),
@@ -242,11 +505,463 @@ Inlining example:
242
505
 
243
506
  You can run Sidekiq workers inline in your tests by requiring the `sidekiq/testing/inline` file in your `{test,spec}_helper.rb`:
244
507
 
245
- `require 'sidekiq/testing/inline'`
508
+ ```ruby
509
+ require 'sidekiq/testing/inline'
510
+ ```
246
511
 
247
512
  To use `sidekiq-status` inlining, require it too in your `{test,spec}_helper.rb`:
248
513
 
249
- `require 'sidekiq-status/testing/inline'`
514
+ ```ruby
515
+ require 'sidekiq-status/testing/inline'
516
+ ```
517
+
518
+ ## Troubleshooting
519
+
520
+ ### Common Issues and Solutions
521
+
522
+ #### Job Status Always Returns `nil`
523
+
524
+ **Problem:** `Sidekiq::Status.status(job_id)` returns `nil` even for recent jobs.
525
+
526
+ **Solutions:**
527
+ 1. **Verify middleware configuration:**
528
+ ```ruby
529
+ # Make sure both client and server middleware are configured
530
+ Sidekiq.configure_client do |config|
531
+ Sidekiq::Status.configure_client_middleware config
532
+ end
533
+
534
+ Sidekiq.configure_server do |config|
535
+ Sidekiq::Status.configure_server_middleware config
536
+ Sidekiq::Status.configure_client_middleware config # Also needed in server
537
+ end
538
+ ```
539
+
540
+ 2. **Check if job includes the Worker module:**
541
+ ```ruby
542
+ class MyJob
543
+ include Sidekiq::Worker
544
+ include Sidekiq::Status::Worker # This is required!
545
+ end
546
+ ```
547
+
548
+ 3. **Verify Redis connection:**
549
+ ```ruby
550
+ # Test Redis connectivity
551
+ Sidekiq.redis { |conn| conn.ping } # Should return "PONG"
552
+ ```
553
+
554
+ #### Jobs Not Appearing in Web Interface
555
+
556
+ **Problem:** Jobs are tracked but don't show up in `/sidekiq/statuses`.
557
+
558
+ **Solutions:**
559
+ 1. **Include the web module:**
560
+ ```ruby
561
+ require 'sidekiq/web'
562
+ require 'sidekiq-status/web' # Must be after sidekiq/web
563
+ ```
564
+
565
+ 2. **Check job worker includes status module:**
566
+ ```ruby
567
+ # Only jobs with this module appear in web interface
568
+ include Sidekiq::Status::Worker
569
+ ```
570
+
571
+ 3. **Verify Redis key existence:**
572
+ ```ruby
573
+ # Check if status keys exist in Redis
574
+ Sidekiq.redis do |conn|
575
+ keys = conn.scan(match: 'sidekiq:status:*', count: 100)
576
+ puts "Found #{keys.size} status keys"
577
+ end
578
+ ```
579
+
580
+ #### Progress Not Updating
581
+
582
+ **Problem:** Job progress stays at 0% or doesn't update.
583
+
584
+ **Solutions:**
585
+ 1. **Call `total` before `at`:**
586
+ ```ruby
587
+ def perform
588
+ total 100 # Set total first
589
+ at 1 # Then update progress
590
+ end
591
+ ```
592
+
593
+ 2. **Use numeric values:**
594
+ ```ruby
595
+ # Correct
596
+ at 50, "Halfway done"
597
+
598
+ # Wrong - will not calculate percentage correctly
599
+ at "50", "Halfway done"
600
+ ```
601
+
602
+ 3. **Check for exceptions:**
603
+ ```ruby
604
+ def perform
605
+ total 100
606
+ begin
607
+ at 50
608
+ rescue => e
609
+ puts "Progress update failed: #{e.message}"
610
+ end
611
+ end
612
+ ```
613
+
614
+ #### Memory Usage Growing Over Time
615
+
616
+ **Problem:** Redis memory usage increases continuously.
617
+
618
+ **Solutions:**
619
+ 1. **Set appropriate expiration:**
620
+ ```ruby
621
+ # Configure shorter expiration for high-volume jobs
622
+ Sidekiq::Status.configure_client_middleware config, expiration: 5.minutes.to_i
623
+ ```
624
+
625
+ 2. **Clean up manually if needed:**
626
+ ```ruby
627
+ # Remove old status data
628
+ Sidekiq.redis do |conn|
629
+ old_keys = conn.scan(match: 'sidekiq:status:*').select do |key|
630
+ conn.ttl(key) == -1 # Keys without expiration
631
+ end
632
+ conn.del(*old_keys) unless old_keys.empty?
633
+ end
634
+ ```
635
+
636
+ #### Version Compatibility Issues
637
+
638
+ **Problem:** Errors after upgrading Sidekiq or Ruby versions.
639
+
640
+ **Solutions:**
641
+ 1. **Check version compatibility:**
642
+ ```ruby
643
+ # sidekiq-status 4.x requirements:
644
+ # Ruby 3.2+
645
+ # Sidekiq 7.0+
646
+
647
+ puts "Ruby: #{RUBY_VERSION}"
648
+ puts "Sidekiq: #{Sidekiq::VERSION}"
649
+ ```
650
+
651
+ 2. **Update gemfile constraints:**
652
+ ```ruby
653
+ gem 'sidekiq', '~> 8.0' # Use compatible version
654
+ gem 'sidekiq-status' # Latest version
655
+ ```
656
+
657
+ 3. **Check for breaking changes:**
658
+ - Version 4.x renamed `#working_at` to `#updated_at`
659
+ - Timestamp storage format changed in 4.x
660
+
661
+ #### ActiveJob Integration Issues
662
+
663
+ **Problem:** ActiveJob jobs not being tracked.
664
+
665
+ **Solutions:**
666
+ 1. **Include module in base class:**
667
+ ```ruby
668
+ class ApplicationJob < ActiveJob::Base
669
+ include Sidekiq::Status::Worker # Add to base class
670
+ end
671
+ ```
672
+
673
+ 2. **Verify Sidekiq adapter:**
674
+ ```ruby
675
+ # In config/application.rb or config/environments/production.rb
676
+ config.active_job.queue_adapter = :sidekiq
677
+ ```
678
+
679
+ #### Testing Issues
680
+
681
+ **Problem:** Tests failing with status-related code.
682
+
683
+ **Solutions:**
684
+ 1. **Use testing inline mode:**
685
+ ```ruby
686
+ # In test helper
687
+ require 'sidekiq/testing'
688
+ require 'sidekiq-status/testing/inline'
689
+
690
+ Sidekiq::Testing.inline!
691
+ ```
692
+
693
+ 2. **Mock status calls in tests:**
694
+ ```ruby
695
+ # RSpec example
696
+ allow(Sidekiq::Status).to receive(:status).and_return(:complete)
697
+ allow(Sidekiq::Status).to receive(:pct_complete).and_return(100)
698
+ ```
699
+
700
+ ### Performance Considerations
701
+
702
+ #### High-Volume Job Optimization
703
+
704
+ For applications processing thousands of jobs:
705
+
706
+ ```ruby
707
+ # Use longer expiration to reduce Redis operations
708
+ Sidekiq::Status.configure_client_middleware config, expiration: 24.hours.to_i
709
+
710
+ # Reduce progress update frequency
711
+ class HighVolumeJob
712
+ include Sidekiq::Worker
713
+ include Sidekiq::Status::Worker
714
+
715
+ def perform(items)
716
+ total items.size
717
+
718
+ items.each_with_index do |item, index|
719
+ process_item(item)
720
+
721
+ # Update progress every 100 items instead of every item
722
+ if (index + 1) % 100 == 0
723
+ at index + 1, "Processed #{index + 1} items"
724
+ end
725
+ end
726
+ end
727
+ end
728
+ ```
729
+
730
+ #### Redis Optimization
731
+
732
+ ```ruby
733
+ # Use Redis pipelining for batch operations
734
+ def batch_update_status(job_data)
735
+ Sidekiq.redis do |conn|
736
+ conn.pipelined do |pipeline|
737
+ job_data.each do |job_id, data|
738
+ pipeline.hmset("sidekiq:status:#{job_id}", data.flatten)
739
+ end
740
+ end
741
+ end
742
+ end
743
+ ```
744
+
745
+ ### Getting Help
746
+
747
+ If you're still experiencing issues:
748
+
749
+ 1. **Check the logs:** Look for Redis connection errors or middleware loading issues
750
+ 2. **Enable debug logging:** Add `Sidekiq.logger.level = Logger::DEBUG`
751
+ 3. **Test with minimal example:** Create a simple job to isolate the problem
752
+ 4. **Check GitHub issues:** Search for similar problems
753
+ 5. **Create an issue:** Include Ruby/Sidekiq versions, configuration, and error messages
754
+
755
+ ## Development Environment
756
+
757
+ This project provides multiple ways to set up a consistent development environment with all necessary dependencies.
758
+
759
+ ### Using VS Code Dev Containers (Recommended)
760
+
761
+ The easiest way to get started is using VS Code with the Dev Containers extension:
762
+
763
+ 1. **Prerequisites:**
764
+ - [VS Code](https://code.visualstudio.com/)
765
+ - [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
766
+ - [Docker Desktop](https://www.docker.com/products/docker-desktop)
767
+
768
+ 2. **Setup:**
769
+ ```bash
770
+ git clone https://github.com/kenaniah/sidekiq-status.git
771
+ cd sidekiq-status
772
+ code . # Open in VS Code
773
+ ```
774
+
775
+ 3. **Launch Container:**
776
+ - When prompted, click "Reopen in Container"
777
+ - Or use Command Palette (`Ctrl+Shift+P`): "Dev Containers: Reopen in Container"
778
+
779
+ The devcontainer automatically provides:
780
+ - **Ruby 3.4** with all required gems
781
+ - **Redis 7.4.0** server (auto-started)
782
+ - **VS Code extensions**: Ruby LSP, Endwise, Docker support
783
+ - **Pre-configured environment** with proper PATH and aliases
784
+
785
+ ### Manual Development Setup
786
+
787
+ If you prefer a local setup:
788
+
789
+ 1. **Install Dependencies:**
790
+ ```bash
791
+ # Ruby 3.2+ required
792
+ ruby --version # Verify version
793
+
794
+ # Install Redis (macOS)
795
+ brew install redis
796
+ brew services start redis
797
+
798
+ # Install Redis (Ubuntu/Debian)
799
+ sudo apt-get install redis-server
800
+ sudo systemctl start redis-server
801
+ ```
802
+
803
+ 2. **Clone and Setup:**
804
+ ```bash
805
+ git clone https://github.com/kenaniah/sidekiq-status.git
806
+ cd sidekiq-status
807
+ bundle install
808
+ ```
809
+
810
+ ### Docker Compose Setup
811
+
812
+ For a containerized development environment without VS Code:
813
+
814
+ ```bash
815
+ # Start development environment
816
+ docker compose -f .devcontainer/docker-compose.yml up -d
817
+
818
+ # Enter the container
819
+ docker compose -f .devcontainer/docker-compose.yml exec app bash
820
+
821
+ # Install dependencies
822
+ bundle install
823
+
824
+ # Stop environment
825
+ docker compose -f .devcontainer/docker-compose.yml down
826
+ ```
827
+
828
+ ## Testing with Appraisal
829
+
830
+ This project uses [Appraisal](https://github.com/thoughtbot/appraisal) to ensure compatibility across multiple Sidekiq versions. This is crucial because Sidekiq has breaking changes between major versions.
831
+
832
+ ### Supported Versions
833
+
834
+ Current test matrix includes:
835
+ - **Sidekiq 7.0.x** - Stable release
836
+ - **Sidekiq 7.3.x** - Recent stable
837
+ - **Sidekiq 7.x** - Latest 7.x
838
+ - **Sidekiq 8.0.x** - Latest major version
839
+ - **Sidekiq 8.x** - Bleeding edge
840
+
841
+ ### Appraisal Workflow
842
+
843
+ #### 1. Install All Dependencies
844
+
845
+ ```bash
846
+ # Install base dependencies
847
+ bundle install
848
+
849
+ # Generate and install appraisal gemfiles
850
+ bundle exec appraisal install
851
+ ```
852
+
853
+ This creates version-specific Gemfiles in `gemfiles/` directory:
854
+ ```
855
+ gemfiles/
856
+ ├── sidekiq_7.0.gemfile # Sidekiq ~> 7.0.0
857
+ ├── sidekiq_7.3.gemfile # Sidekiq ~> 7.3.0
858
+ ├── sidekiq_7.x.gemfile # Sidekiq ~> 7
859
+ ├── sidekiq_8.0.gemfile # Sidekiq ~> 8.0.0
860
+ └── sidekiq_8.x.gemfile # Sidekiq ~> 8
861
+ ```
862
+
863
+ #### 2. Running Tests
864
+
865
+ **Test all Sidekiq versions:**
866
+ ```bash
867
+ bundle exec appraisal rake spec
868
+ ```
869
+
870
+ **Test specific version:**
871
+ ```bash
872
+ # Test against Sidekiq 7.0.x
873
+ bundle exec appraisal sidekiq-7.0 rake spec
874
+
875
+ # Test against Sidekiq 7.3.x
876
+ bundle exec appraisal sidekiq-7.3 rake spec
877
+
878
+ # Test against Sidekiq 8.x
879
+ bundle exec appraisal sidekiq-8.x rake spec
880
+ ```
881
+
882
+ **Quick test with current Gemfile:**
883
+ ```bash
884
+ bundle exec rake spec
885
+ # or
886
+ rake spec
887
+ ```
888
+
889
+ #### 3. Interactive Debugging
890
+
891
+ **Start console with specific Sidekiq version:**
892
+ ```bash
893
+ # Debug with Sidekiq 7.0.x dependencies
894
+ bundle exec appraisal sidekiq-7.0 irb
895
+ ```
896
+
897
+ **Run individual test files:**
898
+ ```bash
899
+ # Test specific file with Sidekiq 8.x
900
+ bundle exec appraisal sidekiq-8.x rspec spec/lib/sidekiq-status/worker_spec.rb
901
+
902
+ # Run with verbose output
903
+ bundle exec appraisal sidekiq-8.x rspec spec/lib/sidekiq-status/worker_spec.rb -v
904
+ ```
905
+
906
+ #### 4. Updating Dependencies
907
+
908
+ **Regenerate gemfiles after dependency changes:**
909
+ ```bash
910
+ # Update Appraisals file, then:
911
+ bundle exec appraisal generate
912
+
913
+ # Install new dependencies
914
+ bundle exec appraisal install
915
+ ```
916
+
917
+ **Update specific version:**
918
+ ```bash
919
+ # Update only Sidekiq 7.x dependencies
920
+ bundle exec appraisal sidekiq-7.x bundle update
921
+ ```
922
+
923
+ ### Testing Best Practices
924
+
925
+ #### Running Tests in CI/CD Style
926
+
927
+ ```bash
928
+ # Full test suite (like GitHub Actions)
929
+ bundle exec appraisal install
930
+ bundle exec appraisal rake spec
931
+
932
+ # Check for dependency issues
933
+ bundle exec bundle-audit check --update
934
+ ```
935
+
936
+ ### Common Development Tasks
937
+
938
+ ```bash
939
+ # Start Redis for testing
940
+ redis-server
941
+
942
+ # Run Sidekiq worker with test environment
943
+ bundle exec sidekiq -r ./spec/environment.rb
944
+
945
+ # Start IRB with sidekiq-status loaded
946
+ bundle exec irb -r ./lib/sidekiq-status
947
+
948
+ # Generate test coverage report
949
+ COVERAGE=true bundle exec rake spec
950
+ open coverage/index.html
951
+ ```
952
+
953
+ ### Docker Development Shortcuts
954
+
955
+ ```bash
956
+ # Quick test run using Docker
957
+ docker compose run --rm sidekiq-status bundle exec rake spec
958
+
959
+ # Interactive shell in container
960
+ docker compose run --rm sidekiq-status bash
961
+
962
+ # Test specific Sidekiq version in Docker
963
+ docker compose run --rm sidekiq-status bundle exec appraisal sidekiq-8.x rake spec
964
+ ```
250
965
 
251
966
  ## Contributing
252
967