-
Notifications
You must be signed in to change notification settings - Fork 928
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
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this 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
8081f33
to
42170ab
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BE only review
coderd/workspacebuilds.go
Outdated
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) |
There was a problem hiding this comment.
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
coderd/workspacebuilds.go
Outdated
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") | ||
} |
There was a problem hiding this comment.
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
.
coderd/workspacebuilds.go
Outdated
return xerrors.Errorf("user is not allowed to cancel workspace builds") | ||
} | ||
|
||
job, err := store.GetProvisionerJobByID(ctx, workspaceBuild.JobID) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 🙅
coderd/workspacebuilds.go
Outdated
code = http.StatusBadRequest | ||
resp.Message = "Job has already completed!" | ||
|
||
return xerrors.Errorf("job has already completed") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return xerrors.Errorf("job has already completed") | |
return xerrors.New("job has already completed") |
coderd/workspacebuilds.go
Outdated
code = http.StatusBadRequest | ||
resp.Message = "Job has already been marked as canceled!" | ||
|
||
return xerrors.Errorf("job has already been marked as canceled") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return xerrors.Errorf("job has already been marked as canceled") | |
return xerrors.New("job has already been marked as canceled") |
coderd/workspacebuilds.go
Outdated
code = http.StatusBadRequest | ||
resp.Message = "Invalid expect_status. Only 'running' or 'pending' are allowed." | ||
|
||
return xerrors.Errorf("invalid expect_status") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return xerrors.Errorf("invalid expect_status") | |
return xerrors.Errorf("invalid expect_status %q", expectStatus) |
coderd/workspacebuilds.go
Outdated
if expectStatus != "" { | ||
if expectStatus != "running" && expectStatus != "pending" { | ||
code = http.StatusBadRequest | ||
resp.Message = "Invalid expect_status. Only 'running' or 'pending' are allowed." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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) |
coderd/workspacebuilds.go
Outdated
code = http.StatusPreconditionFailed | ||
resp.Message = "Job is not in the expected state." | ||
|
||
return xerrors.Errorf("job is not in the expected state") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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) |
codersdk/workspacebuilds.go
Outdated
CancelWorkspaceBuildStatusPending CancelWorkspaceBuildStatus = "pending" | ||
) | ||
|
||
type CancelWorkspaceBuildRequest struct { |
There was a problem hiding this comment.
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
There was a problem hiding this 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, |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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}` : ""}`, |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 */} |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
Closes #17791
This PR adds ability to cancel workspace builds that are in "pending" status.
Breaking changes:
API:
expect_status
query parameter to the cancel workspace build endpoint412 Precondition Failed
if the job is not in the expected statusrunning
orpending
UI:
Cancel
button, since it's a destructive operationexpect_status=pending
is sent if workspace is in pending status)