Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add deleting jobs from UI. #265

Merged
merged 3 commits into from
Jun 4, 2021
Merged

Conversation

morgoth
Copy link
Collaborator

@morgoth morgoth commented May 28, 2021

refs #256

image

@@ -4,5 +4,10 @@ def show
@jobs = GoodJob::Job.where("serialized_params ->> 'job_id' = ?", params[:id])
.order(Arel.sql("COALESCE(scheduled_at, created_at) DESC"))
end

def destroy
GoodJob::Job.where("serialized_params ->> 'job_id' = ?", params[:id]).delete_all
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used the same lookup as for show page, but why are we using AJ job id here? Wouldn't be easier to just use db id?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When an ActiveJob is retried, it generates a new GoodJob resource. That GJ resource has a new primary ID, but the ActiveJob ID is the same. In other words, a single ActiveJob job's history is represented across multiple GoodJob job entries.

I think you have the implementation right here, by deleting all of the GoodJob resources that have the same ActiveJob ID.

...but it's not pretty. And when thinking about "retry" in the future, we have to make sure that it's operating on the latest/newest/head GoodJob resource for a particular ActiveJob job.

To put it another way, GoodJob::Job resources are "runs".

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also makes me wonder about the safety of having the "delete" on the GJ Job, rather than on the Show page itself to represent that it's deleting all the runs. But this also doesn't have to be perfect.

I think the worry I have is that someone will delete a job that has another run that is currently executing or waiting to execute.

Generative/blue sky idea: delete could be wrapped with an advisory lock on all the matching records.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah right. We have GoodJob.preserve_job_records = :on_unhandled_error so I never seen this.

So I think that for deleting we should use record id to delete only given record, as I believe this is what would be expected, by the principle of least surprise. WDYT?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed it, to remove only one, given job.
I believe to be more REST-ful we could change the show action to be actually the index one with applied filter.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I think that's least surprise to only remove the one job 👍

I want to suggest moving the destroy action into good_jobs#destroy because it is acting upon a good_job record. It could be clearer by having a GoodJobJobsController and an ActiveJobJobsController maybe?

I want to be fairly permissive at this stage because I'm learning both the functionality that people want, and it's also helpful to think through these things with you on actual PRs 😄

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense to not mix both, however what do you think about:

I believe to be more REST-ful we could change the show action to be actually the index one with applied filter.

This way all the actions fit into one controller.
Also currently the "show" action can display many jobs, which might be confusing.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my mind, the active_jobs#show controller is still displaying a single ActiveJob Job, of which the details are multiple GoodJob Jobs.

I am imagining that this page would, in the future, also have some roll-up statistics (when the ActiveJob Job was initially enqueued, whether it has finished running, total number of retries, etc.).

As an end-user, the ActiveJob job is the thing I imagine most users are conceptualizing and the GoodJob Job is the a "run" or "attempt". Unfortunately there isn't a single database record that models the ActiveJob Job yet for the purpose of display.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for explanation. I moved it out to separate controller

@@ -5,4 +5,17 @@
visit '/good_job'
expect(page).to have_content 'GoodJob 👍'
end

# This requires storing job in database, but rest of the tests is using inline adapter
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I add this to config/environments/test.rb the unit tests are failing

  config.active_job.queue_adapter = :good_job
  config.good_job = {execution_mode: :external}

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will look into that.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@morgoth You can add this:

 ActiveJob::Base.queue_adapter = GoodJob::Adapter.new(execution_mode: :external)

The adapter is reset after every test, so that's should be all you need:

Copy link
Owner

@bensheldon bensheldon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fantastic! Thank you.

It does uncover some conceptual issues of how GoodJob jobs related to ActiveJob jobs, but no time like the present to figure it out 😄

@@ -4,5 +4,10 @@ def show
@jobs = GoodJob::Job.where("serialized_params ->> 'job_id' = ?", params[:id])
.order(Arel.sql("COALESCE(scheduled_at, created_at) DESC"))
end

def destroy
GoodJob::Job.where("serialized_params ->> 'job_id' = ?", params[:id]).delete_all
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When an ActiveJob is retried, it generates a new GoodJob resource. That GJ resource has a new primary ID, but the ActiveJob ID is the same. In other words, a single ActiveJob job's history is represented across multiple GoodJob job entries.

I think you have the implementation right here, by deleting all of the GoodJob resources that have the same ActiveJob ID.

...but it's not pretty. And when thinking about "retry" in the future, we have to make sure that it's operating on the latest/newest/head GoodJob resource for a particular ActiveJob job.

To put it another way, GoodJob::Job resources are "runs".

<div class="alert alert-success alert-dismissible fade show d-flex align-items-center offset-md-3 col-6" role="alert">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-check-circle-fill flex-shrink-0 me-2" viewBox="0 0 16 16">
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z" />
</svg>
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can these SVGs be extracted into a shared partial?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you mean separate partial for each SVG?

@@ -5,4 +5,17 @@
visit '/good_job'
expect(page).to have_content 'GoodJob 👍'
end

# This requires storing job in database, but rest of the tests is using inline adapter
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will look into that.

@@ -4,5 +4,10 @@ def show
@jobs = GoodJob::Job.where("serialized_params ->> 'job_id' = ?", params[:id])
.order(Arel.sql("COALESCE(scheduled_at, created_at) DESC"))
end

def destroy
GoodJob::Job.where("serialized_params ->> 'job_id' = ?", params[:id]).delete_all
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also makes me wonder about the safety of having the "delete" on the GJ Job, rather than on the Show page itself to represent that it's deleting all the runs. But this also doesn't have to be perfect.

I think the worry I have is that someone will delete a job that has another run that is currently executing or waiting to execute.

Generative/blue sky idea: delete could be wrapped with an advisory lock on all the matching records.

Copy link
Owner

@bensheldon bensheldon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@bensheldon bensheldon merged commit df35a44 into bensheldon:main Jun 4, 2021
@bensheldon
Copy link
Owner

I just released this as v1.9.6.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants