fix: prevent editing build parameters if template requires active version (#11117)

Co-authored-by: McKayla Washburn <mckayla@hey.com>
This commit is contained in:
Jon Ayers
2023-12-11 15:54:16 -07:00
committed by GitHub
co-authored by McKayla Washburn
parent b7ea330aea
commit ba3b835339
3 changed files with 215 additions and 99 deletions
@@ -1,3 +1,7 @@
import { useFormik } from "formik";
import { type FC } from "react";
import * as Yup from "yup";
import { Alert } from "components/Alert/Alert";
import {
FormFields,
FormFooter,
@@ -5,16 +9,14 @@ import {
HorizontalForm,
} from "components/Form/Form";
import { RichParameterInput } from "components/RichParameterInput/RichParameterInput";
import { useFormik } from "formik";
import { FC } from "react";
import {
getInitialRichParameterValues,
useValidationSchemaForRichParameters,
} from "utils/richParameters";
import * as Yup from "yup";
import { getFormHelpers } from "utils/formUtils";
import {
import type {
TemplateVersionParameter,
Workspace,
WorkspaceBuildParameter,
} from "api/typesGenerated";
@@ -22,19 +24,25 @@ export type WorkspaceParametersFormValues = {
rich_parameter_values: WorkspaceBuildParameter[];
};
export const WorkspaceParametersForm: FC<{
isSubmitting: boolean;
interface WorkspaceParameterFormProps {
workspace: Workspace;
templateVersionRichParameters: TemplateVersionParameter[];
buildParameters: WorkspaceBuildParameter[];
isSubmitting: boolean;
canChangeVersions: boolean;
error: unknown;
onCancel: () => void;
onSubmit: (values: WorkspaceParametersFormValues) => void;
}> = ({
}
export const WorkspaceParametersForm: FC<WorkspaceParameterFormProps> = ({
workspace,
onCancel,
onSubmit,
templateVersionRichParameters,
buildParameters,
error,
canChangeVersions,
isSubmitting,
}) => {
const form = useFormik<WorkspaceParametersFormValues>({
@@ -65,97 +73,121 @@ export const WorkspaceParametersForm: FC<{
(parameter) => !parameter.mutable,
);
const disabled =
workspace.outdated &&
workspace.template_require_active_version &&
!canChangeVersions;
return (
<HorizontalForm onSubmit={form.handleSubmit} data-testid="form">
{hasNonEphemeralParameters && (
<FormSection
title="Parameters"
description="Settings used by your template"
>
<FormFields>
{templateVersionRichParameters.map((parameter, index) =>
// Since we are adding the values to the form based on the index
// we can't filter them to not loose the right index position
parameter.mutable && !parameter.ephemeral ? (
<RichParameterInput
{...getFieldHelpers(
"rich_parameter_values[" + index + "].value",
)}
disabled={isSubmitting}
key={parameter.name}
onChange={async (value) => {
await form.setFieldValue("rich_parameter_values." + index, {
name: parameter.name,
value: value,
});
}}
parameter={parameter}
/>
) : null,
)}
</FormFields>
</FormSection>
<>
{disabled && (
<Alert severity="warning" css={{ marginBottom: 48 }}>
The template for this workspace requires automatic updates. Update the
workspace to edit parameters.
</Alert>
)}
{hasEphemeralParameters && (
<FormSection
title="Ephemeral Parameters"
description="These parameters only apply for a single workspace start."
>
<FormFields>
{templateVersionRichParameters.map((parameter, index) =>
// Since we are adding the values to the form based on the index
// we can't filter them to not loose the right index position
parameter.mutable && parameter.ephemeral ? (
<RichParameterInput
{...getFieldHelpers(
"rich_parameter_values[" + index + "].value",
)}
disabled={isSubmitting}
key={parameter.name}
onChange={async (value) => {
await form.setFieldValue("rich_parameter_values." + index, {
name: parameter.name,
value: value,
});
}}
parameter={parameter}
/>
) : null,
)}
</FormFields>
</FormSection>
)}
{/* They are displayed here only for visibility purposes */}
{hasImmutableParameters && (
<FormSection
title="Immutable parameters"
description={
<>
These settings <strong>cannot be changed</strong> after creating
the workspace.
</>
}
>
<FormFields>
{templateVersionRichParameters.map((parameter, index) =>
!parameter.mutable ? (
<RichParameterInput
disabled
{...getFieldHelpers(
"rich_parameter_values[" + index + "].value",
)}
key={parameter.name}
parameter={parameter}
onChange={() => {
throw new Error("Immutable parameters cannot be changed");
}}
/>
) : null,
)}
</FormFields>
</FormSection>
)}
<FormFooter onCancel={onCancel} isLoading={isSubmitting} />
</HorizontalForm>
<HorizontalForm onSubmit={form.handleSubmit} data-testid="form">
{hasNonEphemeralParameters && (
<FormSection
title="Parameters"
description="Settings used by your template"
>
<FormFields>
{templateVersionRichParameters.map((parameter, index) =>
// Since we are adding the values to the form based on the index
// we can't filter them to not loose the right index position
parameter.mutable && !parameter.ephemeral ? (
<RichParameterInput
{...getFieldHelpers(
"rich_parameter_values[" + index + "].value",
)}
disabled={isSubmitting || disabled}
key={parameter.name}
onChange={async (value) => {
await form.setFieldValue(
"rich_parameter_values." + index,
{
name: parameter.name,
value: value,
},
);
}}
parameter={parameter}
/>
) : null,
)}
</FormFields>
</FormSection>
)}
{hasEphemeralParameters && (
<FormSection
title="Ephemeral Parameters"
description="These parameters only apply for a single workspace start."
>
<FormFields>
{templateVersionRichParameters.map((parameter, index) =>
// Since we are adding the values to the form based on the index
// we can't filter them to not loose the right index position
parameter.mutable && parameter.ephemeral ? (
<RichParameterInput
{...getFieldHelpers(
"rich_parameter_values[" + index + "].value",
)}
disabled={isSubmitting || disabled}
key={parameter.name}
onChange={async (value) => {
await form.setFieldValue(
"rich_parameter_values." + index,
{
name: parameter.name,
value: value,
},
);
}}
parameter={parameter}
/>
) : null,
)}
</FormFields>
</FormSection>
)}
{/* They are displayed here only for visibility purposes */}
{hasImmutableParameters && (
<FormSection
title="Immutable parameters"
description={
<>
These settings <strong>cannot be changed</strong> after creating
the workspace.
</>
}
>
<FormFields>
{templateVersionRichParameters.map((parameter, index) =>
!parameter.mutable ? (
<RichParameterInput
disabled
{...getFieldHelpers(
"rich_parameter_values[" + index + "].value",
)}
key={parameter.name}
parameter={parameter}
onChange={() => {
throw new Error("Immutable parameters cannot be changed");
}}
/>
) : null,
)}
</FormFields>
</FormSection>
)}
<FormFooter
onCancel={onCancel}
isLoading={isSubmitting}
submitDisabled={disabled}
/>
</HorizontalForm>
</>
);
};
@@ -7,6 +7,8 @@ import {
MockTemplateVersionParameter2,
MockTemplateVersionParameter3,
MockWorkspaceBuildParameter3,
MockWorkspace,
MockOutdatedStoppedWorkspaceRequireActiveVersion,
} from "testHelpers/entities";
const meta: Meta<typeof WorkspaceParametersPageView> = {
@@ -15,6 +17,8 @@ const meta: Meta<typeof WorkspaceParametersPageView> = {
args: {
submitError: undefined,
isSubmitting: false,
workspace: MockWorkspace,
canChangeVersions: true,
data: {
buildParameters: [
@@ -48,4 +52,48 @@ export const Empty: Story = {
},
};
export const RequireActiveVersionNoChangeVersion: Story = {
args: {
workspace: MockOutdatedStoppedWorkspaceRequireActiveVersion,
canChangeVersions: false,
data: {
buildParameters: [
MockWorkspaceBuildParameter1,
MockWorkspaceBuildParameter2,
MockWorkspaceBuildParameter3,
],
templateVersionRichParameters: [
MockTemplateVersionParameter1,
MockTemplateVersionParameter2,
{
...MockTemplateVersionParameter3,
mutable: false,
},
],
},
},
};
export const RequireActiveVersionCanChangeVersion: Story = {
args: {
workspace: MockOutdatedStoppedWorkspaceRequireActiveVersion,
canChangeVersions: true,
data: {
buildParameters: [
MockWorkspaceBuildParameter1,
MockWorkspaceBuildParameter2,
MockWorkspaceBuildParameter3,
],
templateVersionRichParameters: [
MockTemplateVersionParameter1,
MockTemplateVersionParameter2,
{
...MockTemplateVersionParameter3,
mutable: false,
},
],
},
},
};
export { Example as WorkspaceParametersPage };
@@ -1,7 +1,13 @@
import { getWorkspaceParameters, postWorkspaceBuild } from "api/api";
import { Helmet } from "react-helmet-async";
import { pageTitle } from "utils/page";
import {
WorkspacePermissions,
workspaceChecks,
} from "../../WorkspacePage/permissions";
import { checkAuthorization } from "api/queries/authCheck";
import { useWorkspaceSettings } from "../WorkspaceSettingsLayout";
import { templateByName } from "api/queries/templates";
import { useMutation, useQuery } from "react-query";
import { Loader } from "components/Loader/Loader";
import {
@@ -13,7 +19,7 @@ import { PageHeader, PageHeaderTitle } from "components/PageHeader/PageHeader";
import { type FC } from "react";
import { isApiValidationError } from "api/errors";
import { ErrorAlert } from "components/Alert/ErrorAlert";
import { WorkspaceBuildParameter } from "api/typesGenerated";
import { Workspace, WorkspaceBuildParameter } from "api/typesGenerated";
import { EmptyState } from "components/EmptyState/EmptyState";
import Button from "@mui/material/Button";
import OpenInNewOutlined from "@mui/icons-material/OpenInNewOutlined";
@@ -37,6 +43,22 @@ const WorkspaceParametersPage: FC = () => {
},
});
const templateQuery = useQuery({
...templateByName(workspace.organization_id, workspace.template_name ?? ""),
enabled: workspace !== undefined,
});
const template = templateQuery.data;
// Permissions
const checks =
workspace && template ? workspaceChecks(workspace, template) : {};
const permissionsQuery = useQuery({
...checkAuthorization({ checks }),
enabled: workspace !== undefined && template !== undefined,
});
const permissions = permissionsQuery.data as WorkspacePermissions | undefined;
const canChangeVersions = Boolean(permissions?.updateTemplate);
return (
<>
<Helmet>
@@ -44,6 +66,8 @@ const WorkspaceParametersPage: FC = () => {
</Helmet>
<WorkspaceParametersPageView
workspace={workspace}
canChangeVersions={canChangeVersions}
data={parameters.data}
submitError={updateParameters.error}
isSubmitting={updateParameters.isLoading}
@@ -67,6 +91,8 @@ const WorkspaceParametersPage: FC = () => {
};
export type WorkspaceParametersPageViewProps = {
workspace: Workspace;
canChangeVersions: boolean;
data: Awaited<ReturnType<typeof getWorkspaceParameters>> | undefined;
submitError: unknown;
isSubmitting: boolean;
@@ -76,7 +102,15 @@ export type WorkspaceParametersPageViewProps = {
export const WorkspaceParametersPageView: FC<
WorkspaceParametersPageViewProps
> = ({ data, submitError, isSubmitting, onSubmit, onCancel }) => {
> = ({
workspace,
canChangeVersions,
data,
submitError,
onSubmit,
isSubmitting,
onCancel,
}) => {
return (
<>
<PageHeader css={{ paddingTop: 0 }}>
@@ -90,6 +124,8 @@ export const WorkspaceParametersPageView: FC<
{data ? (
data.templateVersionRichParameters.length > 0 ? (
<WorkspaceParametersForm
workspace={workspace}
canChangeVersions={canChangeVersions}
buildParameters={data.buildParameters}
templateVersionRichParameters={data.templateVersionRichParameters}
error={submitError}