From 036c2dc4cb559d716d6d6fda8e232764b7c9b98f Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Sun, 10 Dec 2023 22:51:14 +0000 Subject: [PATCH 1/6] fix: prevent editing build parameters if template requires active version --- .../WorkspaceParametersForm.tsx | 20 ++++++-- .../WorkspaceParametersPage.tsx | 48 ++++++++++++++++++- 2 files changed, 62 insertions(+), 6 deletions(-) diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersForm.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersForm.tsx index e281a740ed9ce..7992d4ed22b39 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersForm.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersForm.tsx @@ -15,6 +15,7 @@ import * as Yup from "yup"; import { getFormHelpers } from "utils/formUtils"; import { TemplateVersionParameter, + Workspace, WorkspaceBuildParameter, } from "api/typesGenerated"; @@ -23,18 +24,22 @@ export type WorkspaceParametersFormValues = { }; export const WorkspaceParametersForm: FC<{ - isSubmitting: boolean; + workspace: Workspace; templateVersionRichParameters: TemplateVersionParameter[]; buildParameters: WorkspaceBuildParameter[]; + isSubmitting: boolean; + canChangeVersions: boolean; error: unknown; onCancel: () => void; onSubmit: (values: WorkspaceParametersFormValues) => void; }> = ({ + workspace, onCancel, onSubmit, templateVersionRichParameters, buildParameters, error, + canChangeVersions, isSubmitting, }) => { const form = useFormik({ @@ -65,6 +70,9 @@ export const WorkspaceParametersForm: FC<{ (parameter) => !parameter.mutable, ); + const disabled = + workspace.template_require_active_version && !canChangeVersions; + return ( {hasNonEphemeralParameters && ( @@ -81,7 +89,7 @@ export const WorkspaceParametersForm: FC<{ {...getFieldHelpers( "rich_parameter_values[" + index + "].value", )} - disabled={isSubmitting} + disabled={isSubmitting || disabled} key={parameter.name} onChange={async (value) => { await form.setFieldValue("rich_parameter_values." + index, { @@ -110,7 +118,7 @@ export const WorkspaceParametersForm: FC<{ {...getFieldHelpers( "rich_parameter_values[" + index + "].value", )} - disabled={isSubmitting} + disabled={isSubmitting || disabled} key={parameter.name} onChange={async (value) => { await form.setFieldValue("rich_parameter_values." + index, { @@ -155,7 +163,11 @@ export const WorkspaceParametersForm: FC<{ )} - + ); }; diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.tsx index 10968243188d6..ca5d968eab2c8 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.tsx @@ -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,11 +19,12 @@ 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"; import { docs } from "utils/docs"; +import { Alert } from "@mui/material"; const WorkspaceParametersPage: FC = () => { const workspace = useWorkspaceSettings(); @@ -37,6 +44,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 ( <> @@ -44,6 +67,8 @@ const WorkspaceParametersPage: FC = () => { { }; export type WorkspaceParametersPageViewProps = { + workspace: Workspace; + canChangeVersions: boolean; data: Awaited> | undefined; submitError: unknown; isSubmitting: boolean; @@ -76,7 +103,15 @@ export type WorkspaceParametersPageViewProps = { export const WorkspaceParametersPageView: FC< WorkspaceParametersPageViewProps -> = ({ data, submitError, isSubmitting, onSubmit, onCancel }) => { +> = ({ + workspace, + canChangeVersions, + data, + submitError, + onSubmit, + isSubmitting, + onCancel, +}) => { return ( <> @@ -87,9 +122,18 @@ export const WorkspaceParametersPageView: FC< )} + {workspace.template_require_active_version && !canChangeVersions && ( + + The template for this workspace requires automatic updates. This + workspace must be on the latest version to edit paremeters. + + )} + {data ? ( data.templateVersionRichParameters.length > 0 ? ( Date: Sun, 10 Dec 2023 23:02:50 +0000 Subject: [PATCH 2/6] typos --- .../WorkspaceParametersPage/WorkspaceParametersForm.tsx | 4 +++- .../WorkspaceParametersPage/WorkspaceParametersPage.tsx | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersForm.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersForm.tsx index 7992d4ed22b39..8a01ace5f1b09 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersForm.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersForm.tsx @@ -71,7 +71,9 @@ export const WorkspaceParametersForm: FC<{ ); const disabled = - workspace.template_require_active_version && !canChangeVersions; + workspace.outdated && + workspace.template_require_active_version && + !canChangeVersions; return ( diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.tsx index ca5d968eab2c8..916f8ee9cfa0c 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.tsx @@ -124,8 +124,8 @@ export const WorkspaceParametersPageView: FC< {workspace.template_require_active_version && !canChangeVersions && ( - The template for this workspace requires automatic updates. This - workspace must be on the latest version to edit paremeters. + The template for this workspace requires automatic updates. Update the + workspace to edit parameters. )} From cde38cf81bd47fc4cb16f3bc2cfe7130d658b483 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Sun, 10 Dec 2023 23:06:26 +0000 Subject: [PATCH 3/6] use component alert --- .../WorkspaceParametersPage/WorkspaceParametersPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.tsx index 916f8ee9cfa0c..823262bac3dfd 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.tsx @@ -24,7 +24,7 @@ import { EmptyState } from "components/EmptyState/EmptyState"; import Button from "@mui/material/Button"; import OpenInNewOutlined from "@mui/icons-material/OpenInNewOutlined"; import { docs } from "utils/docs"; -import { Alert } from "@mui/material"; +import { Alert } from "components/Alert/Alert"; const WorkspaceParametersPage: FC = () => { const workspace = useWorkspaceSettings(); From b97f42e261eab38cc40f66aed8cfb94a28a9e9cf Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Sun, 10 Dec 2023 23:10:56 +0000 Subject: [PATCH 4/6] only display if outdated --- .../WorkspaceParametersPage.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.tsx index 823262bac3dfd..745e943214d27 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.tsx @@ -122,12 +122,14 @@ export const WorkspaceParametersPageView: FC< )} - {workspace.template_require_active_version && !canChangeVersions && ( - - The template for this workspace requires automatic updates. Update the - workspace to edit parameters. - - )} + {workspace.outdated && + workspace.template_require_active_version && + !canChangeVersions && ( + + The template for this workspace requires automatic updates. Update + the workspace to edit parameters. + + )} {data ? ( data.templateVersionRichParameters.length > 0 ? ( From e5af12429b0410c2dedc35393ba2c89ca18e9e77 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Sun, 10 Dec 2023 23:14:41 +0000 Subject: [PATCH 5/6] add storybook --- .../WorkspaceParametersPage.stories.tsx | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.stories.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.stories.tsx index bd21640a8fc7f..d27a5fa3d7b4d 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.stories.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.stories.tsx @@ -7,6 +7,8 @@ import { MockTemplateVersionParameter2, MockTemplateVersionParameter3, MockWorkspaceBuildParameter3, + MockWorkspace, + MockOutdatedStoppedWorkspaceRequireActiveVersion, } from "testHelpers/entities"; const meta: Meta = { @@ -15,6 +17,8 @@ const meta: Meta = { 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 }; From d756a6f703381410865965e6e400cb8a51060da4 Mon Sep 17 00:00:00 2001 From: McKayla Washburn Date: Mon, 11 Dec 2023 22:40:00 +0000 Subject: [PATCH 6/6] move `Alert` --- .../WorkspaceParametersForm.tsx | 218 ++++++++++-------- .../WorkspaceParametersPage.tsx | 10 - 2 files changed, 118 insertions(+), 110 deletions(-) diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersForm.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersForm.tsx index 8a01ace5f1b09..12ea287acfb84 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersForm.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersForm.tsx @@ -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,15 +9,12 @@ 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, @@ -23,7 +24,7 @@ export type WorkspaceParametersFormValues = { rich_parameter_values: WorkspaceBuildParameter[]; }; -export const WorkspaceParametersForm: FC<{ +interface WorkspaceParameterFormProps { workspace: Workspace; templateVersionRichParameters: TemplateVersionParameter[]; buildParameters: WorkspaceBuildParameter[]; @@ -32,7 +33,9 @@ export const WorkspaceParametersForm: FC<{ error: unknown; onCancel: () => void; onSubmit: (values: WorkspaceParametersFormValues) => void; -}> = ({ +} + +export const WorkspaceParametersForm: FC = ({ workspace, onCancel, onSubmit, @@ -76,100 +79,115 @@ export const WorkspaceParametersForm: FC<{ !canChangeVersions; return ( - - {hasNonEphemeralParameters && ( - - - {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 ? ( - { - await form.setFieldValue("rich_parameter_values." + index, { - name: parameter.name, - value: value, - }); - }} - parameter={parameter} - /> - ) : null, - )} - - - )} - {hasEphemeralParameters && ( - - - {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 ? ( - { - await form.setFieldValue("rich_parameter_values." + index, { - name: parameter.name, - value: value, - }); - }} - parameter={parameter} - /> - ) : null, - )} - - + <> + {disabled && ( + + The template for this workspace requires automatic updates. Update the + workspace to edit parameters. + )} - {/* They are displayed here only for visibility purposes */} - {hasImmutableParameters && ( - - These settings cannot be changed after creating - the workspace. - - } - > - - {templateVersionRichParameters.map((parameter, index) => - !parameter.mutable ? ( - { - throw new Error("Immutable parameters cannot be changed"); - }} - /> - ) : null, - )} - - - )} - - + + + {hasNonEphemeralParameters && ( + + + {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 ? ( + { + await form.setFieldValue( + "rich_parameter_values." + index, + { + name: parameter.name, + value: value, + }, + ); + }} + parameter={parameter} + /> + ) : null, + )} + + + )} + {hasEphemeralParameters && ( + + + {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 ? ( + { + await form.setFieldValue( + "rich_parameter_values." + index, + { + name: parameter.name, + value: value, + }, + ); + }} + parameter={parameter} + /> + ) : null, + )} + + + )} + {/* They are displayed here only for visibility purposes */} + {hasImmutableParameters && ( + + These settings cannot be changed after creating + the workspace. + + } + > + + {templateVersionRichParameters.map((parameter, index) => + !parameter.mutable ? ( + { + throw new Error("Immutable parameters cannot be changed"); + }} + /> + ) : null, + )} + + + )} + + + ); }; diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.tsx index 745e943214d27..6d9f64dae5070 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.tsx @@ -24,7 +24,6 @@ import { EmptyState } from "components/EmptyState/EmptyState"; import Button from "@mui/material/Button"; import OpenInNewOutlined from "@mui/icons-material/OpenInNewOutlined"; import { docs } from "utils/docs"; -import { Alert } from "components/Alert/Alert"; const WorkspaceParametersPage: FC = () => { const workspace = useWorkspaceSettings(); @@ -122,15 +121,6 @@ export const WorkspaceParametersPageView: FC< )} - {workspace.outdated && - workspace.template_require_active_version && - !canChangeVersions && ( - - The template for this workspace requires automatic updates. Update - the workspace to edit parameters. - - )} - {data ? ( data.templateVersionRichParameters.length > 0 ? (