diff --git a/app/controllers/good_job/jobs_controller.rb b/app/controllers/good_job/jobs_controller.rb new file mode 100644 index 00000000000..19cd0a5dbb3 --- /dev/null +++ b/app/controllers/good_job/jobs_controller.rb @@ -0,0 +1,81 @@ +module GoodJob + class JobsController < GoodJob::ApplicationController + rescue_from GoodJob::AdvisoryLockable::RecordAlreadyAdvisoryLockedError, with: :handle_record_already_locked + + before_action :set_job, only: [:show, :destroy, :discard, :retry, :reschedule] + after_action :enable_polling, only: [:index] + + def index + @jobs = Job.all + @jobs = @jobs.where(active_job_id: params[:active_job_id]) if params[:active_job_id].present? + @jobs = @jobs.where(queue_name: params[:queue_name]) if params[:queue_name].present? + @jobs = @jobs.includes_advisory_locks + + respond_to do |format| + format.html + format.json { render json: @jobs } + end + end + + def show + respond_to do |format| + format.html + format.json { render json: @job } + end + end + + def destroy + @job.destroy + respond_to do |format| + format.html { redirect_to good_job_jobs_path, notice: "Job was successfully destroyed." } + format.json { head :no_content } + end + end + + def discard + @job.discard! + respond_to do |format| + format.html { redirect_to good_job_jobs_path, notice: "Job was successfully discarded." } + format.json { head :no_content } + end + end + + def retry + @job.retry! + respond_to do |format| + format.html { redirect_to good_job_jobs_path, notice: "Job was successfully retried." } + format.json { head :no_content } + end + end + + def reschedule + @job.reschedule! + respond_to do |format| + format.html { redirect_to good_job_jobs_path, notice: "Job was successfully rescheduled." } + format.json { head :no_content } + end + end + + private + + def set_job + @job = Job.find(params[:id]) + end + + def enable_polling + @polling_enabled = true + end + + def handle_record_already_locked + respond_to do |format| + format.html { + flash[:alert] = "This job is already being processed or locked." + redirect_to good_job_jobs_path + } + format.json { + render json: { error: "Job is locked" }, status: :conflict + } + end + end + end +end diff --git a/spec/controllers/good_job/jobs_controller_spec.rb b/spec/controllers/good_job/jobs_controller_spec.rb new file mode 100644 index 00000000000..5cfe3155780 --- /dev/null +++ b/spec/controllers/good_job/jobs_controller_spec.rb @@ -0,0 +1,60 @@ +require 'rails_helper' + +RSpec.describe GoodJob::JobsController, type: :controller do + let(:user) { create(:user, :super_admin) } + + before do + sign_in(user) + end + + describe "GET #show" do + let(:job) { create(:good_job_job) } + + context "when the job is available" do + it "returns successful response" do + get :show, params: { id: job.id } + expect(response).to be_successful + end + end + + context "when the job is already advisory locked" do + before do + allow_any_instance_of(GoodJob::Job).to receive(:advisory_locked?).and_return(true) + allow_any_instance_of(GoodJob::Job).to receive(:perform).and_raise(GoodJob::AdvisoryLockable::RecordAlreadyAdvisoryLockedError) + end + + it "redirects with an alert message" do + get :show, params: { id: job.id } + expect(response).to redirect_to(good_job_jobs_path) + expect(flash[:alert]).to eq("This job is already being processed or locked.") + end + + it "returns conflict status for JSON requests" do + get :show, params: { id: job.id }, format: :json + expect(response).to have_http_status(:conflict) + expect(JSON.parse(response.body)).to eq({ "error" => "Job is locked" }) + end + end + end + + describe "POST #retry" do + let(:job) { create(:good_job_job) } + + context "when the job is already advisory locked" do + before do + allow_any_instance_of(GoodJob::Job).to receive(:retry!).and_raise(GoodJob::AdvisoryLockable::RecordAlreadyAdvisoryLockedError) + end + + it "redirects with an alert message" do + post :retry, params: { id: job.id } + expect(response).to redirect_to(good_job_jobs_path) + expect(flash[:alert]).to eq("This job is already being processed or locked.") + end + + it "returns conflict status for JSON requests" do + post :retry, params: { id: job.id }, format: :json + expect(response).to have_http_status(:conflict) + end + end + end +end diff --git a/spec/features/good_job/jobs_spec.rb b/spec/features/good_job/jobs_spec.rb new file mode 100644 index 00000000000..9fe9405dd54 --- /dev/null +++ b/spec/features/good_job/jobs_spec.rb @@ -0,0 +1,33 @@ +require 'rails_helper' + +RSpec.describe "GoodJob Jobs Management", type: :feature do + let(:user) { create(:user, :super_admin) } + + before do + sign_in(user) + end + + describe "handling locked jobs" do + let!(:job) { create(:good_job_job) } + + scenario "attempting to view a locked job" do + allow_any_instance_of(GoodJob::Job).to receive(:advisory_locked?).and_return(true) + allow_any_instance_of(GoodJob::Job).to receive(:perform).and_raise(GoodJob::AdvisoryLockable::RecordAlreadyAdvisoryLockedError) + + visit good_job_job_path(job) + + expect(page).to have_content("This job is already being processed or locked.") + expect(current_path).to eq(good_job_jobs_path) + end + + scenario "attempting to retry a locked job" do + allow_any_instance_of(GoodJob::Job).to receive(:retry!).and_raise(GoodJob::AdvisoryLockable::RecordAlreadyAdvisoryLockedError) + + visit good_job_jobs_path + click_link "Retry" + + expect(page).to have_content("This job is already being processed or locked.") + expect(current_path).to eq(good_job_jobs_path) + end + end +end