feat!: add ability to cancel pending workspace build by kacpersaw · Pull Request #18713 · coder/coder · GitHub | Latest TMZ Celebrity News & Gossip | Watch TMZ Live
Skip to content

feat!: add ability to cancel pending workspace build #18713

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

Open
wants to merge 13 commits into
base: main
Choose a base branch
from

Conversation

kacpersaw
Copy link
Contributor

Closes #17791

This PR adds ability to cancel workspace builds that are in "pending" status.

Breaking changes:

  • CancelWorkspaceBuild method in codersdk now accepts an optional request parameter

API:

  • Added expect_status query parameter to the cancel workspace build endpoint
  • This parameter ensures the job hasn't changed state before canceling
  • API returns 412 Precondition Failed if the job is not in the expected status
  • Valid values: running or pending
  • Wrapped the entire cancel method in a database transaction

UI:

  • Added confirmation dialog to the Cancel button, since it's a destructive operation
  • Enabled cancel action for pending workspaces (expect_status=pending is sent if workspace is in pending status)

@kacpersaw kacpersaw marked this pull request as ready for review July 2, 2025 13:52
@github-actions github-actions bot added the release/breaking This label is applied to PRs to detect breaking changes as part of the release process label Jul 2, 2025
Copy link
Member

@ethanndickson ethanndickson left a comment

Choose a reason for hiding this comment

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

Looking good, just one comment w/ the transaction

@kacpersaw kacpersaw force-pushed the kacpersaw/cancel-pending-provisioner-jobs branch from 8081f33 to 42170ab Compare July 3, 2025 10:51
@kacpersaw kacpersaw requested a review from BrunoQuaresma July 3, 2025 11:42
Copy link
Member

@deansheather deansheather left a comment

Choose a reason for hiding this comment

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

BE only review

code := http.StatusOK
resp := codersdk.Response{}
err = api.Database.InTx(func(store database.Store) error {
valid, err := verifyUserCanCancelWorkspaceBuilds(ctx, store, httpmw.APIKey(r).UserID, workspace.TemplateID)
Copy link
Member

Choose a reason for hiding this comment

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

We often just use the variable name db in these transaction closures

Comment on lines 610 to 615
if !valid {
code = http.StatusForbidden
resp.Message = "User is not allowed to cancel workspace builds. Owner role is required."

return xerrors.Errorf("user is not allowed to cancel workspace builds")
}
Copy link
Member

Choose a reason for hiding this comment

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

I think we should still allow for cancellations even if the template forbids them as long as the user specified expect_status=pending.

return xerrors.Errorf("user is not allowed to cancel workspace builds")
}

job, err := store.GetProvisionerJobByID(ctx, workspaceBuild.JobID)
Copy link
Member

Choose a reason for hiding this comment

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

This should probably be a clone of this query with a SELECT ... FOR UPDATE query instead, otherwise there's nothing preventing this job from being picked up during the cancel operation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch, I forgot to change that query 🙅

code = http.StatusBadRequest
resp.Message = "Job has already completed!"

return xerrors.Errorf("job has already completed")
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
return xerrors.Errorf("job has already completed")
return xerrors.New("job has already completed")

code = http.StatusBadRequest
resp.Message = "Job has already been marked as canceled!"

return xerrors.Errorf("job has already been marked as canceled")
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
return xerrors.Errorf("job has already been marked as canceled")
return xerrors.New("job has already been marked as canceled")

code = http.StatusBadRequest
resp.Message = "Invalid expect_status. Only 'running' or 'pending' are allowed."

return xerrors.Errorf("invalid expect_status")
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
return xerrors.Errorf("invalid expect_status")
return xerrors.Errorf("invalid expect_status %q", expectStatus)

if expectStatus != "" {
if expectStatus != "running" && expectStatus != "pending" {
code = http.StatusBadRequest
resp.Message = "Invalid expect_status. Only 'running' or 'pending' are allowed."
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
resp.Message = "Invalid expect_status. Only 'running' or 'pending' are allowed."
resp.Message = fmt.Sprintf("Invalid expect_status %q. Only 'running' or 'pending' are allowed.", expectStatus)

code = http.StatusPreconditionFailed
resp.Message = "Job is not in the expected state."

return xerrors.Errorf("job is not in the expected state")
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
return xerrors.Errorf("job is not in the expected state")
return xerrors.Errorf("job is not in the expected state: expected %q, got %q", expectStatus, job.JobStatus)

CancelWorkspaceBuildStatusPending CancelWorkspaceBuildStatus = "pending"
)

type CancelWorkspaceBuildRequest struct {
Copy link
Member

Choose a reason for hiding this comment

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

Maybe call this Params instead of Request since it's not the request body

Copy link
Collaborator

@BrunoQuaresma BrunoQuaresma left a comment

Choose a reason for hiding this comment

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

I left a few comments related to the FE.

Note: When submitting FE related code, it is always nice to include a screenshot or a short video demo showing off the changes. It makes easier for us to visualize things and make faster reviews.

@@ -1277,9 +1277,14 @@ class ApiMethods {

cancelWorkspaceBuild = async (
workspaceBuildId: TypesGen.WorkspaceBuild["id"],
request?: TypesGen.CancelWorkspaceBuildParams,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Since the type has the "Params" sufix, I would name this variable as params instead of request.

): Promise<TypesGen.Response> => {
const params = request?.expect_status
Copy link
Collaborator

Choose a reason for hiding this comment

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

You can do:

const params = new URLSearchParams(params)

const response = await this.axios.patch(
`/api/v2/workspacebuilds/${workspaceBuildId}/cancel`,
`/api/v2/workspacebuilds/${workspaceBuildId}/cancel${params ? `?${params}` : ""}`,
Copy link
Collaborator

Choose a reason for hiding this comment

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

And just use it directly:

`/api/v2/workspacebuilds/${workspaceBuildId}/cancel?${params}`

@@ -266,6 +266,12 @@ export const startWorkspace = (
export const cancelBuild = (workspace: Workspace, queryClient: QueryClient) => {
return {
mutationFn: () => {
// If workspace status is pending, include expect_status parameter
Copy link
Collaborator

Choose a reason for hiding this comment

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

The comment is just saying what the code is doing below. I would rather not having this comment at all, since I can see it in the code, or explaining the why we need to set the expect_status to pending.

@@ -352,6 +354,21 @@ export const WorkspaceReadyPage: FC<WorkspaceReadyPageProps> = ({
}
/>

{/* Cancel confirmation dialog */}
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think we need this comment. The code below is pretty easy to understand.

@@ -497,6 +497,8 @@ const WorkspaceActionsCell: FC<WorkspaceActionsCellProps> = ({

// State for stop confirmation dialog
const [isStopConfirmOpen, setIsStopConfirmOpen] = useState(false);
// State for cancel confirmation dialog
Copy link
Collaborator

Choose a reason for hiding this comment

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

The same here, even seeing we do this for the state above.

@@ -292,6 +292,19 @@ export const BypassRatelimitHeader = "X-Coder-Bypass-Ratelimit";
// From codersdk/client.go
export const CLITelemetryHeader = "Coder-CLI-Telemetry";

// From codersdk/workspacebuilds.go
export interface CancelWorkspaceBuildParams {
readonly expect_status?: CancelWorkspaceBuildStatus;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I can be wrong, but I think when we add a comment to the struct, or its props in codersdk package, it gets included in the TS types as well. I think lefting a comment in the CancelWorkspaceBuildParams.ExpectStatus could be helpful to understand when and why use it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
release/breaking This label is applied to PRs to detect breaking changes as part of the release process
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Ability to cancel Pending provisioner jobs in the UI
4 participants

TMZ Celebrity News – Breaking Stories, Videos & Gossip

Looking for the latest TMZ celebrity news? You've come to the right place. From shocking Hollywood scandals to exclusive videos, TMZ delivers it all in real time.

Whether it’s a red carpet slip-up, a viral paparazzi moment, or a legal drama involving your favorite stars, TMZ news is always first to break the story. Stay in the loop with daily updates, insider tips, and jaw-dropping photos.

🎥 Watch TMZ Live

TMZ Live brings you daily celebrity news and interviews straight from the TMZ newsroom. Don’t miss a beat—watch now and see what’s trending in Hollywood.