Skip to content

Commit 80e9dbd

Browse files
committed
chore: cleanup, update validation
1 parent 54f1b7d commit 80e9dbd

File tree

3 files changed

+187
-38
lines changed

3 files changed

+187
-38
lines changed

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

+174-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import type { Parameter, ParameterOption } from "api/typesParameter";
1+
import type { WorkspaceBuildParameter } from "api/typesGenerated";
2+
import type {
3+
Parameter,
4+
ParameterOption,
5+
ParameterValidation,
6+
} from "api/typesParameter";
27
import { Badge } from "components/Badge/Badge";
38
import { Checkbox } from "components/Checkbox/Checkbox";
49
import { ExternalImage } from "components/ExternalImage/ExternalImage";
@@ -26,6 +31,7 @@ import {
2631
} from "components/Tooltip/Tooltip";
2732
import { Info, Settings, TriangleAlert } from "lucide-react";
2833
import { type FC, useId } from "react";
34+
import * as Yup from "yup";
2935

3036
export interface DynamicParameterProps {
3137
parameter: Parameter;
@@ -324,7 +330,9 @@ const OptionDisplay: FC<OptionDisplayProps> = ({ option }) => {
324330
<TooltipTrigger asChild>
325331
<Info className="w-3.5 h-3.5 text-content-secondary" />
326332
</TooltipTrigger>
327-
<TooltipContent>{option.description}</TooltipContent>
333+
<TooltipContent side="right" sideOffset={10}>
334+
{option.description}
335+
</TooltipContent>
328336
</Tooltip>
329337
</TooltipProvider>
330338
)}
@@ -357,3 +365,167 @@ const ParameterDiagnostics: FC<ParameterDiagnosticsProps> = ({
357365
</div>
358366
);
359367
};
368+
369+
export const useValidationSchemaForDynamicParameters = (
370+
parameters?: Parameter[],
371+
lastBuildParameters?: WorkspaceBuildParameter[],
372+
): Yup.AnySchema => {
373+
if (!parameters) {
374+
return Yup.object();
375+
}
376+
377+
return Yup.array()
378+
.of(
379+
Yup.object().shape({
380+
name: Yup.string().required(),
381+
value: Yup.string()
382+
.test("verify with template", (val, ctx) => {
383+
const name = ctx.parent.name;
384+
const parameter = parameters.find(
385+
(parameter) => parameter.name === name,
386+
);
387+
if (parameter) {
388+
switch (parameter.type) {
389+
case "number": {
390+
const minValidation = parameter.validations.find(
391+
(v) => v.validation_min !== null,
392+
);
393+
const maxValidation = parameter.validations.find(
394+
(v) => v.validation_max !== null,
395+
);
396+
397+
if (
398+
minValidation?.validation_min &&
399+
!maxValidation &&
400+
Number(val) < minValidation.validation_min
401+
) {
402+
return ctx.createError({
403+
path: ctx.path,
404+
message:
405+
parameterError(parameter, val) ??
406+
`Value must be greater than ${minValidation.validation_min}.`,
407+
});
408+
}
409+
410+
if (
411+
!minValidation &&
412+
maxValidation?.validation_max &&
413+
Number(val) > maxValidation.validation_max
414+
) {
415+
return ctx.createError({
416+
path: ctx.path,
417+
message:
418+
parameterError(parameter, val) ??
419+
`Value must be less than ${maxValidation.validation_max}.`,
420+
});
421+
}
422+
423+
if (
424+
minValidation?.validation_min &&
425+
maxValidation?.validation_max &&
426+
(Number(val) < minValidation.validation_min ||
427+
Number(val) > maxValidation.validation_max)
428+
) {
429+
return ctx.createError({
430+
path: ctx.path,
431+
message:
432+
parameterError(parameter, val) ??
433+
`Value must be between ${minValidation.validation_min} and ${maxValidation.validation_max}.`,
434+
});
435+
}
436+
437+
const monotonicValidation = parameter.validations.find(
438+
(v) => v.validation_monotonic !== null,
439+
);
440+
if (
441+
monotonicValidation?.validation_monotonic &&
442+
lastBuildParameters
443+
) {
444+
const lastBuildParameter = lastBuildParameters.find(
445+
(last: { name: string }) => last.name === name,
446+
);
447+
if (lastBuildParameter) {
448+
switch (monotonicValidation.validation_monotonic) {
449+
case "increasing":
450+
if (Number(lastBuildParameter.value) > Number(val)) {
451+
return ctx.createError({
452+
path: ctx.path,
453+
message: `Value must only ever increase (last value was ${lastBuildParameter.value})`,
454+
});
455+
}
456+
break;
457+
case "decreasing":
458+
if (Number(lastBuildParameter.value) < Number(val)) {
459+
return ctx.createError({
460+
path: ctx.path,
461+
message: `Value must only ever decrease (last value was ${lastBuildParameter.value})`,
462+
});
463+
}
464+
break;
465+
}
466+
}
467+
}
468+
break;
469+
}
470+
case "string": {
471+
const regexValidation = parameter.validations.find(
472+
(v) => v.validation_regex !== null,
473+
);
474+
if (!regexValidation?.validation_regex) {
475+
return true;
476+
}
477+
478+
if (
479+
val &&
480+
!new RegExp(regexValidation.validation_regex).test(val)
481+
) {
482+
return ctx.createError({
483+
path: ctx.path,
484+
message: parameterError(parameter, val),
485+
});
486+
}
487+
break;
488+
}
489+
}
490+
}
491+
return true;
492+
}),
493+
}),
494+
)
495+
.required();
496+
};
497+
498+
const parameterError = (
499+
parameter: Parameter,
500+
value?: string,
501+
): string | undefined => {
502+
const validation_error = parameter.validations.find(
503+
(v) => v.validation_error !== null,
504+
);
505+
const minValidation = parameter.validations.find(
506+
(v) => v.validation_min !== null,
507+
);
508+
const maxValidation = parameter.validations.find(
509+
(v) => v.validation_max !== null,
510+
);
511+
512+
if (!validation_error || !value) {
513+
return;
514+
}
515+
516+
const r = new Map<string, string>([
517+
[
518+
"{min}",
519+
minValidation ? (minValidation.validation_min?.toString() ?? "") : "",
520+
],
521+
[
522+
"{max}",
523+
maxValidation ? (maxValidation.validation_max?.toString() ?? "") : "",
524+
],
525+
["{value}", value],
526+
]);
527+
return validation_error.validation_error.replace(
528+
/{min}|{max}|{value}/g,
529+
(match) => r.get(match) || "",
530+
);
531+
};

site/src/pages/CreateWorkspacePage/CreateWorkspacePageExperimental.tsx

+4-23
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
11
import type { ApiErrorResponse } from "api/errors";
22
import { checkAuthorization } from "api/queries/authCheck";
33
import {
4-
richParameters,
54
templateByName,
65
templateVersionExternalAuth,
76
templateVersionPresets,
87
} from "api/queries/templates";
98
import { autoCreateWorkspace, createWorkspace } from "api/queries/workspaces";
10-
import type {
11-
Template,
12-
TemplateVersionParameter,
13-
Workspace,
14-
} from "api/typesGenerated";
9+
import type { Template, Workspace } from "api/typesGenerated";
1510
import { Loader } from "components/Loader/Loader";
1611
import { useAuthenticated } from "contexts/auth/RequireAuth";
1712
import { useEffectEvent } from "hooks/hookPolyfills";
@@ -29,7 +24,6 @@ import { useMutation, useQuery, useQueryClient } from "react-query";
2924
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
3025
import { pageTitle } from "utils/page";
3126
import type { AutofillBuildParameter } from "utils/richParameters";
32-
import { paramsUsedToCreateWorkspace } from "utils/workspace";
3327
import { CreateWorkspacePageViewExperimental } from "./CreateWorkspacePageViewExperimental";
3428
export const createWorkspaceModes = ["form", "auto", "duplicate"] as const;
3529
export type CreateWorkspaceMode = (typeof createWorkspaceModes)[number];
@@ -101,14 +95,8 @@ const CreateWorkspacePageExperimental: FC = () => {
10195
);
10296
const realizedVersionId =
10397
customVersionId ?? templateQuery.data?.active_version_id;
98+
10499
const organizationId = templateQuery.data?.organization_id;
105-
const richParametersQuery = useQuery({
106-
...richParameters(realizedVersionId ?? ""),
107-
enabled: realizedVersionId !== undefined,
108-
});
109-
const realizedParameters = richParametersQuery.data
110-
? richParametersQuery.data.filter(paramsUsedToCreateWorkspace)
111-
: undefined;
112100

113101
const {
114102
externalAuth,
@@ -118,11 +106,8 @@ const CreateWorkspacePageExperimental: FC = () => {
118106
} = useExternalAuth(realizedVersionId);
119107

120108
const isLoadingFormData =
121-
templateQuery.isLoading ||
122-
permissionsQuery.isLoading ||
123-
richParametersQuery.isLoading;
124-
const loadFormDataError =
125-
templateQuery.error ?? permissionsQuery.error ?? richParametersQuery.error;
109+
templateQuery.isLoading || permissionsQuery.isLoading;
110+
const loadFormDataError = templateQuery.error ?? permissionsQuery.error;
126111

127112
const title = autoCreateWorkspaceMutation.isLoading
128113
? "Creating workspace..."
@@ -205,7 +190,6 @@ const CreateWorkspacePageExperimental: FC = () => {
205190
return [...currentResponse.parameters].sort((a, b) => a.order - b.order);
206191
}, [currentResponse?.parameters]);
207192

208-
// console.log("sortedParams", sortedParams);
209193
return (
210194
<>
211195
<Helmet>
@@ -238,9 +222,6 @@ const CreateWorkspacePageExperimental: FC = () => {
238222
startPollingExternalAuth={startPollingExternalAuth}
239223
hasAllRequiredExternalAuth={hasAllRequiredExternalAuth}
240224
permissions={permissionsQuery.data as CreateWorkspacePermissions}
241-
templateVersionParameters={
242-
realizedParameters as TemplateVersionParameter[]
243-
}
244225
parameters={sortedParams}
245226
presets={templateVersionPresetsQuery.data ?? []}
246227
creatingWorkspace={createWorkspaceMutation.isLoading}

site/src/pages/CreateWorkspacePage/CreateWorkspacePageViewExperimental.tsx

+9-13
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ import { Switch } from "components/Switch/Switch";
1515
import { UserAutocomplete } from "components/UserAutocomplete/UserAutocomplete";
1616
import { type FormikContextType, useFormik } from "formik";
1717
import { ArrowLeft } from "lucide-react";
18-
import { DynamicParameter } from "modules/workspaces/DynamicParameter/DynamicParameter";
18+
import {
19+
DynamicParameter,
20+
useValidationSchemaForDynamicParameters,
21+
} from "modules/workspaces/DynamicParameter/DynamicParameter";
1922
import { generateWorkspaceName } from "modules/workspaces/generateWorkspaceName";
2023
import {
2124
type FC,
@@ -26,11 +29,7 @@ import {
2629
useState,
2730
} from "react";
2831
import { getFormHelpers, nameValidator } from "utils/formUtils";
29-
import {
30-
type AutofillBuildParameter,
31-
getInitialRichParameterValues,
32-
useValidationSchemaForRichParameters,
33-
} from "utils/richParameters";
32+
import type { AutofillBuildParameter } from "utils/richParameters";
3433
import * as Yup from "yup";
3534
import type {
3635
CreateWorkspaceMode,
@@ -60,7 +59,6 @@ export interface CreateWorkspacePageViewExperimentalProps {
6059
permissions: CreateWorkspacePermissions;
6160
presets: TypesGen.Preset[];
6261
template: TypesGen.Template;
63-
templateVersionParameters: TypesGen.TemplateVersionParameter[];
6462
versionId?: string;
6563
onCancel: () => void;
6664
onSubmit: (
@@ -73,7 +71,7 @@ export interface CreateWorkspacePageViewExperimentalProps {
7371
startPollingExternalAuth: () => void;
7472
}
7573

76-
// const getInitialParameterValues = (
74+
// const getInitialParameterValues1 = (
7775
// params: Parameter[],
7876
// autofillParams?: AutofillBuildParameter[],
7977
// ): WorkspaceBuildParameter[] => {
@@ -95,7 +93,7 @@ export interface CreateWorkspacePageViewExperimentalProps {
9593
// name: parameter.name,
9694
// value:
9795
// autofillParam &&
98-
// // isValidValue(parameter, autofillParam) &&
96+
// isValidValue(parameter, autofillParam) &&
9997
// autofillParam.source !== "user_history"
10098
// ? autofillParam.value
10199
// : parameter.default_value,
@@ -129,7 +127,6 @@ export const CreateWorkspacePageViewExperimental: FC<
129127
permissions,
130128
presets = [],
131129
template,
132-
templateVersionParameters,
133130
versionId,
134131
onSubmit,
135132
onCancel,
@@ -157,9 +154,8 @@ export const CreateWorkspacePageViewExperimental: FC<
157154
},
158155
validationSchema: Yup.object({
159156
name: nameValidator("Workspace Name"),
160-
rich_parameter_values: useValidationSchemaForRichParameters(
161-
templateVersionParameters,
162-
),
157+
rich_parameter_values:
158+
useValidationSchemaForDynamicParameters(parameters),
163159
}),
164160
enableReinitialize: true,
165161
validateOnChange: false,

0 commit comments

Comments
 (0)