From 39200b984e4e8124ed2091a04c81c8696ed4af2b Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Wed, 4 Jun 2025 10:22:46 +0000 Subject: [PATCH 01/16] feat: handle update build for dynamic params --- site/src/api/api.ts | 76 +++++++++++++++++-- site/src/api/queries/workspaces.ts | 22 +++++- site/src/components/Dialog/Dialog.tsx | 10 +-- ...pdateBuildParametersDialogExperimental.tsx | 67 ++++++++++++++++ .../WorkspaceMoreActions.tsx | 58 +++++++++----- .../workspaces/WorkspaceUpdateDialogs.tsx | 35 +++++++-- ...orkspaceParametersPageViewExperimental.tsx | 18 ++++- .../pages/WorkspacesPage/WorkspacesPage.tsx | 7 +- .../src/pages/WorkspacesPage/batchActions.tsx | 8 +- 9 files changed, 257 insertions(+), 44 deletions(-) create mode 100644 site/src/modules/workspaces/WorkspaceMoreActions/UpdateBuildParametersDialogExperimental.tsx diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 5463ad7a44dd6..b0cf8a0b9a47b 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -24,7 +24,10 @@ import type dayjs from "dayjs"; import userAgentParser from "ua-parser-js"; import { OneWayWebSocket } from "../utils/OneWayWebSocket"; import { delay } from "../utils/delay"; -import type { PostWorkspaceUsageRequest } from "./typesGenerated"; +import type { + DynamicParametersRequest, + PostWorkspaceUsageRequest, +} from "./typesGenerated"; import * as TypesGen from "./typesGenerated"; const getMissingParameters = ( @@ -990,6 +993,17 @@ class ApiMethods { return response.data; }; + getTemplateVersionDynamicParameters = async ( + versionId: string, + data: TypesGen.DynamicParametersRequest, + ): Promise => { + const response = await this.axios.post( + `/api/v2/templateversions/${versionId}/dynamic-parameters/evaluate`, + data, + ); + return response.data; + }; + getTemplateVersionRichParameters = async ( versionId: string, ): Promise => { @@ -2132,6 +2146,32 @@ class ApiMethods { await this.axios.delete(`/api/v2/licenses/${licenseId}`); }; + getDynamicParameters = async (templateVersionId: string, ownerId: string) => { + const request: DynamicParametersRequest = { + id: 1, + owner_id: ownerId, + inputs: {}, + }; + + const dynamicParametersResponse = + await this.getTemplateVersionDynamicParameters( + templateVersionId, + request, + ); + + return dynamicParametersResponse.parameters.map((p) => ({ + ...p, + description_plaintext: p.description || "", + default_value: p.default_value?.valid ? p.default_value.value : "", + options: p.options + ? p.options.map((opt) => ({ + ...opt, + value: opt.value?.valid ? opt.value.value : "", + })) + : [], + })); + }; + /** Steps to change the workspace version * - Get the latest template to access the latest active version * - Get the current build parameters @@ -2145,11 +2185,22 @@ class ApiMethods { workspace: TypesGen.Workspace, templateVersionId: string, newBuildParameters: TypesGen.WorkspaceBuildParameter[] = [], + isDynamicParametersEnabled = false, ): Promise => { - const [currentBuildParameters, templateParameters] = await Promise.all([ - this.getWorkspaceBuildParameters(workspace.latest_build.id), - this.getTemplateVersionRichParameters(templateVersionId), - ]); + const currentBuildParameters = await this.getWorkspaceBuildParameters( + workspace.latest_build.id, + ); + + let templateParameters: TypesGen.TemplateVersionParameter[] = []; + if (isDynamicParametersEnabled) { + templateParameters = await this.getDynamicParameters( + templateVersionId, + workspace.owner_id, + ); + } else { + templateParameters = + await this.getTemplateVersionRichParameters(templateVersionId); + } const missingParameters = getMissingParameters( currentBuildParameters, @@ -2180,6 +2231,7 @@ class ApiMethods { updateWorkspace = async ( workspace: TypesGen.Workspace, newBuildParameters: TypesGen.WorkspaceBuildParameter[] = [], + isDynamicParametersEnabled = false, ): Promise => { const [template, oldBuildParameters] = await Promise.all([ this.getTemplate(workspace.template_id), @@ -2187,8 +2239,18 @@ class ApiMethods { ]); const activeVersionId = template.active_version_id; - const templateParameters = - await this.getTemplateVersionRichParameters(activeVersionId); + + let templateParameters: TypesGen.TemplateVersionParameter[] = []; + + if (isDynamicParametersEnabled) { + templateParameters = await this.getDynamicParameters( + activeVersionId, + workspace.owner_id, + ); + } else { + templateParameters = + await this.getTemplateVersionRichParameters(activeVersionId); + } const missingParameters = getMissingParameters( oldBuildParameters, diff --git a/site/src/api/queries/workspaces.ts b/site/src/api/queries/workspaces.ts index 6c6a1aa19825c..5a4cdb46dd4e9 100644 --- a/site/src/api/queries/workspaces.ts +++ b/site/src/api/queries/workspaces.ts @@ -163,6 +163,7 @@ export const updateDeadline = ( export const changeVersion = ( workspace: Workspace, queryClient: QueryClient, + isDynamicParametersEnabled: boolean, ) => { return { mutationFn: ({ @@ -172,7 +173,12 @@ export const changeVersion = ( versionId: string; buildParameters?: WorkspaceBuildParameter[]; }) => { - return API.changeWorkspaceVersion(workspace, versionId, buildParameters); + return API.changeWorkspaceVersion( + workspace, + versionId, + buildParameters, + isDynamicParametersEnabled, + ); }, onSuccess: async (build: WorkspaceBuild) => { await updateWorkspaceBuild(build, queryClient); @@ -185,8 +191,18 @@ export const updateWorkspace = ( queryClient: QueryClient, ) => { return { - mutationFn: (buildParameters?: WorkspaceBuildParameter[]) => { - return API.updateWorkspace(workspace, buildParameters); + mutationFn: ({ + buildParameters, + isDynamicParametersEnabled, + }: { + buildParameters?: WorkspaceBuildParameter[]; + isDynamicParametersEnabled: boolean; + }) => { + return API.updateWorkspace( + workspace, + buildParameters, + isDynamicParametersEnabled, + ); }, onSuccess: async (build: WorkspaceBuild) => { await updateWorkspaceBuild(build, queryClient); diff --git a/site/src/components/Dialog/Dialog.tsx b/site/src/components/Dialog/Dialog.tsx index 7dbd536204254..a71e00bf4da50 100644 --- a/site/src/components/Dialog/Dialog.tsx +++ b/site/src/components/Dialog/Dialog.tsx @@ -16,9 +16,9 @@ export const Dialog = DialogPrimitive.Root; export const DialogTrigger = DialogPrimitive.Trigger; -const DialogPortal = DialogPrimitive.Portal; +export const DialogPortal = DialogPrimitive.Portal; -const DialogClose = DialogPrimitive.Close; +export const DialogClose = DialogPrimitive.Close; const DialogOverlay = forwardRef< ElementRef, @@ -45,7 +45,7 @@ export const DialogContent = forwardRef< > = ({ }) => (
(({ className, ...props }, ref) => ( )); diff --git a/site/src/modules/workspaces/WorkspaceMoreActions/UpdateBuildParametersDialogExperimental.tsx b/site/src/modules/workspaces/WorkspaceMoreActions/UpdateBuildParametersDialogExperimental.tsx new file mode 100644 index 0000000000000..212df5b6c48ec --- /dev/null +++ b/site/src/modules/workspaces/WorkspaceMoreActions/UpdateBuildParametersDialogExperimental.tsx @@ -0,0 +1,67 @@ +import type { TemplateVersionParameter } from "api/typesGenerated"; +import { Button } from "components/Button/Button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "components/Dialog/Dialog"; +import type { FC } from "react"; +import { useNavigate } from "react-router-dom"; + +type UpdateBuildParametersDialogExperimentalProps = { + open: boolean; + onClose: () => void; + missedParameters: TemplateVersionParameter[]; + workspaceOwnerName: string; + workspaceName: string; +}; + +export const UpdateBuildParametersDialogExperimental: FC< + UpdateBuildParametersDialogExperimentalProps +> = ({ + missedParameters, + open, + onClose, + workspaceOwnerName, + workspaceName, +}) => { + const navigate = useNavigate(); + + const handleGoToParameters = () => { + onClose(); + navigate(`/@${workspaceOwnerName}/${workspaceName}/settings/parameters`); + }; + + return ( + !isOpen && onClose()}> + + + Update workspace parameters + + This template has{" "} + + {missedParameters.length} new parameter + {missedParameters.length === 1 ? "" : "s"} + {" "} + that must be configured to complete the update. + + + Would you like to go to the workspace parameters page to review and + update these parameters before continuing? + + + + + + + + + ); +}; diff --git a/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx b/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx index 22e9638ee7caa..38f202428e9fb 100644 --- a/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx +++ b/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx @@ -21,12 +21,14 @@ import { SettingsIcon, TrashIcon, } from "lucide-react"; +import { useDashboard } from "modules/dashboard/useDashboard"; import { type FC, useEffect, useState } from "react"; import { useMutation, useQuery, useQueryClient } from "react-query"; import { Link as RouterLink } from "react-router-dom"; import { ChangeWorkspaceVersionDialog } from "./ChangeWorkspaceVersionDialog"; import { DownloadLogsDialog } from "./DownloadLogsDialog"; import { UpdateBuildParametersDialog } from "./UpdateBuildParametersDialog"; +import { UpdateBuildParametersDialogExperimental } from "./UpdateBuildParametersDialogExperimental"; import { WorkspaceDeleteDialog } from "./WorkspaceDeleteDialog"; import { useWorkspaceDuplication } from "./useWorkspaceDuplication"; @@ -40,6 +42,8 @@ export const WorkspaceMoreActions: FC = ({ disabled, }) => { const queryClient = useQueryClient(); + const { experiments } = useDashboard(); + const isDynamicParametersEnabled = experiments.includes("dynamic-parameters"); // Permissions const { data: permissions } = useQuery(workspacePermissions(workspace)); @@ -50,7 +54,7 @@ export const WorkspaceMoreActions: FC = ({ // Change version const [changeVersionDialogOpen, setChangeVersionDialogOpen] = useState(false); const changeVersionMutation = useMutation( - changeVersion(workspace, queryClient), + changeVersion(workspace, queryClient, isDynamicParametersEnabled), ); // Delete @@ -142,25 +146,41 @@ export const WorkspaceMoreActions: FC = ({ onClose={() => setIsDownloadDialogOpen(false)} /> - { - changeVersionMutation.reset(); - }} - onUpdate={(buildParameters) => { - if (changeVersionMutation.error instanceof MissingBuildParameters) { - changeVersionMutation.mutate({ - versionId: changeVersionMutation.error.versionId, - buildParameters, - }); + {isDynamicParametersEnabled ? ( + + open={changeVersionMutation.error instanceof MissingBuildParameters} + onClose={() => { + changeVersionMutation.reset(); + }} + workspaceOwnerName={workspace.owner_name} + workspaceName={workspace.name} + /> + ) : ( + { + changeVersionMutation.reset(); + }} + onUpdate={(buildParameters) => { + if (changeVersionMutation.error instanceof MissingBuildParameters) { + changeVersionMutation.mutate({ + versionId: changeVersionMutation.error.versionId, + buildParameters, + }); + } + }} + /> + )} { + const { experiments } = useDashboard(); + const isDynamicParametersEnabled = experiments.includes("dynamic-parameters"); const queryClient = useQueryClient(); const [isConfirmingUpdate, setIsConfirmingUpdate] = useState(false); @@ -52,7 +56,10 @@ export const useWorkspaceUpdate = ({ }; const confirmUpdate = (buildParameters: WorkspaceBuildParameter[] = []) => { - updateWorkspaceMutation.mutate(buildParameters); + updateWorkspaceMutation.mutate({ + buildParameters, + isDynamicParametersEnabled, + }); setIsConfirmingUpdate(false); }; @@ -67,6 +74,7 @@ export const useWorkspaceUpdate = ({ latestVersion, }, missingBuildParameters: { + workspace, error: updateWorkspaceMutation.error, onClose: () => { updateWorkspaceMutation.reset(); @@ -134,21 +142,36 @@ const UpdateConfirmationDialog: FC = ({ }; type MissingBuildParametersDialogProps = { + workspace: Workspace; error: unknown; onClose: () => void; onUpdate: (buildParameters: WorkspaceBuildParameter[]) => void; }; const MissingBuildParametersDialog: FC = ({ + workspace, error, ...dialogProps }) => { - return ( + const { experiments } = useDashboard(); + const isDynamicParametersEnabled = experiments.includes("dynamic-parameters"); + + const missedParameters = + error instanceof MissingBuildParameters ? error.parameters : []; + const isOpen = error instanceof MissingBuildParameters; + + return isDynamicParametersEnabled ? ( + + ) : ( ); diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageViewExperimental.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageViewExperimental.tsx index 4fe9d19c956d1..2725fad381f3a 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageViewExperimental.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageViewExperimental.tsx @@ -5,6 +5,8 @@ import type { } from "api/typesGenerated"; import { Alert } from "components/Alert/Alert"; import { Button } from "components/Button/Button"; +import { Input } from "components/Input/Input"; +import { Label } from "components/Label/Label"; import { Link } from "components/Link/Link"; import { Spinner } from "components/Spinner/Spinner"; import { useFormik } from "formik"; @@ -14,7 +16,7 @@ import { getInitialParameterValues, useValidationSchemaForDynamicParameters, } from "modules/workspaces/DynamicParameter/DynamicParameter"; -import type { FC } from "react"; +import { type FC, useId } from "react"; import { docs } from "utils/docs"; import type { AutofillBuildParameter } from "utils/richParameters"; @@ -45,6 +47,7 @@ export const WorkspaceParametersPageViewExperimental: FC< sendMessage, onCancel, }) => { + const id = useId(); const autofillByName = Object.fromEntries( autofillParameters.map((param) => [param.name, param]), ); @@ -152,6 +155,19 @@ export const WorkspaceParametersPageViewExperimental: FC<
)} + {workspace.template_active_version_id && ( +
+ + +
+ )} +
{standardParameters.length > 0 && (
diff --git a/site/src/pages/WorkspacesPage/WorkspacesPage.tsx b/site/src/pages/WorkspacesPage/WorkspacesPage.tsx index b6309638e209d..acdded15d4bc9 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPage.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPage.tsx @@ -37,6 +37,8 @@ function useSafeSearchParams() { } const WorkspacesPage: FC = () => { + const { experiments } = useDashboard(); + const isDynamicParametersEnabled = experiments.includes("dynamic-parameters"); const queryClient = useQueryClient(); // If we use a useSearchParams for each hook, the values will not be in sync. // So we have to use a single one, centralizing the values, and pass it to @@ -162,7 +164,10 @@ const WorkspacesPage: FC = () => { checkedWorkspaces={checkedWorkspaces} open={confirmingBatchAction === "update"} onConfirm={async () => { - await batchActions.updateAll(checkedWorkspaces); + await batchActions.updateAll({ + workspaces: checkedWorkspaces, + isDynamicParametersEnabled, + }); setConfirmingBatchAction(null); }} onClose={() => { diff --git a/site/src/pages/WorkspacesPage/batchActions.tsx b/site/src/pages/WorkspacesPage/batchActions.tsx index cbec017eb8583..806c7a03afddb 100644 --- a/site/src/pages/WorkspacesPage/batchActions.tsx +++ b/site/src/pages/WorkspacesPage/batchActions.tsx @@ -45,11 +45,15 @@ export function useBatchActions(options: UseBatchActionsProps) { }); const updateAllMutation = useMutation({ - mutationFn: (workspaces: readonly Workspace[]) => { + mutationFn: (payload: { + workspaces: readonly Workspace[]; + isDynamicParametersEnabled: boolean; + }) => { + const { workspaces, isDynamicParametersEnabled } = payload; return Promise.all( workspaces .filter((w) => w.outdated && !w.dormant_at) - .map((w) => API.updateWorkspace(w)), + .map((w) => API.updateWorkspace(w, [], isDynamicParametersEnabled)), ); }, onSuccess, From e7d38cfcca8ed9fcb43308664246d95f44999952 Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Wed, 4 Jun 2025 16:23:13 +0000 Subject: [PATCH 02/16] feat: pass template version id to workspace parameters page --- ...pdateBuildParametersDialogExperimental.tsx | 6 ++- .../WorkspaceMoreActions.tsx | 5 +++ .../workspaces/WorkspaceUpdateDialogs.tsx | 3 ++ .../WorkspaceParametersPageExperimental.tsx | 17 +++++++-- ...orkspaceParametersPageViewExperimental.tsx | 37 +++++++++++-------- 5 files changed, 48 insertions(+), 20 deletions(-) diff --git a/site/src/modules/workspaces/WorkspaceMoreActions/UpdateBuildParametersDialogExperimental.tsx b/site/src/modules/workspaces/WorkspaceMoreActions/UpdateBuildParametersDialogExperimental.tsx index 212df5b6c48ec..04bb92a5e79b2 100644 --- a/site/src/modules/workspaces/WorkspaceMoreActions/UpdateBuildParametersDialogExperimental.tsx +++ b/site/src/modules/workspaces/WorkspaceMoreActions/UpdateBuildParametersDialogExperimental.tsx @@ -17,6 +17,7 @@ type UpdateBuildParametersDialogExperimentalProps = { missedParameters: TemplateVersionParameter[]; workspaceOwnerName: string; workspaceName: string; + templateVersionId: string | undefined; }; export const UpdateBuildParametersDialogExperimental: FC< @@ -27,12 +28,15 @@ export const UpdateBuildParametersDialogExperimental: FC< onClose, workspaceOwnerName, workspaceName, + templateVersionId, }) => { const navigate = useNavigate(); const handleGoToParameters = () => { onClose(); - navigate(`/@${workspaceOwnerName}/${workspaceName}/settings/parameters`); + navigate( + `/@${workspaceOwnerName}/${workspaceName}/settings/parameters?templateVersionId=${templateVersionId}`, + ); }; return ( diff --git a/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx b/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx index 38f202428e9fb..83a38d379c6df 100644 --- a/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx +++ b/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx @@ -159,6 +159,11 @@ export const WorkspaceMoreActions: FC = ({ }} workspaceOwnerName={workspace.owner_name} workspaceName={workspace.name} + templateVersionId={ + changeVersionMutation.error instanceof MissingBuildParameters + ? changeVersionMutation.error?.versionId + : undefined + } /> ) : ( = ({ const missedParameters = error instanceof MissingBuildParameters ? error.parameters : []; + const versionId = + error instanceof MissingBuildParameters ? error.versionId : undefined; const isOpen = error instanceof MissingBuildParameters; return isDynamicParametersEnabled ? ( @@ -167,6 +169,7 @@ const MissingBuildParametersDialog: FC = ({ onClose={dialogProps.onClose} workspaceOwnerName={workspace.owner_name} workspaceName={workspace.name} + templateVersionId={versionId} /> ) : ( { const workspace = useWorkspaceSettings(); const navigate = useNavigate(); const experimentalFormContext = useContext(ExperimentalFormContext); + const [searchParams] = useSearchParams(); + const templateVersionId = searchParams.get("templateVersionId") ?? undefined; // autofill the form with the workspace build parameters from the latest build const { @@ -107,10 +109,11 @@ const WorkspaceParametersPageExperimental: FC = () => { }); useEffect(() => { - if (!workspace.latest_build.template_version_id) return; + if (!templateVersionId && !workspace.latest_build.template_version_id) + return; const socket = API.templateVersionDynamicParameters( - workspace.latest_build.template_version_id, + templateVersionId ?? workspace.latest_build.template_version_id, { onMessage, onError: (error) => { @@ -136,12 +139,17 @@ const WorkspaceParametersPageExperimental: FC = () => { return () => { socket.close(); }; - }, [workspace.latest_build.template_version_id, onMessage]); + }, [ + templateVersionId, + workspace.latest_build.template_version_id, + onMessage, + ]); const updateParameters = useMutation({ mutationFn: (buildParameters: WorkspaceBuildParameter[]) => API.postWorkspaceBuild(workspace.id, { transition: "start", + template_version_id: templateVersionId, rich_parameter_values: buildParameters, }), onSuccess: () => { @@ -250,6 +258,7 @@ const WorkspaceParametersPageExperimental: FC = () => { {sortedParams.length > 0 ? ( void; sendMessage: (formValues: Record) => void; + templateVersionId: string | undefined; }; export const WorkspaceParametersPageViewExperimental: FC< @@ -46,8 +46,8 @@ export const WorkspaceParametersPageViewExperimental: FC< onSubmit, sendMessage, onCancel, + templateVersionId, }) => { - const id = useId(); const autofillByName = Object.fromEntries( autofillParameters.map((param) => [param.name, param]), ); @@ -155,16 +155,12 @@ export const WorkspaceParametersPageViewExperimental: FC< )} - {workspace.template_active_version_id && ( -
- - + {(templateVersionId || workspace.template_active_version_id) && ( +
+ +

+ {templateVersionId ?? workspace.template_active_version_id} +

)} @@ -252,10 +248,21 @@ export const WorkspaceParametersPageViewExperimental: FC<
From 561a0ebb069d1dc5bcd103ea6f048bb144e4a9ac Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Wed, 4 Jun 2025 22:56:48 +0000 Subject: [PATCH 03/16] feat: check opt-in per template --- site/src/hooks/useDynamicParametersOptOut.ts | 42 ++++++++++++++++ .../WorkspaceMoreActions.tsx | 50 ++++++++++++------- .../workspaces/WorkspaceUpdateDialogs.tsx | 44 ++++++++++++---- .../CreateWorkspaceExperimentRouter.tsx | 39 ++++++--------- .../WorkspaceParametersExperimentRouter.tsx | 45 ++++------------- ...orkspaceParametersPageViewExperimental.tsx | 4 +- 6 files changed, 134 insertions(+), 90 deletions(-) create mode 100644 site/src/hooks/useDynamicParametersOptOut.ts diff --git a/site/src/hooks/useDynamicParametersOptOut.ts b/site/src/hooks/useDynamicParametersOptOut.ts new file mode 100644 index 0000000000000..6401f5f7f3564 --- /dev/null +++ b/site/src/hooks/useDynamicParametersOptOut.ts @@ -0,0 +1,42 @@ +import { useQuery } from "react-query"; + +export const optOutKey = (id: string): string => `parameters.${id}.optOut`; + +interface UseDynamicParametersOptOutOptions { + templateId: string | undefined; + templateUsesClassicParameters: boolean | undefined; + enabled: boolean; +} + +export const useDynamicParametersOptOut = ({ + templateId, + templateUsesClassicParameters, + enabled, +}: UseDynamicParametersOptOutOptions) => { + return useQuery({ + enabled: !!templateId && enabled, + queryKey: ["dynamicParametersOptOut", templateId], + queryFn: () => { + if (!templateId) { + // This should not happen if enabled is working correctly, + // but as a type guard and sanity check. + throw new Error("templateId is required"); + } + const localStorageKey = optOutKey(templateId); + const storedOptOutString = localStorage.getItem(localStorageKey); + + let optedOut: boolean; + + if (storedOptOutString !== null) { + optedOut = storedOptOutString === "true"; + } else { + optedOut = Boolean(templateUsesClassicParameters); + } + + return { + templateId, + optedOut, + }; + }, + }); +}; diff --git a/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx b/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx index 83a38d379c6df..b6d55db9bc413 100644 --- a/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx +++ b/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx @@ -31,6 +31,7 @@ import { UpdateBuildParametersDialog } from "./UpdateBuildParametersDialog"; import { UpdateBuildParametersDialogExperimental } from "./UpdateBuildParametersDialogExperimental"; import { WorkspaceDeleteDialog } from "./WorkspaceDeleteDialog"; import { useWorkspaceDuplication } from "./useWorkspaceDuplication"; +import { useDynamicParametersOptOut } from "hooks/useDynamicParametersOptOut"; type WorkspaceMoreActionsProps = { workspace: Workspace; @@ -45,6 +46,13 @@ export const WorkspaceMoreActions: FC = ({ const { experiments } = useDashboard(); const isDynamicParametersEnabled = experiments.includes("dynamic-parameters"); + const optOutQuery = useDynamicParametersOptOut({ + templateId: workspace.template_id, + templateUsesClassicParameters: + workspace.template_use_classic_parameter_flow, + enabled: isDynamicParametersEnabled, + }); + // Permissions const { data: permissions } = useQuery(workspacePermissions(workspace)); @@ -54,7 +62,11 @@ export const WorkspaceMoreActions: FC = ({ // Change version const [changeVersionDialogOpen, setChangeVersionDialogOpen] = useState(false); const changeVersionMutation = useMutation( - changeVersion(workspace, queryClient, isDynamicParametersEnabled), + changeVersion( + workspace, + queryClient, + Boolean(!optOutQuery.data?.optedOut) ?? false, + ), ); // Delete @@ -146,8 +158,8 @@ export const WorkspaceMoreActions: FC = ({ onClose={() => setIsDownloadDialogOpen(false)} /> - {isDynamicParametersEnabled ? ( - = ({ onClose={() => { changeVersionMutation.reset(); }} - workspaceOwnerName={workspace.owner_name} - workspaceName={workspace.name} - templateVersionId={ - changeVersionMutation.error instanceof MissingBuildParameters - ? changeVersionMutation.error?.versionId - : undefined - } + onUpdate={(buildParameters) => { + if (changeVersionMutation.error instanceof MissingBuildParameters) { + changeVersionMutation.mutate({ + versionId: changeVersionMutation.error.versionId, + buildParameters, + }); + } + }} /> ) : ( - = ({ onClose={() => { changeVersionMutation.reset(); }} - onUpdate={(buildParameters) => { - if (changeVersionMutation.error instanceof MissingBuildParameters) { - changeVersionMutation.mutate({ - versionId: changeVersionMutation.error.versionId, - buildParameters, - }); - } - }} + workspaceOwnerName={workspace.owner_name} + workspaceName={workspace.name} + templateVersionId={ + changeVersionMutation.error instanceof MissingBuildParameters + ? changeVersionMutation.error?.versionId + : undefined + } /> )} diff --git a/site/src/modules/workspaces/WorkspaceUpdateDialogs.tsx b/site/src/modules/workspaces/WorkspaceUpdateDialogs.tsx index 36f1315d164b3..e28f27b36db13 100644 --- a/site/src/modules/workspaces/WorkspaceUpdateDialogs.tsx +++ b/site/src/modules/workspaces/WorkspaceUpdateDialogs.tsx @@ -13,6 +13,9 @@ import { UpdateBuildParametersDialog } from "modules/workspaces/WorkspaceMoreAct import { UpdateBuildParametersDialogExperimental } from "modules/workspaces/WorkspaceMoreActions/UpdateBuildParametersDialogExperimental"; import { type FC, useState } from "react"; import { useMutation, useQueryClient } from "react-query"; +import { useDynamicParametersOptOut } from "hooks/useDynamicParametersOptOut"; +import { ErrorAlert } from "components/Alert/ErrorAlert"; +import { Loader } from "components/Loader/Loader"; type UseWorkspaceUpdateOptions = { workspace: Workspace; @@ -36,8 +39,6 @@ export const useWorkspaceUpdate = ({ onSuccess, onError, }: UseWorkspaceUpdateOptions): UseWorkspaceUpdateResult => { - const { experiments } = useDashboard(); - const isDynamicParametersEnabled = experiments.includes("dynamic-parameters"); const queryClient = useQueryClient(); const [isConfirmingUpdate, setIsConfirmingUpdate] = useState(false); @@ -55,10 +56,20 @@ export const useWorkspaceUpdate = ({ setIsConfirmingUpdate(true); }; + const { experiments } = useDashboard(); + const dynamicParametersEnabled = experiments.includes("dynamic-parameters"); + + const optOutQuery = useDynamicParametersOptOut({ + templateId: workspace.template_id, + templateUsesClassicParameters: + workspace.template_use_classic_parameter_flow, + enabled: dynamicParametersEnabled, + }); + const confirmUpdate = (buildParameters: WorkspaceBuildParameter[] = []) => { updateWorkspaceMutation.mutate({ buildParameters, - isDynamicParametersEnabled, + isDynamicParametersEnabled: Boolean(!optOutQuery.data?.optedOut) ?? false, }); setIsConfirmingUpdate(false); }; @@ -155,6 +166,12 @@ const MissingBuildParametersDialog: FC = ({ }) => { const { experiments } = useDashboard(); const isDynamicParametersEnabled = experiments.includes("dynamic-parameters"); + const optOutQuery = useDynamicParametersOptOut({ + templateId: workspace.template_id, + templateUsesClassicParameters: + workspace.template_use_classic_parameter_flow, + enabled: isDynamicParametersEnabled, + }); const missedParameters = error instanceof MissingBuildParameters ? error.parameters : []; @@ -162,7 +179,20 @@ const MissingBuildParametersDialog: FC = ({ error instanceof MissingBuildParameters ? error.versionId : undefined; const isOpen = error instanceof MissingBuildParameters; - return isDynamicParametersEnabled ? ( + if (optOutQuery.isError) { + return ; + } + if (!optOutQuery.data) { + return ; + } + + return optOutQuery.data?.optedOut ? ( + + ) : ( = ({ workspaceName={workspace.name} templateVersionId={versionId} /> - ) : ( - ); }; diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspaceExperimentRouter.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspaceExperimentRouter.tsx index 4f2d0a4e4f8f7..54315cff51c2d 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspaceExperimentRouter.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspaceExperimentRouter.tsx @@ -8,42 +8,33 @@ import { useParams } from "react-router-dom"; import CreateWorkspacePage from "./CreateWorkspacePage"; import CreateWorkspacePageExperimental from "./CreateWorkspacePageExperimental"; import { ExperimentalFormContext } from "./ExperimentalFormContext"; +import { + optOutKey, + useDynamicParametersOptOut, +} from "hooks/useDynamicParametersOptOut"; const CreateWorkspaceExperimentRouter: FC = () => { const { experiments } = useDashboard(); - const dynamicParametersEnabled = experiments.includes("dynamic-parameters"); + const isDynamicParametersEnabled = experiments.includes("dynamic-parameters"); const { organization: organizationName = "default", template: templateName } = useParams() as { organization?: string; template: string }; const templateQuery = useQuery({ ...templateByName(organizationName, templateName), - enabled: dynamicParametersEnabled, + enabled: isDynamicParametersEnabled, }); - const optOutQuery = useQuery({ + const optOutQuery = useDynamicParametersOptOut({ + templateId: templateQuery.data?.id, + templateUsesClassicParameters: + templateQuery.data?.use_classic_parameter_flow, enabled: !!templateQuery.data, - queryKey: [organizationName, "template", templateQuery.data?.id, "optOut"], - queryFn: () => { - const templateId = templateQuery.data?.id; - const localStorageKey = optOutKey(templateId ?? ""); - const storedOptOutString = localStorage.getItem(localStorageKey); - - let optOutResult: boolean; - - if (storedOptOutString !== null) { - optOutResult = storedOptOutString === "true"; - } else { - optOutResult = !!templateQuery.data?.use_classic_parameter_flow; - } - - return { - templateId: templateId, - optedOut: optOutResult, - }; - }, }); - if (dynamicParametersEnabled) { + if (isDynamicParametersEnabled) { + if (templateQuery.isError) { + return ; + } if (optOutQuery.isError) { return ; } @@ -77,5 +68,3 @@ const CreateWorkspaceExperimentRouter: FC = () => { }; export default CreateWorkspaceExperimentRouter; - -const optOutKey = (id: string) => `parameters.${id}.optOut`; diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersExperimentRouter.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersExperimentRouter.tsx index e7f57108f8e54..7f2d158968549 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersExperimentRouter.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersExperimentRouter.tsx @@ -2,54 +2,33 @@ import { ErrorAlert } from "components/Alert/ErrorAlert"; import { Loader } from "components/Loader/Loader"; import { useDashboard } from "modules/dashboard/useDashboard"; import type { FC } from "react"; -import { useQuery } from "react-query"; import { ExperimentalFormContext } from "../../CreateWorkspacePage/ExperimentalFormContext"; import { useWorkspaceSettings } from "../WorkspaceSettingsLayout"; import WorkspaceParametersPage from "./WorkspaceParametersPage"; import WorkspaceParametersPageExperimental from "./WorkspaceParametersPageExperimental"; +import { + optOutKey, + useDynamicParametersOptOut, +} from "hooks/useDynamicParametersOptOut"; const WorkspaceParametersExperimentRouter: FC = () => { const { experiments } = useDashboard(); const workspace = useWorkspaceSettings(); const dynamicParametersEnabled = experiments.includes("dynamic-parameters"); - const optOutQuery = useQuery({ + const optOutQuery = useDynamicParametersOptOut({ + templateId: workspace.template_id, + templateUsesClassicParameters: + workspace.template_use_classic_parameter_flow, enabled: dynamicParametersEnabled, - queryKey: [ - "workspace", - workspace.id, - "template_id", - workspace.template_id, - "optOut", - ], - queryFn: () => { - const templateId = workspace.template_id; - const workspaceId = workspace.id; - const localStorageKey = optOutKey(templateId); - const storedOptOutString = localStorage.getItem(localStorageKey); - - let optOutResult: boolean; - - if (storedOptOutString !== null) { - optOutResult = storedOptOutString === "true"; - } else { - optOutResult = Boolean(workspace.template_use_classic_parameter_flow); - } - - return { - templateId, - workspaceId, - optedOut: optOutResult, - }; - }, }); if (dynamicParametersEnabled) { - if (optOutQuery.isLoading) { - return ; + if (optOutQuery.isError) { + return ; } if (!optOutQuery.data) { - return ; + return ; } const toggleOptedOut = () => { @@ -79,5 +58,3 @@ const WorkspaceParametersExperimentRouter: FC = () => { }; export default WorkspaceParametersExperimentRouter; - -const optOutKey = (id: string) => `parameters.${id}.optOut`; diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageViewExperimental.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageViewExperimental.tsx index 481f0275370e6..8d11c08fcc587 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageViewExperimental.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageViewExperimental.tsx @@ -155,11 +155,11 @@ export const WorkspaceParametersPageViewExperimental: FC< )} - {(templateVersionId || workspace.template_active_version_id) && ( + {(templateVersionId || workspace.latest_build.template_version_id) && (

- {templateVersionId ?? workspace.template_active_version_id} + {templateVersionId ?? workspace.latest_build.template_version_id}

)} From 67ac3c79aaed48c80263a33cb12ec375dc44a31f Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Wed, 4 Jun 2025 22:59:09 +0000 Subject: [PATCH 04/16] fix: format --- .../WorkspaceMoreActions/WorkspaceMoreActions.tsx | 2 +- site/src/modules/workspaces/WorkspaceUpdateDialogs.tsx | 6 +++--- .../CreateWorkspaceExperimentRouter.tsx | 8 ++++---- .../WorkspaceParametersExperimentRouter.tsx | 8 ++++---- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx b/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx index b6d55db9bc413..0aac123ce7744 100644 --- a/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx +++ b/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx @@ -13,6 +13,7 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from "components/DropdownMenu/DropdownMenu"; +import { useDynamicParametersOptOut } from "hooks/useDynamicParametersOptOut"; import { CopyIcon, DownloadIcon, @@ -31,7 +32,6 @@ import { UpdateBuildParametersDialog } from "./UpdateBuildParametersDialog"; import { UpdateBuildParametersDialogExperimental } from "./UpdateBuildParametersDialogExperimental"; import { WorkspaceDeleteDialog } from "./WorkspaceDeleteDialog"; import { useWorkspaceDuplication } from "./useWorkspaceDuplication"; -import { useDynamicParametersOptOut } from "hooks/useDynamicParametersOptOut"; type WorkspaceMoreActionsProps = { workspace: Workspace; diff --git a/site/src/modules/workspaces/WorkspaceUpdateDialogs.tsx b/site/src/modules/workspaces/WorkspaceUpdateDialogs.tsx index e28f27b36db13..a8a17c6cfa3c3 100644 --- a/site/src/modules/workspaces/WorkspaceUpdateDialogs.tsx +++ b/site/src/modules/workspaces/WorkspaceUpdateDialogs.tsx @@ -6,16 +6,16 @@ import type { WorkspaceBuild, WorkspaceBuildParameter, } from "api/typesGenerated"; +import { ErrorAlert } from "components/Alert/ErrorAlert"; import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog"; +import { Loader } from "components/Loader/Loader"; import { MemoizedInlineMarkdown } from "components/Markdown/Markdown"; +import { useDynamicParametersOptOut } from "hooks/useDynamicParametersOptOut"; import { useDashboard } from "modules/dashboard/useDashboard"; import { UpdateBuildParametersDialog } from "modules/workspaces/WorkspaceMoreActions/UpdateBuildParametersDialog"; import { UpdateBuildParametersDialogExperimental } from "modules/workspaces/WorkspaceMoreActions/UpdateBuildParametersDialogExperimental"; import { type FC, useState } from "react"; import { useMutation, useQueryClient } from "react-query"; -import { useDynamicParametersOptOut } from "hooks/useDynamicParametersOptOut"; -import { ErrorAlert } from "components/Alert/ErrorAlert"; -import { Loader } from "components/Loader/Loader"; type UseWorkspaceUpdateOptions = { workspace: Workspace; diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspaceExperimentRouter.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspaceExperimentRouter.tsx index 54315cff51c2d..f0116134f7e91 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspaceExperimentRouter.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspaceExperimentRouter.tsx @@ -1,6 +1,10 @@ import { templateByName } from "api/queries/templates"; import { ErrorAlert } from "components/Alert/ErrorAlert"; import { Loader } from "components/Loader/Loader"; +import { + optOutKey, + useDynamicParametersOptOut, +} from "hooks/useDynamicParametersOptOut"; import { useDashboard } from "modules/dashboard/useDashboard"; import type { FC } from "react"; import { useQuery } from "react-query"; @@ -8,10 +12,6 @@ import { useParams } from "react-router-dom"; import CreateWorkspacePage from "./CreateWorkspacePage"; import CreateWorkspacePageExperimental from "./CreateWorkspacePageExperimental"; import { ExperimentalFormContext } from "./ExperimentalFormContext"; -import { - optOutKey, - useDynamicParametersOptOut, -} from "hooks/useDynamicParametersOptOut"; const CreateWorkspaceExperimentRouter: FC = () => { const { experiments } = useDashboard(); diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersExperimentRouter.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersExperimentRouter.tsx index 7f2d158968549..ab58286c311f6 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersExperimentRouter.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersExperimentRouter.tsx @@ -1,15 +1,15 @@ import { ErrorAlert } from "components/Alert/ErrorAlert"; import { Loader } from "components/Loader/Loader"; +import { + optOutKey, + useDynamicParametersOptOut, +} from "hooks/useDynamicParametersOptOut"; import { useDashboard } from "modules/dashboard/useDashboard"; import type { FC } from "react"; import { ExperimentalFormContext } from "../../CreateWorkspacePage/ExperimentalFormContext"; import { useWorkspaceSettings } from "../WorkspaceSettingsLayout"; import WorkspaceParametersPage from "./WorkspaceParametersPage"; import WorkspaceParametersPageExperimental from "./WorkspaceParametersPageExperimental"; -import { - optOutKey, - useDynamicParametersOptOut, -} from "hooks/useDynamicParametersOptOut"; const WorkspaceParametersExperimentRouter: FC = () => { const { experiments } = useDashboard(); From 38ae3c718411bd2086b90afffb16b8ff1227183f Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Wed, 4 Jun 2025 23:28:19 +0000 Subject: [PATCH 05/16] fix: cleanup logic --- .../WorkspaceMoreActions/WorkspaceMoreActions.tsx | 6 +----- site/src/modules/workspaces/WorkspaceUpdateDialogs.tsx | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx b/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx index 0aac123ce7744..ac64c7194878b 100644 --- a/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx +++ b/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx @@ -62,11 +62,7 @@ export const WorkspaceMoreActions: FC = ({ // Change version const [changeVersionDialogOpen, setChangeVersionDialogOpen] = useState(false); const changeVersionMutation = useMutation( - changeVersion( - workspace, - queryClient, - Boolean(!optOutQuery.data?.optedOut) ?? false, - ), + changeVersion(workspace, queryClient, optOutQuery.data?.optedOut === false), ); // Delete diff --git a/site/src/modules/workspaces/WorkspaceUpdateDialogs.tsx b/site/src/modules/workspaces/WorkspaceUpdateDialogs.tsx index a8a17c6cfa3c3..9ac174bd943ab 100644 --- a/site/src/modules/workspaces/WorkspaceUpdateDialogs.tsx +++ b/site/src/modules/workspaces/WorkspaceUpdateDialogs.tsx @@ -69,7 +69,7 @@ export const useWorkspaceUpdate = ({ const confirmUpdate = (buildParameters: WorkspaceBuildParameter[] = []) => { updateWorkspaceMutation.mutate({ buildParameters, - isDynamicParametersEnabled: Boolean(!optOutQuery.data?.optedOut) ?? false, + isDynamicParametersEnabled: optOutQuery.data?.optedOut === false, }); setIsConfirmingUpdate(false); }; From 8c50de02e1054457ba52b4fe7dea909d82dab823 Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Wed, 4 Jun 2025 23:32:35 +0000 Subject: [PATCH 06/16] chore: cleanup --- .../DynamicParameter}/useDynamicParametersOptOut.ts | 0 .../workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx | 2 +- site/src/modules/workspaces/WorkspaceUpdateDialogs.tsx | 2 +- .../CreateWorkspacePage/CreateWorkspaceExperimentRouter.tsx | 4 ++-- .../WorkspaceParametersExperimentRouter.tsx | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) rename site/src/{hooks => modules/workspaces/DynamicParameter}/useDynamicParametersOptOut.ts (100%) diff --git a/site/src/hooks/useDynamicParametersOptOut.ts b/site/src/modules/workspaces/DynamicParameter/useDynamicParametersOptOut.ts similarity index 100% rename from site/src/hooks/useDynamicParametersOptOut.ts rename to site/src/modules/workspaces/DynamicParameter/useDynamicParametersOptOut.ts diff --git a/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx b/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx index ac64c7194878b..e818bb22ae04f 100644 --- a/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx +++ b/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx @@ -13,7 +13,6 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from "components/DropdownMenu/DropdownMenu"; -import { useDynamicParametersOptOut } from "hooks/useDynamicParametersOptOut"; import { CopyIcon, DownloadIcon, @@ -23,6 +22,7 @@ import { TrashIcon, } from "lucide-react"; import { useDashboard } from "modules/dashboard/useDashboard"; +import { useDynamicParametersOptOut } from "modules/workspaces/DynamicParameter/useDynamicParametersOptOut"; import { type FC, useEffect, useState } from "react"; import { useMutation, useQuery, useQueryClient } from "react-query"; import { Link as RouterLink } from "react-router-dom"; diff --git a/site/src/modules/workspaces/WorkspaceUpdateDialogs.tsx b/site/src/modules/workspaces/WorkspaceUpdateDialogs.tsx index 9ac174bd943ab..e6dc00e747cce 100644 --- a/site/src/modules/workspaces/WorkspaceUpdateDialogs.tsx +++ b/site/src/modules/workspaces/WorkspaceUpdateDialogs.tsx @@ -10,8 +10,8 @@ import { ErrorAlert } from "components/Alert/ErrorAlert"; import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog"; import { Loader } from "components/Loader/Loader"; import { MemoizedInlineMarkdown } from "components/Markdown/Markdown"; -import { useDynamicParametersOptOut } from "hooks/useDynamicParametersOptOut"; import { useDashboard } from "modules/dashboard/useDashboard"; +import { useDynamicParametersOptOut } from "modules/workspaces/DynamicParameter/useDynamicParametersOptOut"; import { UpdateBuildParametersDialog } from "modules/workspaces/WorkspaceMoreActions/UpdateBuildParametersDialog"; import { UpdateBuildParametersDialogExperimental } from "modules/workspaces/WorkspaceMoreActions/UpdateBuildParametersDialogExperimental"; import { type FC, useState } from "react"; diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspaceExperimentRouter.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspaceExperimentRouter.tsx index f0116134f7e91..fe63391916128 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspaceExperimentRouter.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspaceExperimentRouter.tsx @@ -1,11 +1,11 @@ import { templateByName } from "api/queries/templates"; import { ErrorAlert } from "components/Alert/ErrorAlert"; import { Loader } from "components/Loader/Loader"; +import { useDashboard } from "modules/dashboard/useDashboard"; import { optOutKey, useDynamicParametersOptOut, -} from "hooks/useDynamicParametersOptOut"; -import { useDashboard } from "modules/dashboard/useDashboard"; +} from "modules/workspaces/DynamicParameter/useDynamicParametersOptOut"; import type { FC } from "react"; import { useQuery } from "react-query"; import { useParams } from "react-router-dom"; diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersExperimentRouter.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersExperimentRouter.tsx index ab58286c311f6..1b33b0027bb60 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersExperimentRouter.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersExperimentRouter.tsx @@ -1,10 +1,10 @@ import { ErrorAlert } from "components/Alert/ErrorAlert"; import { Loader } from "components/Loader/Loader"; +import { useDashboard } from "modules/dashboard/useDashboard"; import { optOutKey, useDynamicParametersOptOut, -} from "hooks/useDynamicParametersOptOut"; -import { useDashboard } from "modules/dashboard/useDashboard"; +} from "modules/workspaces/DynamicParameter/useDynamicParametersOptOut"; import type { FC } from "react"; import { ExperimentalFormContext } from "../../CreateWorkspacePage/ExperimentalFormContext"; import { useWorkspaceSettings } from "../WorkspaceSettingsLayout"; From 86af69d011cfc0f483b1abc044386ace857bc9da Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Wed, 4 Jun 2025 23:34:20 +0000 Subject: [PATCH 07/16] fix: cleanup --- site/src/modules/workspaces/WorkspaceUpdateDialogs.tsx | 4 ++-- .../WorkspaceParametersExperimentRouter.tsx | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/site/src/modules/workspaces/WorkspaceUpdateDialogs.tsx b/site/src/modules/workspaces/WorkspaceUpdateDialogs.tsx index e6dc00e747cce..336d8055fab58 100644 --- a/site/src/modules/workspaces/WorkspaceUpdateDialogs.tsx +++ b/site/src/modules/workspaces/WorkspaceUpdateDialogs.tsx @@ -57,13 +57,13 @@ export const useWorkspaceUpdate = ({ }; const { experiments } = useDashboard(); - const dynamicParametersEnabled = experiments.includes("dynamic-parameters"); + const isDynamicParametersEnabled = experiments.includes("dynamic-parameters"); const optOutQuery = useDynamicParametersOptOut({ templateId: workspace.template_id, templateUsesClassicParameters: workspace.template_use_classic_parameter_flow, - enabled: dynamicParametersEnabled, + enabled: isDynamicParametersEnabled, }); const confirmUpdate = (buildParameters: WorkspaceBuildParameter[] = []) => { diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersExperimentRouter.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersExperimentRouter.tsx index 1b33b0027bb60..476a764ac9204 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersExperimentRouter.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersExperimentRouter.tsx @@ -14,16 +14,16 @@ import WorkspaceParametersPageExperimental from "./WorkspaceParametersPageExperi const WorkspaceParametersExperimentRouter: FC = () => { const { experiments } = useDashboard(); const workspace = useWorkspaceSettings(); - const dynamicParametersEnabled = experiments.includes("dynamic-parameters"); + const isDynamicParametersEnabled = experiments.includes("dynamic-parameters"); const optOutQuery = useDynamicParametersOptOut({ templateId: workspace.template_id, templateUsesClassicParameters: workspace.template_use_classic_parameter_flow, - enabled: dynamicParametersEnabled, + enabled: isDynamicParametersEnabled, }); - if (dynamicParametersEnabled) { + if (isDynamicParametersEnabled) { if (optOutQuery.isError) { return ; } From 6a20a0d996432912da932bf88541e255551d451a Mon Sep 17 00:00:00 2001 From: "blink-so[bot]" <211532188+blink-so[bot]@users.noreply.github.com> Date: Thu, 5 Jun 2025 09:47:50 +0000 Subject: [PATCH 08/16] fix: update test calls to match new updateWorkspace signature The updateWorkspace function now requires additional parameters: - buildParameters (array, defaults to []) - isDynamicParametersEnabled (boolean, defaults to false) Updated all test calls to include these parameters to fix test failures. --- site/src/api/api.test.ts | 6 +++--- .../WorkspacePage/WorkspacePage.test.tsx | 2 +- .../WorkspacesPage/WorkspacesPage.test.tsx | 20 +++++++++---------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/site/src/api/api.test.ts b/site/src/api/api.test.ts index e72dd5f8d0bad..9b8f7360ad4ce 100644 --- a/site/src/api/api.test.ts +++ b/site/src/api/api.test.ts @@ -176,7 +176,7 @@ describe("api.ts", () => { .spyOn(API, "postWorkspaceBuild") .mockResolvedValueOnce(MockWorkspaceBuild); jest.spyOn(API, "getTemplate").mockResolvedValueOnce(MockTemplate); - await API.updateWorkspace(MockWorkspace); + await API.updateWorkspace(MockWorkspace, [], false); expect(API.postWorkspaceBuild).toHaveBeenCalledWith(MockWorkspace.id, { transition: "start", template_version_id: MockTemplate.active_version_id, @@ -199,7 +199,7 @@ describe("api.ts", () => { let error = new Error(); try { - await API.updateWorkspace(MockWorkspace); + await API.updateWorkspace(MockWorkspace, [], false); } catch (e) { error = e as Error; } @@ -225,7 +225,7 @@ describe("api.ts", () => { .mockResolvedValue([ { ...MockTemplateVersionParameter1, required: true, mutable: false }, ]); - await API.updateWorkspace(MockWorkspace); + await API.updateWorkspace(MockWorkspace, [], false); expect(API.postWorkspaceBuild).toHaveBeenCalledWith(MockWorkspace.id, { transition: "start", template_version_id: MockTemplate.active_version_id, diff --git a/site/src/pages/WorkspacePage/WorkspacePage.test.tsx b/site/src/pages/WorkspacePage/WorkspacePage.test.tsx index 3f217a86a3aad..87b19fca7b8ae 100644 --- a/site/src/pages/WorkspacePage/WorkspacePage.test.tsx +++ b/site/src/pages/WorkspacePage/WorkspacePage.test.tsx @@ -314,7 +314,7 @@ describe("WorkspacePage", () => { name: MockTemplateVersionParameter2.name, value: "2", }, - ]); + ], false); }); }); diff --git a/site/src/pages/WorkspacesPage/WorkspacesPage.test.tsx b/site/src/pages/WorkspacesPage/WorkspacesPage.test.tsx index c8577f191d47e..94c12f0372b59 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPage.test.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPage.test.tsx @@ -123,8 +123,8 @@ describe("WorkspacesPage", () => { await waitFor(() => { expect(updateWorkspace).toHaveBeenCalledTimes(2); }); - expect(updateWorkspace).toHaveBeenCalledWith(workspaces[2]); - expect(updateWorkspace).toHaveBeenCalledWith(workspaces[3]); + expect(updateWorkspace).toHaveBeenCalledWith(workspaces[2], [], false); + expect(updateWorkspace).toHaveBeenCalledWith(workspaces[3], [], false); }); it("warns about and updates running workspaces", async () => { @@ -160,9 +160,9 @@ describe("WorkspacesPage", () => { await waitFor(() => { expect(updateWorkspace).toHaveBeenCalledTimes(3); }); - expect(updateWorkspace).toHaveBeenCalledWith(workspaces[0]); - expect(updateWorkspace).toHaveBeenCalledWith(workspaces[1]); - expect(updateWorkspace).toHaveBeenCalledWith(workspaces[2]); + expect(updateWorkspace).toHaveBeenCalledWith(workspaces[0], [], false); + expect(updateWorkspace).toHaveBeenCalledWith(workspaces[1], [], false); + expect(updateWorkspace).toHaveBeenCalledWith(workspaces[2], [], false); }); it("warns about and ignores dormant workspaces", async () => { @@ -199,8 +199,8 @@ describe("WorkspacesPage", () => { await waitFor(() => { expect(updateWorkspace).toHaveBeenCalledTimes(2); }); - expect(updateWorkspace).toHaveBeenCalledWith(workspaces[1]); - expect(updateWorkspace).toHaveBeenCalledWith(workspaces[2]); + expect(updateWorkspace).toHaveBeenCalledWith(workspaces[1], [], false); + expect(updateWorkspace).toHaveBeenCalledWith(workspaces[2], [], false); }); it("warns about running workspaces and then dormant workspaces", async () => { @@ -241,9 +241,9 @@ describe("WorkspacesPage", () => { await waitFor(() => { expect(updateWorkspace).toHaveBeenCalledTimes(3); }); - expect(updateWorkspace).toHaveBeenCalledWith(workspaces[0]); - expect(updateWorkspace).toHaveBeenCalledWith(workspaces[2]); - expect(updateWorkspace).toHaveBeenCalledWith(workspaces[3]); + expect(updateWorkspace).toHaveBeenCalledWith(workspaces[0], [], false); + expect(updateWorkspace).toHaveBeenCalledWith(workspaces[2], [], false); + expect(updateWorkspace).toHaveBeenCalledWith(workspaces[3], [], false); }); }); From c82b1450d5a2a5d10f46eb722b196e0a6fc084b2 Mon Sep 17 00:00:00 2001 From: "blink-so[bot]" <211532188+blink-so[bot]@users.noreply.github.com> Date: Thu, 5 Jun 2025 13:05:29 +0000 Subject: [PATCH 09/16] fix: handle missing dynamic-parameters experiment in parameter dialogs When the dynamic-parameters experiment is not enabled, the parameter dialogs were showing a loader indefinitely instead of the classic parameter dialog. This was causing e2e tests to fail because they couldn't find the parameter fields. Fixed by: - Only showing loader when experiment is enabled AND optOut data is loading - Using classic dialog when experiment is disabled OR user opted out - This ensures e2e tests work correctly without the experiment flag --- .../WorkspaceMoreActions/WorkspaceMoreActions.tsx | 2 +- site/src/modules/workspaces/WorkspaceUpdateDialogs.tsx | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx b/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx index e818bb22ae04f..aa2a077e9a2f7 100644 --- a/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx +++ b/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx @@ -154,7 +154,7 @@ export const WorkspaceMoreActions: FC = ({ onClose={() => setIsDownloadDialogOpen(false)} /> - {optOutQuery.data?.optedOut ? ( + {(!isDynamicParametersEnabled || optOutQuery.data?.optedOut) ? ( = ({ if (optOutQuery.isError) { return ; } - if (!optOutQuery.data) { + if (isDynamicParametersEnabled && !optOutQuery.data) { return ; } - return optOutQuery.data?.optedOut ? ( + // If dynamic parameters experiment is not enabled, or if opted out, use classic dialog + const shouldUseClassicDialog = !isDynamicParametersEnabled || optOutQuery.data?.optedOut; + + return shouldUseClassicDialog ? ( Date: Thu, 5 Jun 2025 16:33:13 +0000 Subject: [PATCH 10/16] fix: format --- .../workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx | 2 +- site/src/modules/workspaces/WorkspaceUpdateDialogs.tsx | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx b/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx index aa2a077e9a2f7..8cdbafad435a3 100644 --- a/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx +++ b/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceMoreActions.tsx @@ -154,7 +154,7 @@ export const WorkspaceMoreActions: FC = ({ onClose={() => setIsDownloadDialogOpen(false)} /> - {(!isDynamicParametersEnabled || optOutQuery.data?.optedOut) ? ( + {!isDynamicParametersEnabled || optOutQuery.data?.optedOut ? ( = ({ } // If dynamic parameters experiment is not enabled, or if opted out, use classic dialog - const shouldUseClassicDialog = !isDynamicParametersEnabled || optOutQuery.data?.optedOut; + const shouldUseClassicDialog = + !isDynamicParametersEnabled || optOutQuery.data?.optedOut; return shouldUseClassicDialog ? ( Date: Thu, 5 Jun 2025 16:59:02 +0000 Subject: [PATCH 11/16] fix: format --- .../WorkspacePage/WorkspacePage.test.tsx | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/site/src/pages/WorkspacePage/WorkspacePage.test.tsx b/site/src/pages/WorkspacePage/WorkspacePage.test.tsx index 87b19fca7b8ae..fb95d0c883627 100644 --- a/site/src/pages/WorkspacePage/WorkspacePage.test.tsx +++ b/site/src/pages/WorkspacePage/WorkspacePage.test.tsx @@ -305,16 +305,20 @@ describe("WorkspacePage", () => { // Check if the update was called using the values from the form await waitFor(() => { - expect(API.updateWorkspace).toBeCalledWith(MockOutdatedWorkspace, [ - { - name: MockTemplateVersionParameter1.name, - value: "some-value", - }, - { - name: MockTemplateVersionParameter2.name, - value: "2", - }, - ], false); + expect(API.updateWorkspace).toBeCalledWith( + MockOutdatedWorkspace, + [ + { + name: MockTemplateVersionParameter1.name, + value: "some-value", + }, + { + name: MockTemplateVersionParameter2.name, + value: "2", + }, + ], + false, + ); }); }); From f2876d2aac57688b5ecd5ff538a91e8f07544edf Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Thu, 5 Jun 2025 17:01:00 +0000 Subject: [PATCH 12/16] fix: lint error --- site/src/components/Dialog/Dialog.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/src/components/Dialog/Dialog.tsx b/site/src/components/Dialog/Dialog.tsx index a71e00bf4da50..2ec8ab40781c7 100644 --- a/site/src/components/Dialog/Dialog.tsx +++ b/site/src/components/Dialog/Dialog.tsx @@ -16,9 +16,9 @@ export const Dialog = DialogPrimitive.Root; export const DialogTrigger = DialogPrimitive.Trigger; -export const DialogPortal = DialogPrimitive.Portal; +const DialogPortal = DialogPrimitive.Portal; -export const DialogClose = DialogPrimitive.Close; +const DialogClose = DialogPrimitive.Close; const DialogOverlay = forwardRef< ElementRef, From 962f5fd2067666833776076ec7d0de993b8d5a86 Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Thu, 5 Jun 2025 17:14:37 +0000 Subject: [PATCH 13/16] fix: remove unnecessary parameters --- site/src/api/api.test.ts | 6 +++--- .../WorkspacesPage/WorkspacesPage.test.tsx | 20 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/site/src/api/api.test.ts b/site/src/api/api.test.ts index 9b8f7360ad4ce..e72dd5f8d0bad 100644 --- a/site/src/api/api.test.ts +++ b/site/src/api/api.test.ts @@ -176,7 +176,7 @@ describe("api.ts", () => { .spyOn(API, "postWorkspaceBuild") .mockResolvedValueOnce(MockWorkspaceBuild); jest.spyOn(API, "getTemplate").mockResolvedValueOnce(MockTemplate); - await API.updateWorkspace(MockWorkspace, [], false); + await API.updateWorkspace(MockWorkspace); expect(API.postWorkspaceBuild).toHaveBeenCalledWith(MockWorkspace.id, { transition: "start", template_version_id: MockTemplate.active_version_id, @@ -199,7 +199,7 @@ describe("api.ts", () => { let error = new Error(); try { - await API.updateWorkspace(MockWorkspace, [], false); + await API.updateWorkspace(MockWorkspace); } catch (e) { error = e as Error; } @@ -225,7 +225,7 @@ describe("api.ts", () => { .mockResolvedValue([ { ...MockTemplateVersionParameter1, required: true, mutable: false }, ]); - await API.updateWorkspace(MockWorkspace, [], false); + await API.updateWorkspace(MockWorkspace); expect(API.postWorkspaceBuild).toHaveBeenCalledWith(MockWorkspace.id, { transition: "start", template_version_id: MockTemplate.active_version_id, diff --git a/site/src/pages/WorkspacesPage/WorkspacesPage.test.tsx b/site/src/pages/WorkspacesPage/WorkspacesPage.test.tsx index 94c12f0372b59..c8577f191d47e 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPage.test.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPage.test.tsx @@ -123,8 +123,8 @@ describe("WorkspacesPage", () => { await waitFor(() => { expect(updateWorkspace).toHaveBeenCalledTimes(2); }); - expect(updateWorkspace).toHaveBeenCalledWith(workspaces[2], [], false); - expect(updateWorkspace).toHaveBeenCalledWith(workspaces[3], [], false); + expect(updateWorkspace).toHaveBeenCalledWith(workspaces[2]); + expect(updateWorkspace).toHaveBeenCalledWith(workspaces[3]); }); it("warns about and updates running workspaces", async () => { @@ -160,9 +160,9 @@ describe("WorkspacesPage", () => { await waitFor(() => { expect(updateWorkspace).toHaveBeenCalledTimes(3); }); - expect(updateWorkspace).toHaveBeenCalledWith(workspaces[0], [], false); - expect(updateWorkspace).toHaveBeenCalledWith(workspaces[1], [], false); - expect(updateWorkspace).toHaveBeenCalledWith(workspaces[2], [], false); + expect(updateWorkspace).toHaveBeenCalledWith(workspaces[0]); + expect(updateWorkspace).toHaveBeenCalledWith(workspaces[1]); + expect(updateWorkspace).toHaveBeenCalledWith(workspaces[2]); }); it("warns about and ignores dormant workspaces", async () => { @@ -199,8 +199,8 @@ describe("WorkspacesPage", () => { await waitFor(() => { expect(updateWorkspace).toHaveBeenCalledTimes(2); }); - expect(updateWorkspace).toHaveBeenCalledWith(workspaces[1], [], false); - expect(updateWorkspace).toHaveBeenCalledWith(workspaces[2], [], false); + expect(updateWorkspace).toHaveBeenCalledWith(workspaces[1]); + expect(updateWorkspace).toHaveBeenCalledWith(workspaces[2]); }); it("warns about running workspaces and then dormant workspaces", async () => { @@ -241,9 +241,9 @@ describe("WorkspacesPage", () => { await waitFor(() => { expect(updateWorkspace).toHaveBeenCalledTimes(3); }); - expect(updateWorkspace).toHaveBeenCalledWith(workspaces[0], [], false); - expect(updateWorkspace).toHaveBeenCalledWith(workspaces[2], [], false); - expect(updateWorkspace).toHaveBeenCalledWith(workspaces[3], [], false); + expect(updateWorkspace).toHaveBeenCalledWith(workspaces[0]); + expect(updateWorkspace).toHaveBeenCalledWith(workspaces[2]); + expect(updateWorkspace).toHaveBeenCalledWith(workspaces[3]); }); }); From 80fd236b0d7976e6c07cad2f27a926bff5de6189 Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Thu, 5 Jun 2025 18:31:28 +0000 Subject: [PATCH 14/16] fix: fix tests --- .../WorkspacesPage/WorkspacesPage.test.tsx | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/site/src/pages/WorkspacesPage/WorkspacesPage.test.tsx b/site/src/pages/WorkspacesPage/WorkspacesPage.test.tsx index c8577f191d47e..94c12f0372b59 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPage.test.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPage.test.tsx @@ -123,8 +123,8 @@ describe("WorkspacesPage", () => { await waitFor(() => { expect(updateWorkspace).toHaveBeenCalledTimes(2); }); - expect(updateWorkspace).toHaveBeenCalledWith(workspaces[2]); - expect(updateWorkspace).toHaveBeenCalledWith(workspaces[3]); + expect(updateWorkspace).toHaveBeenCalledWith(workspaces[2], [], false); + expect(updateWorkspace).toHaveBeenCalledWith(workspaces[3], [], false); }); it("warns about and updates running workspaces", async () => { @@ -160,9 +160,9 @@ describe("WorkspacesPage", () => { await waitFor(() => { expect(updateWorkspace).toHaveBeenCalledTimes(3); }); - expect(updateWorkspace).toHaveBeenCalledWith(workspaces[0]); - expect(updateWorkspace).toHaveBeenCalledWith(workspaces[1]); - expect(updateWorkspace).toHaveBeenCalledWith(workspaces[2]); + expect(updateWorkspace).toHaveBeenCalledWith(workspaces[0], [], false); + expect(updateWorkspace).toHaveBeenCalledWith(workspaces[1], [], false); + expect(updateWorkspace).toHaveBeenCalledWith(workspaces[2], [], false); }); it("warns about and ignores dormant workspaces", async () => { @@ -199,8 +199,8 @@ describe("WorkspacesPage", () => { await waitFor(() => { expect(updateWorkspace).toHaveBeenCalledTimes(2); }); - expect(updateWorkspace).toHaveBeenCalledWith(workspaces[1]); - expect(updateWorkspace).toHaveBeenCalledWith(workspaces[2]); + expect(updateWorkspace).toHaveBeenCalledWith(workspaces[1], [], false); + expect(updateWorkspace).toHaveBeenCalledWith(workspaces[2], [], false); }); it("warns about running workspaces and then dormant workspaces", async () => { @@ -241,9 +241,9 @@ describe("WorkspacesPage", () => { await waitFor(() => { expect(updateWorkspace).toHaveBeenCalledTimes(3); }); - expect(updateWorkspace).toHaveBeenCalledWith(workspaces[0]); - expect(updateWorkspace).toHaveBeenCalledWith(workspaces[2]); - expect(updateWorkspace).toHaveBeenCalledWith(workspaces[3]); + expect(updateWorkspace).toHaveBeenCalledWith(workspaces[0], [], false); + expect(updateWorkspace).toHaveBeenCalledWith(workspaces[2], [], false); + expect(updateWorkspace).toHaveBeenCalledWith(workspaces[3], [], false); }); }); From 9117b7f54300d326b538b8db9126d54c653eea8c Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Thu, 5 Jun 2025 19:12:30 +0000 Subject: [PATCH 15/16] fix: send existing build parameters for dynamic params --- site/src/api/api.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/site/src/api/api.ts b/site/src/api/api.ts index b0cf8a0b9a47b..1ab9268b1e368 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -234,7 +234,7 @@ export const watchWorkspaceAgentLogs = ( /** * WebSocket compression in Safari (confirmed in 16.5) is broken when * the server sends large messages. The following error is seen: - * WebSocket connection to 'wss://...' failed: The operation couldn’t be completed. + * WebSocket connection to 'wss://...' failed: The operation couldn't be completed. */ if (userAgentParser(navigator.userAgent).browser.name === "Safari") { searchParams.set("no_compression", ""); @@ -2146,11 +2146,17 @@ class ApiMethods { await this.axios.delete(`/api/v2/licenses/${licenseId}`); }; - getDynamicParameters = async (templateVersionId: string, ownerId: string) => { + getDynamicParameters = async ( + templateVersionId: string, + ownerId: string, + oldBuildParameters: TypesGen.WorkspaceBuildParameter[], + ) => { const request: DynamicParametersRequest = { id: 1, owner_id: ownerId, - inputs: {}, + inputs: Object.fromEntries( + new Map(oldBuildParameters.map((param) => [param.name, param.value])), + ), }; const dynamicParametersResponse = @@ -2196,6 +2202,7 @@ class ApiMethods { templateParameters = await this.getDynamicParameters( templateVersionId, workspace.owner_id, + currentBuildParameters, ); } else { templateParameters = @@ -2246,6 +2253,7 @@ class ApiMethods { templateParameters = await this.getDynamicParameters( activeVersionId, workspace.owner_id, + oldBuildParameters, ); } else { templateParameters = From d2856cd94e7d9f141e6cfdf904207a122828abec Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Thu, 5 Jun 2025 19:29:28 +0000 Subject: [PATCH 16/16] fix: handle multi-select --- site/src/api/api.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 1ab9268b1e368..28807bd547c2a 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -76,8 +76,10 @@ const getMissingParameters = ( if (templateParameter.options.length === 0) { continue; } - - // Check if there is a new value + // For multi-select, extra steps are necessary to JSON parse the value. + if (templateParameter.form_type === "multi-select") { + continue; + } let buildParameter = newBuildParameters.find( (p) => p.name === templateParameter.name, );