Skip to content

Commit 3d787da

Browse files
authored
feat: setup connection to dynamic parameters websocket (#17393)
resolves coder/preview#57
1 parent f670bc3 commit 3d787da

File tree

4 files changed

+94
-31
lines changed

4 files changed

+94
-31
lines changed

site/src/api/api.ts

+26
Original file line numberDiff line numberDiff line change
@@ -1009,6 +1009,32 @@ class ApiMethods {
10091009
return response.data;
10101010
};
10111011

1012+
templateVersionDynamicParameters = (
1013+
versionId: string,
1014+
{
1015+
onMessage,
1016+
onError,
1017+
}: {
1018+
onMessage: (response: TypesGen.DynamicParametersResponse) => void;
1019+
onError: (error: Error) => void;
1020+
},
1021+
): WebSocket => {
1022+
const socket = createWebSocket(
1023+
`/api/v2/templateversions/${versionId}/dynamic-parameters`,
1024+
);
1025+
1026+
socket.addEventListener("message", (event) =>
1027+
onMessage(JSON.parse(event.data) as TypesGen.DynamicParametersResponse),
1028+
);
1029+
1030+
socket.addEventListener("error", () => {
1031+
onError(new Error("Connection for dynamic parameters failed."));
1032+
socket.close();
1033+
});
1034+
1035+
return socket;
1036+
};
1037+
10121038
/**
10131039
* @param organization Can be the organization's ID or name
10141040
*/

site/src/modules/workspaces/DynamicParameter/DynamicParameter.tsx

+12-11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type {
2+
NullHCLString,
23
PreviewParameter,
34
PreviewParameterOption,
45
WorkspaceBuildParameter,
@@ -156,10 +157,8 @@ const ParameterField: FC<ParameterFieldProps> = ({
156157
disabled,
157158
id,
158159
}) => {
159-
const value = parameter.value.valid ? parameter.value.value : "";
160-
const defaultValue = parameter.default_value.valid
161-
? parameter.default_value.value
162-
: "";
160+
const value = validValue(parameter.value);
161+
const defaultValue = validValue(parameter.default_value);
163162

164163
switch (parameter.form_type) {
165164
case "dropdown":
@@ -376,9 +375,7 @@ export const getInitialParameterValues = (
376375
if (parameter.ephemeral) {
377376
return {
378377
name: parameter.name,
379-
value: parameter.default_value.valid
380-
? parameter.default_value.value
381-
: "",
378+
value: validValue(parameter.default_value),
382379
};
383380
}
384381

@@ -390,15 +387,19 @@ export const getInitialParameterValues = (
390387
name: parameter.name,
391388
value:
392389
autofillParam &&
393-
isValidValue(parameter, autofillParam) &&
390+
isValidParameterOption(parameter, autofillParam) &&
394391
autofillParam.value
395392
? autofillParam.value
396-
: "",
393+
: validValue(parameter.default_value),
397394
};
398395
});
399396
};
400397

401-
const isValidValue = (
398+
const validValue = (value: NullHCLString) => {
399+
return value.valid ? value.value : "";
400+
};
401+
402+
const isValidParameterOption = (
402403
previewParam: PreviewParameter,
403404
buildParam: WorkspaceBuildParameter,
404405
) => {
@@ -409,7 +410,7 @@ const isValidValue = (
409410
return validValues.includes(buildParam.value);
410411
}
411412

412-
return true;
413+
return false;
413414
};
414415

415416
export const useValidationSchemaForDynamicParameters = (

site/src/pages/CreateWorkspacePage/CreateWorkspacePageExperimental.tsx

+52-6
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import { autoCreateWorkspace, createWorkspace } from "api/queries/workspaces";
99
import type {
1010
DynamicParametersRequest,
1111
DynamicParametersResponse,
12-
Template,
1312
Workspace,
1413
} from "api/typesGenerated";
1514
import { Loader } from "components/Loader/Loader";
@@ -32,6 +31,7 @@ import type { AutofillBuildParameter } from "utils/richParameters";
3231
import { CreateWorkspacePageViewExperimental } from "./CreateWorkspacePageViewExperimental";
3332
export const createWorkspaceModes = ["form", "auto", "duplicate"] as const;
3433
export type CreateWorkspaceMode = (typeof createWorkspaceModes)[number];
34+
import { API } from "api/api";
3535
import {
3636
type CreateWorkspacePermissions,
3737
createWorkspaceChecks,
@@ -47,8 +47,9 @@ const CreateWorkspacePageExperimental: FC = () => {
4747

4848
const [currentResponse, setCurrentResponse] =
4949
useState<DynamicParametersResponse | null>(null);
50-
const [wsResponseId, setWSResponseId] = useState<number>(0);
51-
const sendMessage = (message: DynamicParametersRequest) => {};
50+
const [wsResponseId, setWSResponseId] = useState<number>(-1);
51+
const ws = useRef<WebSocket | null>(null);
52+
const [wsError, setWsError] = useState<Error | null>(null);
5253

5354
const customVersionId = searchParams.get("version") ?? undefined;
5455
const defaultName = searchParams.get("name");
@@ -80,6 +81,49 @@ const CreateWorkspacePageExperimental: FC = () => {
8081
const realizedVersionId =
8182
customVersionId ?? templateQuery.data?.active_version_id;
8283

84+
const onMessage = useCallback((response: DynamicParametersResponse) => {
85+
setCurrentResponse((prev) => {
86+
if (prev?.id === response.id) {
87+
return prev;
88+
}
89+
return response;
90+
});
91+
}, []);
92+
93+
// Initialize the WebSocket connection when there is a valid template version ID
94+
useEffect(() => {
95+
if (!realizedVersionId) {
96+
return;
97+
}
98+
99+
const socket = API.templateVersionDynamicParameters(realizedVersionId, {
100+
onMessage,
101+
onError: (error) => {
102+
setWsError(error);
103+
},
104+
});
105+
106+
ws.current = socket;
107+
108+
return () => {
109+
socket.close();
110+
};
111+
}, [realizedVersionId, onMessage]);
112+
113+
const sendMessage = useCallback((formValues: Record<string, string>) => {
114+
setWSResponseId((prevId) => {
115+
const request: DynamicParametersRequest = {
116+
id: prevId + 1,
117+
inputs: formValues,
118+
};
119+
if (ws.current && ws.current.readyState === WebSocket.OPEN) {
120+
ws.current.send(JSON.stringify(request));
121+
return prevId + 1;
122+
}
123+
return prevId;
124+
});
125+
}, []);
126+
83127
const organizationId = templateQuery.data?.organization_id;
84128

85129
const {
@@ -90,7 +134,9 @@ const CreateWorkspacePageExperimental: FC = () => {
90134
} = useExternalAuth(realizedVersionId);
91135

92136
const isLoadingFormData =
93-
templateQuery.isLoading || permissionsQuery.isLoading;
137+
ws.current?.readyState !== WebSocket.OPEN ||
138+
templateQuery.isLoading ||
139+
permissionsQuery.isLoading;
94140
const loadFormDataError = templateQuery.error ?? permissionsQuery.error;
95141

96142
const title = autoCreateWorkspaceMutation.isLoading
@@ -189,11 +235,12 @@ const CreateWorkspacePageExperimental: FC = () => {
189235
<CreateWorkspacePageViewExperimental
190236
mode={mode}
191237
defaultName={defaultName}
192-
diagnostics={currentResponse.diagnostics}
238+
diagnostics={currentResponse?.diagnostics ?? []}
193239
disabledParams={disabledParams}
194240
defaultOwner={me}
195241
autofillParameters={autofillParameters}
196242
error={
243+
wsError ||
197244
createWorkspaceMutation.error ||
198245
autoCreateError ||
199246
loadFormDataError ||
@@ -210,7 +257,6 @@ const CreateWorkspacePageExperimental: FC = () => {
210257
parameters={sortedParams}
211258
presets={templateVersionPresetsQuery.data ?? []}
212259
creatingWorkspace={createWorkspaceMutation.isLoading}
213-
setWSResponseId={setWSResponseId}
214260
sendMessage={sendMessage}
215261
onCancel={() => {
216262
navigate(-1);

site/src/pages/CreateWorkspacePage/CreateWorkspacePageViewExperimental.tsx

+4-14
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,7 @@ export interface CreateWorkspacePageViewExperimentalProps {
6767
owner: TypesGen.User,
6868
) => void;
6969
resetMutation: () => void;
70-
sendMessage: (message: DynamicParametersRequest) => void;
71-
setWSResponseId: (value: React.SetStateAction<number>) => void;
70+
sendMessage: (message: Record<string, string>) => void;
7271
startPollingExternalAuth: () => void;
7372
}
7473

@@ -95,7 +94,6 @@ export const CreateWorkspacePageViewExperimental: FC<
9594
onCancel,
9695
resetMutation,
9796
sendMessage,
98-
setWSResponseId,
9997
startPollingExternalAuth,
10098
}) => {
10199
const [owner, setOwner] = useState(defaultOwner);
@@ -222,15 +220,7 @@ export const CreateWorkspacePageViewExperimental: FC<
222220
// Update the input for the changed parameter
223221
formInputs[parameter.name] = value;
224222

225-
setWSResponseId((prevId) => {
226-
const newId = prevId + 1;
227-
const request: DynamicParametersRequest = {
228-
id: newId,
229-
inputs: formInputs,
230-
};
231-
sendMessage(request);
232-
return newId;
233-
});
223+
sendMessage(formInputs);
234224
};
235225

236226
const { debounced: handleChangeDebounced } = useDebouncedFunction(
@@ -240,7 +230,7 @@ export const CreateWorkspacePageViewExperimental: FC<
240230
value: string,
241231
) => {
242232
await form.setFieldValue(parameterField, {
243-
name: parameter.form_type,
233+
name: parameter.name,
244234
value,
245235
});
246236
sendDynamicParamsRequest(parameter, value);
@@ -257,7 +247,7 @@ export const CreateWorkspacePageViewExperimental: FC<
257247
handleChangeDebounced(parameter, parameterField, value);
258248
} else {
259249
await form.setFieldValue(parameterField, {
260-
name: parameter.form_type,
250+
name: parameter.name,
261251
value,
262252
});
263253
sendDynamicParamsRequest(parameter, value);

0 commit comments

Comments
 (0)