mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix: implement debounce <WorkspaceParametersPageViewExperimental /> (#22029)
Closes #22028 This pull-request simply takes debounces the message sent to our web-socket backend and debounces it to ensure we're not overwriting the users input as they type. As an added bonus this will debounce message spam if people are going crazy on Radio Items or similar. An extra flavour bit of flavour with resolving a good use-case for `cn()` in diagnostic errors 🙂
This commit is contained in:
+11
-4
@@ -96,11 +96,18 @@ const WorkspaceParametersPageExperimental: FC = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!initialParamsSentRef.current && response.parameters?.length > 0) {
|
||||
sendInitialParameters();
|
||||
// Skip stale responses. If we've already sent a newer request,
|
||||
// this response contains outdated parameter values that would
|
||||
// overwrite the user's more recent input.
|
||||
if (response.id < wsResponseId.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
setLatestResponse(response);
|
||||
|
||||
if (!initialParamsSentRef.current && response.parameters?.length > 0) {
|
||||
sendInitialParameters();
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -197,7 +204,7 @@ const WorkspaceParametersPageExperimental: FC = () => {
|
||||
|
||||
if (
|
||||
latestBuildParametersLoading ||
|
||||
!latestResponse ||
|
||||
(!latestResponse && !wsError) ||
|
||||
(ws.current && ws.current.readyState === WebSocket.CONNECTING)
|
||||
) {
|
||||
return <Loader />;
|
||||
@@ -244,7 +251,7 @@ const WorkspaceParametersPageExperimental: FC = () => {
|
||||
autofillParameters={autofillParameters}
|
||||
canChangeVersions={canChangeVersions}
|
||||
parameters={sortedParams}
|
||||
diagnostics={latestResponse.diagnostics}
|
||||
diagnostics={latestResponse?.diagnostics ?? []}
|
||||
isSubmitting={updateParameters.isPending}
|
||||
onSubmit={handleSubmit}
|
||||
onCancel={() =>
|
||||
|
||||
+41
-23
@@ -9,6 +9,7 @@ import { Label } from "components/Label/Label";
|
||||
import { Link } from "components/Link/Link";
|
||||
import { Spinner } from "components/Spinner/Spinner";
|
||||
import { useFormik } from "formik";
|
||||
import { useDebouncedFunction } from "hooks/debounce";
|
||||
import { useSyncFormParameters } from "modules/hooks/useSyncFormParameters";
|
||||
import {
|
||||
DynamicParameter,
|
||||
@@ -16,6 +17,7 @@ import {
|
||||
useValidationSchemaForDynamicParameters,
|
||||
} from "modules/workspaces/DynamicParameter/DynamicParameter";
|
||||
import type { FC } from "react";
|
||||
import { cn } from "utils/cn";
|
||||
import { docs } from "utils/docs";
|
||||
import type { AutofillBuildParameter } from "utils/richParameters";
|
||||
|
||||
@@ -67,6 +69,23 @@ export const WorkspaceParametersPageViewExperimental: FC<
|
||||
workspace.template_require_active_version &&
|
||||
!canChangeVersions;
|
||||
|
||||
// Debounce websocket sends to avoid stale responses overwriting
|
||||
// the form while the user is still typing.
|
||||
const { debounced: sendDynamicParamsRequest } = useDebouncedFunction(
|
||||
(parameter: PreviewParameter, value: string) => {
|
||||
const formInputs: Record<string, string> = {};
|
||||
const formParameters = form.values.rich_parameter_values ?? [];
|
||||
for (const param of formParameters) {
|
||||
if (param?.name && param?.value) {
|
||||
formInputs[param.name] = param.value;
|
||||
}
|
||||
}
|
||||
formInputs[parameter.name] = value;
|
||||
sendMessage(formInputs);
|
||||
},
|
||||
500,
|
||||
);
|
||||
|
||||
const handleChange = async (
|
||||
parameter: PreviewParameter,
|
||||
parameterField: string,
|
||||
@@ -79,29 +98,27 @@ export const WorkspaceParametersPageViewExperimental: FC<
|
||||
sendDynamicParamsRequest(parameter, value);
|
||||
};
|
||||
|
||||
const sendDynamicParamsRequest = (
|
||||
parameter: PreviewParameter,
|
||||
value: string,
|
||||
) => {
|
||||
const formInputs: Record<string, string> = {};
|
||||
const parameters = form.values.rich_parameter_values ?? [];
|
||||
for (const param of parameters) {
|
||||
if (param?.name && param?.value) {
|
||||
formInputs[param.name] = param.value;
|
||||
}
|
||||
}
|
||||
|
||||
formInputs[parameter.name] = value;
|
||||
|
||||
sendMessage(formInputs);
|
||||
};
|
||||
|
||||
useSyncFormParameters({
|
||||
parameters,
|
||||
formValues: form.values.rich_parameter_values ?? [],
|
||||
setFieldValue: form.setFieldValue,
|
||||
});
|
||||
|
||||
// True when the form holds values the backend hasn't evaluated
|
||||
// yet (debounce pending or WS round-trip in flight).
|
||||
const hasUnsyncedParameters = (form.values.rich_parameter_values ?? []).some(
|
||||
(formParam) => {
|
||||
const responseParam = parameters.find((p) => p.name === formParam.name);
|
||||
if (!responseParam) {
|
||||
return true;
|
||||
}
|
||||
const responseValue = responseParam.value.valid
|
||||
? responseParam.value.value
|
||||
: "";
|
||||
return formParam.value !== responseValue;
|
||||
},
|
||||
);
|
||||
|
||||
const hasIncompatibleParameters = parameters.some((parameter) => {
|
||||
if (!parameter.mutable && parameter.diagnostics.length > 0) {
|
||||
return true;
|
||||
@@ -155,12 +172,12 @@ export const WorkspaceParametersPageViewExperimental: FC<
|
||||
{diagnostics.map((diagnostic, index) => (
|
||||
<div
|
||||
key={`diagnostic-${diagnostic.summary}-${index}`}
|
||||
className={`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"
|
||||
}`}
|
||||
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",
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center m-0">
|
||||
<p className="font-medium">{diagnostic.summary}</p>
|
||||
@@ -248,6 +265,7 @@ export const WorkspaceParametersPageViewExperimental: FC<
|
||||
disabled={
|
||||
isSubmitting ||
|
||||
disabled ||
|
||||
hasUnsyncedParameters ||
|
||||
diagnostics.some(
|
||||
(diagnostic) => diagnostic.severity === "error",
|
||||
) ||
|
||||
|
||||
Reference in New Issue
Block a user