diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 013c018d5c656..6b38515a74f1a 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -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, diff --git a/site/src/modules/workspaces/WorkspaceMoreActions/UpdateBuildParametersDialogExperimental.tsx b/site/src/modules/workspaces/WorkspaceMoreActions/UpdateBuildParametersDialogExperimental.tsx index 04bb92a5e79b2..850f31185af2c 100644 --- a/site/src/modules/workspaces/WorkspaceMoreActions/UpdateBuildParametersDialogExperimental.tsx +++ b/site/src/modules/workspaces/WorkspaceMoreActions/UpdateBuildParametersDialogExperimental.tsx @@ -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< This template has{" "} - {missedParameters.length} new parameter - {missedParameters.length === 1 ? "" : "s"} + {validations.length} parameter + {validations.length === 1 ? "" : "s"} {" "} that must be configured to complete the update. diff --git a/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx b/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx index 3853af67d394f..19d12ab2a394e 100644 --- a/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx +++ b/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx @@ -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 = ({ /> ) : ( { changeVersionMutation.reset(); }} workspaceOwnerName={workspace.owner_name} workspaceName={workspace.name} templateVersionId={ - changeVersionMutation.error instanceof MissingBuildParameters + changeVersionMutation.error instanceof ParameterValidationError ? changeVersionMutation.error?.versionId : undefined } diff --git a/site/src/modules/workspaces/WorkspaceUpdateDialogs.tsx b/site/src/modules/workspaces/WorkspaceUpdateDialogs.tsx index bdad9e405bd48..2fad94de2da73 100644 --- a/site/src/modules/workspaces/WorkspaceUpdateDialogs.tsx +++ b/site/src/modules/workspaces/WorkspaceUpdateDialogs.tsx @@ -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 = ({ 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 ? ( = ({ /> ) : (