Roman N
Roman N

Reputation: 5110

How to notify a user after background task is finished?

I use rails with ActiveJob and sidekiq as backend. When user come on a page sidekiq create a long-term background task, how can I notice a user (by render partial on the web page) when a task would be completed?

Rails and sidekiq work as different processes. This fact confused me I don't understand how to handle completed status using background job.

Upvotes: 3

Views: 5051

Answers (2)

Ben Kimball
Ben Kimball

Reputation: 11

My approach in this situation is:

  1. Add sidekiq-status so that background jobs can be tracked by ID.
  2. In the client call that creates the background job, return the newly-created job's ID.

    class MyController < ApplicationController
    
      def create
        # sidekiq-status lets us retrieve a unique job ID when
        # creating a job
        job_id = Workers::MyJob.perform_async(...)
    
        # tell the client where to find the progress of this job
        return :json => {
          :next => "/my/progress?job_id={job_id}"
        }
      end
    
    end
    
  3. Poll a 'progress' endpoint on the server with that job ID. This endpoint fetches job progress information for the job and returns it to the client.

    class MyController < ApplicationController
    
      def progress
        # fetch job status from sidekiq-status
        status = Sidekiq::Status::get_all(params[:job_id])
    
        # in practice, status can be nil if the info has expired from
        # Redis; I'm ignoring that for the purpose of this example
    
        if status["complete"]
          # job is complete; notify the client in some way
          # perhaps by sending it a rendered partial
          payload = {
            :html => render_to_string({
              :partial => "my/job_finished",
              :layout => nil
            })
          }
        else
          # tell client to check back again later
          payload = {:next => "/my/progress?job_id={params[:job_id]}"}
        end
    
        render :json => payload
      end
    
    end
    
  4. If the client sees that the job has completed, it can then display a message or take whatever next step is required.

    var getProgress = function(progress_url, poll_interval) {
      $.get(progress_url).done(function(progress) {
        if(progress.html) {
          // job is complete; show HTML returned by server
          $('#my-container').html(progress.html);
        } else {
          // job is not yet complete, try again later at the URL
          // provided by the server
          setTimeout(function() {
            getProgress(progress.next, poll_interval);
          }, poll_interval);
        }
      });
    };
    $("#my-button").on('click', function(e) {
      $.post("/my").done(function(data) {
        getProgress(data.next, 5000);
      });
      e.preventDefault();
    });
    

Caveat emptor: that code is meant to be illustrative, and is missing things you should take care of such as error handling, preventing duplicate submissions, and so forth.

Upvotes: 1

Nimir
Nimir

Reputation: 5839

ActiveJob provides an after_perform callback which according to docs work like this:

class VideoProcessJob < ActiveJob::Base
  queue_as :default

  after_perform do |job|
    UserMailer.notify_video_processed(job.arguments.first)
  end

  def perform(video_id)
    Video.find(video_id).process
  end
end

So, you don't have to worry to integrate directly with Sidekiq or any other queuing backend, talk to ActiveJob :)

Upvotes: 6

Related Questions