Files
coder/site/src/utils/richParameters.ts
T

205 lines
5.6 KiB
TypeScript

import type {
TemplateVersionParameter,
WorkspaceBuildParameter,
} from "api/typesGenerated";
import * as Yup from "yup";
export type AutofillSource = "user_history" | "url" | "active_build";
// AutofillBuildParameter is a build parameter destined to a form, alongside
// its source so that the form can explain where the value comes from.
export type AutofillBuildParameter = {
source: AutofillSource;
} & WorkspaceBuildParameter;
export const getInitialRichParameterValues = (
templateParams: TemplateVersionParameter[],
autofillParams?: AutofillBuildParameter[],
): WorkspaceBuildParameter[] => {
return templateParams.map((parameter) => {
// Short-circuit for ephemeral parameters, which are always reset to
// the template-defined default.
if (parameter.ephemeral) {
return {
name: parameter.name,
value: parameter.default_value,
};
}
const autofillParam = autofillParams?.find(
({ name }) => name === parameter.name,
);
return {
name: parameter.name,
value:
autofillParam &&
isValidValue(parameter, autofillParam) &&
autofillParam.source !== "user_history"
? autofillParam.value
: parameter.default_value,
};
});
};
const isValidValue = (
templateParam: TemplateVersionParameter,
buildParam: WorkspaceBuildParameter,
) => {
if (templateParam.options.length > 0) {
const validValues = templateParam.options.map((option) => option.value);
return validValues.includes(buildParam.value);
}
return true;
};
export const useValidationSchemaForRichParameters = (
templateParameters?: TemplateVersionParameter[],
lastBuildParameters?: WorkspaceBuildParameter[],
): Yup.AnySchema => {
if (!templateParameters) {
return Yup.object();
}
return Yup.array()
.of(
Yup.object().shape({
name: Yup.string().required(),
value: Yup.string()
.test("verify with template", (val, ctx) => {
const name = ctx.parent.name;
const templateParameter = templateParameters.find(
(parameter) => parameter.name === name,
);
if (templateParameter) {
switch (templateParameter.type) {
case "number":
if (
templateParameter.validation_min &&
!templateParameter.validation_max
) {
if (Number(val) < templateParameter.validation_min) {
return ctx.createError({
path: ctx.path,
message:
parameterError(templateParameter, val) ??
`Value must be greater than ${templateParameter.validation_min}.`,
});
}
} else if (
!templateParameter.validation_min &&
templateParameter.validation_max
) {
if (templateParameter.validation_max < Number(val)) {
return ctx.createError({
path: ctx.path,
message:
parameterError(templateParameter, val) ??
`Value must be less than ${templateParameter.validation_max}.`,
});
}
} else if (
templateParameter.validation_min &&
templateParameter.validation_max
) {
if (
Number(val) < templateParameter.validation_min ||
templateParameter.validation_max < Number(val)
) {
return ctx.createError({
path: ctx.path,
message:
parameterError(templateParameter, val) ??
`Value must be between ${templateParameter.validation_min} and ${templateParameter.validation_max}.`,
});
}
}
if (
templateParameter.validation_monotonic &&
lastBuildParameters
) {
const lastBuildParameter = lastBuildParameters.find(
(last) => last.name === name,
);
if (lastBuildParameter) {
switch (templateParameter.validation_monotonic) {
case "increasing":
if (Number(lastBuildParameter.value) > Number(val)) {
return ctx.createError({
path: ctx.path,
message: `Value must only ever increase (last value was ${lastBuildParameter.value})`,
});
}
break;
case "decreasing":
if (Number(lastBuildParameter.value) < Number(val)) {
return ctx.createError({
path: ctx.path,
message: `Value must only ever decrease (last value was ${lastBuildParameter.value})`,
});
}
break;
}
}
}
break;
case "string":
{
if (
!templateParameter.validation_regex ||
templateParameter.validation_regex.length === 0
) {
return true;
}
const regex = new RegExp(
templateParameter.validation_regex,
);
if (val && !regex.test(val)) {
return ctx.createError({
path: ctx.path,
message: parameterError(templateParameter, val),
});
}
}
break;
}
}
return true;
}),
}),
)
.required();
};
const parameterError = (
parameter: TemplateVersionParameter,
value?: string,
): string | undefined => {
if (!parameter.validation_error || !value) {
return;
}
const r = new Map<string, string>([
[
"{min}",
parameter.validation_min !== undefined
? parameter.validation_min.toString()
: "",
],
[
"{max}",
parameter.validation_max !== undefined
? parameter.validation_max.toString()
: "",
],
["{value}", value],
]);
return parameter.validation_error.replace(
/{min}|{max}|{value}/g,
(match) => r.get(match) || "",
);
};