feat: improve workspace upgrade flow when template parameters change (#18917)

This commit is contained in:
ケイラ
2025-07-21 15:42:04 -06:00
committed by GitHub
parent d7b12535db
commit 19afeda98a
4 changed files with 58 additions and 27 deletions
+35 -11
View File
@@ -24,6 +24,7 @@ import type dayjs from "dayjs";
import userAgentParser from "ua-parser-js";
import { OneWayWebSocket } from "../utils/OneWayWebSocket";
import { delay } from "../utils/delay";
import { type FieldError, isApiError } from "./errors";
import type {
DynamicParametersRequest,
PostWorkspaceUsageRequest,
@@ -390,6 +391,15 @@ export class MissingBuildParameters extends Error {
}
}
export class ParameterValidationError extends Error {
constructor(
public readonly versionId: string,
public readonly validations: FieldError[],
) {
super("Parameters are not valid for new template version");
}
}
export type GetProvisionerJobsParams = {
status?: string;
limit?: number;
@@ -1239,7 +1249,6 @@ class ApiMethods {
`/api/v2/workspaces/${workspaceId}/builds`,
data,
);
return response.data;
};
@@ -2268,19 +2277,34 @@ class ApiMethods {
const activeVersionId = template.active_version_id;
let templateParameters: TypesGen.TemplateVersionParameter[] = [];
if (isDynamicParametersEnabled) {
templateParameters = await this.getDynamicParameters(
activeVersionId,
workspace.owner_id,
oldBuildParameters,
);
} else {
templateParameters =
await this.getTemplateVersionRichParameters(activeVersionId);
try {
return await this.postWorkspaceBuild(workspace.id, {
transition: "start",
template_version_id: activeVersionId,
rich_parameter_values: newBuildParameters,
});
} catch (error) {
// If the build failed because of a parameter validation error, then we
// throw a special sentinel error that can be caught by the caller.
if (
isApiError(error) &&
error.response.status === 400 &&
error.response.data.validations &&
error.response.data.validations.length > 0
) {
throw new ParameterValidationError(
activeVersionId,
error.response.data.validations,
);
}
throw error;
}
}
const templateParameters =
await this.getTemplateVersionRichParameters(activeVersionId);
const missingParameters = getMissingParameters(
oldBuildParameters,
newBuildParameters,
@@ -1,4 +1,4 @@
import type { TemplateVersionParameter } from "api/typesGenerated";
import type { FieldError } from "api/errors";
import { Button } from "components/Button/Button";
import {
Dialog,
@@ -14,7 +14,7 @@ import { useNavigate } from "react-router-dom";
type UpdateBuildParametersDialogExperimentalProps = {
open: boolean;
onClose: () => void;
missedParameters: TemplateVersionParameter[];
validations: FieldError[];
workspaceOwnerName: string;
workspaceName: string;
templateVersionId: string | undefined;
@@ -23,7 +23,7 @@ type UpdateBuildParametersDialogExperimentalProps = {
export const UpdateBuildParametersDialogExperimental: FC<
UpdateBuildParametersDialogExperimentalProps
> = ({
missedParameters,
validations,
open,
onClose,
workspaceOwnerName,
@@ -47,8 +47,8 @@ export const UpdateBuildParametersDialogExperimental: FC<
<DialogDescription>
This template has{" "}
<strong className="text-content-primary">
{missedParameters.length} new parameter
{missedParameters.length === 1 ? "" : "s"}
{validations.length} parameter
{validations.length === 1 ? "" : "s"}
</strong>{" "}
that must be configured to complete the update.
</DialogDescription>
@@ -1,4 +1,4 @@
import { MissingBuildParameters } from "api/api";
import { MissingBuildParameters, ParameterValidationError } from "api/api";
import { isApiError } from "api/errors";
import { type ApiError, getErrorMessage } from "api/errors";
import {
@@ -192,19 +192,19 @@ export const WorkspaceMoreActions: FC<WorkspaceMoreActionsProps> = ({
/>
) : (
<UpdateBuildParametersDialogExperimental
missedParameters={
changeVersionMutation.error instanceof MissingBuildParameters
? changeVersionMutation.error.parameters
validations={
changeVersionMutation.error instanceof ParameterValidationError
? changeVersionMutation.error.validations
: []
}
open={changeVersionMutation.error instanceof MissingBuildParameters}
open={changeVersionMutation.error instanceof ParameterValidationError}
onClose={() => {
changeVersionMutation.reset();
}}
workspaceOwnerName={workspace.owner_name}
workspaceName={workspace.name}
templateVersionId={
changeVersionMutation.error instanceof MissingBuildParameters
changeVersionMutation.error instanceof ParameterValidationError
? changeVersionMutation.error?.versionId
: undefined
}
@@ -1,4 +1,4 @@
import { MissingBuildParameters } from "api/api";
import { MissingBuildParameters, ParameterValidationError } from "api/api";
import { updateWorkspace } from "api/queries/workspaces";
import type {
TemplateVersion,
@@ -78,7 +78,10 @@ export const useWorkspaceUpdate = ({
updateWorkspaceMutation.reset();
},
onUpdate: (buildParameters: WorkspaceBuildParameter[]) => {
if (updateWorkspaceMutation.error instanceof MissingBuildParameters) {
if (
updateWorkspaceMutation.error instanceof MissingBuildParameters ||
updateWorkspaceMutation.error instanceof ParameterValidationError
) {
confirmUpdate(buildParameters);
}
},
@@ -154,8 +157,10 @@ const MissingBuildParametersDialog: FC<MissingBuildParametersDialogProps> = ({
const missedParameters =
error instanceof MissingBuildParameters ? error.parameters : [];
const versionId =
error instanceof MissingBuildParameters ? error.versionId : undefined;
const isOpen = error instanceof MissingBuildParameters;
error instanceof ParameterValidationError ? error.versionId : undefined;
const isOpen =
error instanceof MissingBuildParameters ||
error instanceof ParameterValidationError;
return workspace.template_use_classic_parameter_flow ? (
<UpdateBuildParametersDialog
@@ -165,7 +170,9 @@ const MissingBuildParametersDialog: FC<MissingBuildParametersDialogProps> = ({
/>
) : (
<UpdateBuildParametersDialogExperimental
missedParameters={missedParameters}
validations={
error instanceof ParameterValidationError ? error.validations : []
}
open={isOpen}
onClose={dialogProps.onClose}
workspaceOwnerName={workspace.owner_name}