): boolean => {
- const request: DynamicParametersRequest = {
- id: wsResponseId.current + 1,
- owner_id: workspace.owner_id,
- inputs: formValues,
- };
- if (ws.current && ws.current.readyState === WebSocket.OPEN) {
- wsResponseId.current = wsResponseId.current + 1;
- ws.current.send(JSON.stringify(request));
- return true;
- }
- if (ws.current) {
- console.error(
- "Tried to send message but the web socket state is %s",
- ws.current.readyState,
- request,
- );
- }
- return false;
- };
-
- // Send the initial parameters if necessary and mark the ID of the response we
- // need to wait for until we can finally render the form with the right state.
- const sendInitialParameters = useEffectEvent(() => {
- if (latestBuildParametersLoading || !Number.isNaN(initId)) {
- return;
- }
- if (autofillParameters.length > 0) {
- const values = Object.fromEntries(
- autofillParameters.map((afp) => [afp.name, afp.value]),
- );
- if (!sendMessage(values)) {
- return;
- }
- }
- // If there were no parameters to send, this will end up just using the
- // response we already have. Otherwise it will wait for the next response.
- setInitId(wsResponseId.current);
- });
-
- // Send the build parameters once we get them.
- useEffect(() => {
- // sendInitialParameters already makes this check but the linter complains
- // if the dependency is not used.
- if (!latestBuildParametersLoading) {
- sendInitialParameters();
- }
- }, [latestBuildParametersLoading]);
-
- useEffect(() => {
- if (!templateVersionId && !workspace.latest_build.template_version_id)
- return;
-
- const socket = API.templateVersionDynamicParameters(
- templateVersionId ?? workspace.latest_build.template_version_id,
- workspace.owner_id,
- {
- onOpen: () => {
- // If we already have the build parameters, send them now.
- sendInitialParameters();
- },
- // Record the latest message every time we get one from the web
- // socket. Stale responses are discarded.
- onMessage: (response: DynamicParametersResponse) => {
- if (response.id >= wsResponseId.current) {
- setLatestResponse(response);
- }
- },
- onError: (error) => {
- if (ws.current === socket) {
- setWsError(error);
- }
- },
- onClose: () => {
- if (ws.current === socket) {
- setWsError(
- new DetailedError(
- "Websocket connection for dynamic parameters unexpectedly closed.",
- "Refresh the page to reset the form.",
- ),
- );
- }
- },
- },
- );
-
- ws.current = socket;
-
- return () => {
- socket.close();
- };
- }, [
- templateVersionId,
- workspace.latest_build.template_version_id,
- workspace.owner_id,
- ]);
-
- const startWithParameters = useMutation({
- mutationFn: (buildParameters: WorkspaceBuildParameter[]) =>
- API.postWorkspaceBuild(workspace.id, {
- transition: "start",
- template_version_id: templateVersionId,
- rich_parameter_values: buildParameters,
- reason: "dashboard",
- }),
- onSuccess: () => {
- navigate(`/@${workspace.owner_name}/${workspace.name}`);
- },
- });
-
- const restartWithParameters = useMutation({
- mutationFn: async (buildParameters: WorkspaceBuildParameter[]) => {
- const stopBuild = await API.stopWorkspace(workspace.id);
- const awaitedStopBuild = await API.waitForBuild(stopBuild);
-
- // If the restart is canceled halfway through, make sure we bail
- if (awaitedStopBuild?.status === "canceled") {
- return;
- }
-
- return API.postWorkspaceBuild(workspace.id, {
- transition: "start",
- template_version_id: templateVersionId,
- rich_parameter_values: buildParameters,
- reason: "dashboard",
- });
- },
- onSuccess: () => {
- navigate(`/@${workspace.owner_name}/${workspace.name}`);
- },
- });
-
- const canChangeVersions = Boolean(permissions?.updateWorkspaceVersion);
-
- const handleSubmit = (values: {
- rich_parameter_values: WorkspaceBuildParameter[];
- }) => {
- if (!latestResponse?.parameters) {
- return;
- }
-
- // Only submit mutable parameters
- const onlyMutableValues = latestResponse.parameters
- .filter((p) => p.mutable)
- .map((p) => {
- const value = values.rich_parameter_values.find(
- (v) => v.name === p.name,
- );
- if (!value) {
- throw new Error(`Missing value for parameter ${p.name}`);
- }
- return value;
- });
-
- // We only enable the button to navigate to this page if the workspace can
- // accept new jobs, but if the workspace is in any pending state (user
- // manually loaded the page or workspace state changed after load) then we
- // could still submit a build that will fail.
- if (workspace.latest_build.status === "running") {
- setConfirmingRestart({ open: true, buildParameters: onlyMutableValues });
- } else {
- startWithParameters.mutate(onlyMutableValues);
- }
- };
-
- const sortedParams = useMemo(() => {
- if (!latestResponse?.parameters) {
- return [];
- }
- return [...latestResponse.parameters].sort((a, b) => a.order - b.order);
- }, [latestResponse?.parameters]);
-
- const error =
- wsError || startWithParameters.error || restartWithParameters.error;
-
- // Some of these checks conceptually overlap, but opting to be explicit.
- const isLoading =
- latestBuildParametersLoading ||
- !latestResponse ||
- Number.isNaN(initId) ||
- latestResponse.id < initId ||
- (ws.current && ws.current.readyState === WebSocket.CONNECTING);
-
- let submitLabel = "Update and start";
- if (restartWithParameters.isPending) {
- submitLabel = "Stopping workspace";
- } else if (startWithParameters.isPending) {
- submitLabel = "Starting workspace";
- } else if (workspace.latest_build.status === "running") {
- submitLabel = "Update and restart";
- }
-
- return (
-
-
{pageTitle(workspace.name, "Parameters")}
-
-
-
-
- Workspace parameters
-
-
-
-
-
-
- Dynamic Parameters enhances Coder's existing parameter system
- with real-time validation, conditional parameter behavior, and
- richer input types.
-
-
- View docs
-
-
-
-
-
-
-
-
- {Boolean(error) && }
-
- {isLoading ? (
-
- ) : sortedParams.length > 0 ? (
-
- navigate(`/@${workspace.owner_name}/${workspace.name}`)
- }
- sendMessage={sendMessage}
- />
- ) : (
-
- Learn more about parameters
-
- }
- />
- )}
-
- {
- restartWithParameters.mutate(confirmingRestart.buildParameters ?? []);
- setConfirmingRestart({ open: false });
- }}
- onClose={() => setConfirmingRestart({ open: false })}
- title="Restart your workspace?"
- confirmText="Restart"
- description={
- <>
- Restarting your workspace will stop all running processes and{" "}
- delete non-persistent data.
- >
- }
- />
-
- );
-};
-
-export default WorkspaceParametersPageExperimental;
diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageViewExperimental.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageView.tsx
similarity index 96%
rename from site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageViewExperimental.tsx
rename to site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageView.tsx
index 86234c2e70..5ba346d558 100644
--- a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageViewExperimental.tsx
+++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageView.tsx
@@ -21,7 +21,7 @@ import { cn } from "#/utils/cn";
import { docs } from "#/utils/docs";
import type { AutofillBuildParameter } from "#/utils/richParameters";
-type WorkspaceParametersPageViewExperimentalProps = {
+type WorkspaceParametersPageViewProps = {
workspace: Workspace;
autofillParameters: AutofillBuildParameter[];
parameters: PreviewParameter[];
@@ -37,8 +37,8 @@ type WorkspaceParametersPageViewExperimentalProps = {
templateVersionId: string | undefined;
};
-export const WorkspaceParametersPageViewExperimental: FC<
- WorkspaceParametersPageViewExperimentalProps
+export const WorkspaceParametersPageView: FC<
+ WorkspaceParametersPageViewProps
> = ({
workspace,
autofillParameters,
@@ -189,8 +189,8 @@ export const WorkspaceParametersPageViewExperimental: FC<
className={cn(
"text-xs flex flex-col rounded-md border px-4 pb-3 border-solid",
diagnostic.severity === "error"
- ? " text-content-destructive border-border-destructive"
- : " text-content-warning border-border-warning",
+ ? "text-content-destructive border-border-destructive"
+ : "text-content-warning border-border-warning",
)}
>
diff --git a/site/src/router.tsx b/site/src/router.tsx
index 1b814c4d50..4b2cdd7b39 100644
--- a/site/src/router.tsx
+++ b/site/src/router.tsx
@@ -88,10 +88,10 @@ const WorkspaceSchedulePage = lazy(
"./pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage"
),
);
-const WorkspaceParametersExperimentRouter = lazy(
+const WorkspaceParametersPage = lazy(
() =>
import(
- "./pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersExperimentRouter"
+ "./pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage"
),
);
const WorkspaceSharingPage = lazy(
@@ -724,10 +724,7 @@ export const router = createBrowserRouter(
/>
}>
} />
- }
- />
+ } />
} />
} />
diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts
index af0eb797c2..108889e6fa 100644
--- a/site/src/testHelpers/entities.ts
+++ b/site/src/testHelpers/entities.ts
@@ -1778,12 +1778,22 @@ export const MockDormantOutdatedWorkspace: TypesGen.Workspace = {
dormant_at: new Date().toISOString(),
};
-const MockOutdatedRunningWorkspaceRequireActiveVersion: TypesGen.Workspace = {
- ...MockWorkspace,
- id: "test-outdated-workspace-require-active-version",
- outdated: true,
- template_require_active_version: true,
-};
+export const MockOutdatedRunningWorkspaceRequireActiveVersion: TypesGen.Workspace =
+ {
+ ...MockWorkspace,
+ id: "test-outdated-workspace-require-active-version",
+ outdated: true,
+ template_require_active_version: true,
+ };
+
+export const MockOutdatedStoppedWorkspaceRequireActiveVersion: TypesGen.Workspace =
+ {
+ ...MockOutdatedRunningWorkspaceRequireActiveVersion,
+ latest_build: {
+ ...MockWorkspaceBuild,
+ status: "stopped",
+ },
+ };
const MockOutdatedRunningWorkspaceAlwaysUpdate: TypesGen.Workspace = {
...MockWorkspace,
@@ -1796,15 +1806,6 @@ const MockOutdatedRunningWorkspaceAlwaysUpdate: TypesGen.Workspace = {
},
};
-export const MockOutdatedStoppedWorkspaceRequireActiveVersion: TypesGen.Workspace =
- {
- ...MockOutdatedRunningWorkspaceRequireActiveVersion,
- latest_build: {
- ...MockWorkspaceBuild,
- status: "stopped",
- },
- };
-
export const MockOutdatedStoppedWorkspaceAlwaysUpdate: TypesGen.Workspace = {
...MockOutdatedRunningWorkspaceAlwaysUpdate,
latest_build: {