From c90c22648498cd1750234c2cde76d7aa1ddc95d2 Mon Sep 17 00:00:00 2001 From: Bruno Date: Mon, 23 May 2022 18:10:48 +0000 Subject: [PATCH 01/11] Refactor create workspace machine --- .vscode/settings.json | 3 + .../CreateWorkspacePage.tsx | 67 ++++++--- .../CreateWorkspacePageView.tsx | 108 ++++++++++---- .../createWorkspaceXService.ts | 140 ++++++++++++++++++ 4 files changed, 262 insertions(+), 56 deletions(-) create mode 100644 site/src/xServices/createWorkspace/createWorkspaceXService.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index e962a55458f04..2ea7220cd50ab 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -15,6 +15,7 @@ "drpcserver", "Dsts", "fatih", + "Formik", "goarch", "gographviz", "goleak", @@ -22,6 +23,7 @@ "gsyslog", "hashicorp", "hclsyntax", + "httpapi", "httpmw", "idtoken", "Iflag", @@ -63,6 +65,7 @@ "tfjson", "tfstate", "trimprefix", + "typegen", "unconvert", "Untar", "VMID", diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx index 177d3f1492cb6..297313e9f5e82 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx @@ -1,36 +1,55 @@ -import { useMachine } from "@xstate/react" -import React from "react" -import { useNavigate } from "react-router" -import { useParams } from "react-router-dom" -import { createWorkspace } from "../../api/api" -import { templateMachine } from "../../xServices/template/templateXService" +import { useActor, useMachine } from "@xstate/react" +import React, { useContext } from "react" +import { useNavigate } from "react-router-dom" +import { Template } from "../../api/typesGenerated" +import { createWorkspaceMachine } from "../../xServices/createWorkspace/createWorkspaceXService" +import { XServiceContext } from "../../xServices/StateContext" import { CreateWorkspacePageView } from "./CreateWorkspacePageView" +const useOrganizationId = () => { + const xServices = useContext(XServiceContext) + const [authState] = useActor(xServices.authXService) + const organizationId = authState.context.me?.organization_ids[0] + + if (!organizationId) { + throw new Error("No organization ID found") + } + + return organizationId +} + const CreateWorkspacePage: React.FC = () => { - const { template } = useParams() - const [templateState] = useMachine(templateMachine, { - context: { - name: template, + const organizationId = useOrganizationId() + const navigate = useNavigate() + const [createWorkspaceState, send] = useMachine(createWorkspaceMachine, { + context: { organizationId }, + actions: { + onCreateWorkspace: (_, event) => { + navigate("/workspaces/" + event.data.id) + }, }, }) - const navigate = useNavigate() - const loading = templateState.hasTag("loading") - if (!templateState.context.template || !templateState.context.templateSchema) { - return null - } return ( navigate("/templates")} - onSubmit={async (req) => { - if (!templateState.context.template) { - throw new Error("template isn't valid") - } - const workspace = await createWorkspace(templateState.context.template.organization_id, req) - navigate("/workspaces/" + workspace.id) + onSubmit={(request) => { + send({ + type: "CREATE_WORKSPACE", + request, + }) + }} + onSelectTemplate={(template: Template) => { + send({ + type: "SELECT_TEMPLATE", + template, + }) }} /> ) diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx index cdc24710ccf6a..95133d9287e1b 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx @@ -1,5 +1,6 @@ +import MenuItem from "@material-ui/core/MenuItem" import { makeStyles } from "@material-ui/core/styles" -import TextField from "@material-ui/core/TextField" +import TextField, { TextFieldProps } from "@material-ui/core/TextField" import { FormikContextType, useFormik } from "formik" import React from "react" import * as Yup from "yup" @@ -11,6 +12,7 @@ import { ParameterInput } from "../../components/ParameterInput/ParameterInput" import { getFormHelpers, onChangeTrimmed } from "../../util/formUtils" export const Language = { + templateLabel: "Template", nameLabel: "Name", nameRequired: "Please enter a name.", nameMatches: "Name must start with a-Z or 0-9 and can contain a-Z, 0-9 or -", @@ -24,12 +26,15 @@ const maxLenName = 32 const usernameRE = /^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*$/ export interface CreateWorkspacePageViewProps { - loading?: boolean - template: TypesGen.Template - templateSchema: TypesGen.ParameterSchema[] - + loadingTemplates: boolean + loadingTemplateSchema: boolean + creatingWorkspace: boolean + templates?: TypesGen.Template[] + selectedTemplate?: TypesGen.Template + templateSchema?: TypesGen.ParameterSchema[] onCancel: () => void - onSubmit: (req: TypesGen.CreateWorkspaceRequest) => Promise + onSubmit: (req: TypesGen.CreateWorkspaceRequest) => void + onSelectTemplate: (template: TypesGen.Template) => void } export const validationSchema = Yup.object({ @@ -45,10 +50,14 @@ export const CreateWorkspacePageView: React.FC = ( const form: FormikContextType = useFormik({ initialValues: { name: "", - template_id: props.template.id, + template_id: "", }, validationSchema, onSubmit: (request) => { + if (!props.templateSchema) { + throw new Error("No template schema loaded") + } + const createRequests: TypesGen.CreateParameterRequest[] = [] props.templateSchema.forEach((schema) => { let value = schema.default_source_value @@ -70,38 +79,73 @@ export const CreateWorkspacePageView: React.FC = ( }) const getFieldHelpers = getFormHelpers(form) + const handleTemplateChange: TextFieldProps["onChange"] = (event) => { + if (!props.templates) { + throw new Error("Templates are not loaded") + } + + // The TextField + MenuItem returns the index of the selected option + const templateIndex = Number(event.currentTarget.value) + const selectedTemplate = props.templates[templateIndex] + form.setFieldValue("template_id", selectedTemplate.id) + props.onSelectTemplate(selectedTemplate) + } + return (
- - {props.templateSchema.length > 0 && ( -
- {props.templateSchema.map((schema) => ( - { - setParameterValues({ - ...parameterValues, - [schema.name]: value, - }) - }} - schema={schema} - /> + {props.templates && ( + + {props.templates.map((template) => ( + + {template.name} + ))} -
+
+ )} + + {props.selectedTemplate && props.templateSchema && ( + <> + + {props.templateSchema.length > 0 && ( +
+ {props.templateSchema.map((schema) => ( + { + setParameterValues({ + ...parameterValues, + [schema.name]: value, + }) + }} + schema={schema} + /> + ))} +
+ )} + )} - +
diff --git a/site/src/xServices/createWorkspace/createWorkspaceXService.ts b/site/src/xServices/createWorkspace/createWorkspaceXService.ts new file mode 100644 index 0000000000000..9ec7b15c38c97 --- /dev/null +++ b/site/src/xServices/createWorkspace/createWorkspaceXService.ts @@ -0,0 +1,140 @@ +import { assign, createMachine } from "xstate" +import { createWorkspace, getTemplates, getTemplateVersionSchema } from "../../api/api" +import { CreateWorkspaceRequest, ParameterSchema, Template, Workspace } from "../../api/typesGenerated" + +type CreateWorkspaceContext = { + organizationId: string + templates?: Template[] + selectedTemplate?: Template + templateSchema?: ParameterSchema[] + createWorkspaceRequest?: CreateWorkspaceRequest + createdWorkspace?: Workspace +} + +type CreateWorkspaceEvent = + | { + type: "SELECT_TEMPLATE" + template: Template + } + | { + type: "CREATE_WORKSPACE" + request: CreateWorkspaceRequest + } + +export const createWorkspaceMachine = createMachine( + { + id: "createWorkspaceState", + initial: "gettingTemplates", + schema: { + context: {} as CreateWorkspaceContext, + events: {} as CreateWorkspaceEvent, + services: {} as { + getTemplates: { + data: Template[] + } + getTemplateSchema: { + data: ParameterSchema[] + } + createWorkspace: { + data: Workspace + } + }, + }, + tsTypes: {} as import("./createWorkspaceXService.typegen").Typegen0, + states: { + gettingTemplates: { + invoke: { + src: "getTemplates", + onDone: { + actions: ["assignTemplates"], + target: "selectingTemplate", + }, + onError: { + target: "error", + }, + }, + }, + selectingTemplate: { + on: { + SELECT_TEMPLATE: { + actions: ["assignSelectedTemplate"], + target: "gettingTemplateSchema", + }, + }, + }, + gettingTemplateSchema: { + invoke: { + src: "getTemplateSchema", + onDone: { + actions: ["assignTemplateSchema"], + target: "fillingForm", + }, + onError: { + target: "error", + }, + }, + }, + fillingForm: { + on: { + CREATE_WORKSPACE: { + actions: ["assignCreateWorkspaceRequest"], + target: "creatingWorkspace", + }, + }, + }, + creatingWorkspace: { + invoke: { + src: "createWorkspace", + onDone: { + actions: ["onCreateWorkspace"], + target: "created", + }, + onError: { + target: "error", + }, + }, + }, + created: { + type: "final", + }, + error: {}, + }, + }, + { + services: { + getTemplates: (context) => getTemplates(context.organizationId), + getTemplateSchema: (context) => { + const { selectedTemplate } = context + + if (!selectedTemplate) { + throw new Error("No selected template") + } + + return getTemplateVersionSchema(selectedTemplate.active_version_id) + }, + createWorkspace: (context) => { + const { createWorkspaceRequest, organizationId } = context + + if (!createWorkspaceRequest) { + throw new Error("No create workspace request") + } + + return createWorkspace(organizationId, createWorkspaceRequest) + }, + }, + actions: { + assignTemplates: assign({ + templates: (_, event) => event.data, + }), + assignSelectedTemplate: assign({ + selectedTemplate: (_, event) => event.template, + }), + assignTemplateSchema: assign({ + templateSchema: (_, event) => event.data, + }), + assignCreateWorkspaceRequest: assign({ + createWorkspaceRequest: (_, event) => event.request, + }), + }, + }, +) From 6f6b9109debf2c112014432f02d308276f7bfd0c Mon Sep 17 00:00:00 2001 From: Bruno Date: Mon, 23 May 2022 18:15:52 +0000 Subject: [PATCH 02/11] Update router --- site/src/AppRouter.tsx | 21 +++++++++---------- .../WorkspacesPage/WorkspacesPageView.tsx | 2 +- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/site/src/AppRouter.tsx b/site/src/AppRouter.tsx index b11a33d3e39cc..43150232fb8ce 100644 --- a/site/src/AppRouter.tsx +++ b/site/src/AppRouter.tsx @@ -56,6 +56,16 @@ export const AppRouter: React.FC = () => ( } /> + + + + + } + /> + ( } /> - - - - - - } - /> - diff --git a/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx b/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx index e1f173675ccd5..48696fb46ae25 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx @@ -39,7 +39,7 @@ export const WorkspacesPageView: React.FC = (props) =>
- +
From 679cb4acd29089bb6e21ba153e52b04cacd19b30 Mon Sep 17 00:00:00 2001 From: Bruno Date: Mon, 23 May 2022 18:18:03 +0000 Subject: [PATCH 03/11] Fix route --- site/src/AppRouter.tsx | 4 ++-- site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/site/src/AppRouter.tsx b/site/src/AppRouter.tsx index 43150232fb8ce..4ec0f463cae26 100644 --- a/site/src/AppRouter.tsx +++ b/site/src/AppRouter.tsx @@ -60,9 +60,9 @@ export const AppRouter: React.FC = () => ( + - + } /> diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx index 297313e9f5e82..3aee46c93d24d 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx @@ -38,7 +38,7 @@ const CreateWorkspacePage: React.FC = () => { templates={createWorkspaceState.context.templates} selectedTemplate={createWorkspaceState.context.selectedTemplate} templateSchema={createWorkspaceState.context.templateSchema} - onCancel={() => navigate("/templates")} + onCancel={() => navigate("/workspaces")} onSubmit={(request) => { send({ type: "CREATE_WORKSPACE", From ef436841e787f782ba2699331ce8d727bf8c6b70 Mon Sep 17 00:00:00 2001 From: Bruno Date: Mon, 23 May 2022 18:19:01 +0000 Subject: [PATCH 04/11] Move buttons to condition --- .../src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx index 95133d9287e1b..3f45a53bca740 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx @@ -142,10 +142,10 @@ export const CreateWorkspacePageView: React.FC = ( ))} )} + + )} - -
From 4af315ba8ac4b4848a5f38fc9bdc0ade4a42e172 Mon Sep 17 00:00:00 2001 From: Bruno Date: Mon, 23 May 2022 18:25:30 +0000 Subject: [PATCH 05/11] Add minor adjustments on spacing --- .../CreateWorkspacePageView.tsx | 106 +++++++++--------- 1 file changed, 51 insertions(+), 55 deletions(-) diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx index 3f45a53bca740..6fbce139e5a9f 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx @@ -1,5 +1,4 @@ import MenuItem from "@material-ui/core/MenuItem" -import { makeStyles } from "@material-ui/core/styles" import TextField, { TextFieldProps } from "@material-ui/core/TextField" import { FormikContextType, useFormik } from "formik" import React from "react" @@ -7,8 +6,10 @@ import * as Yup from "yup" import * as TypesGen from "../../api/typesGenerated" import { FormFooter } from "../../components/FormFooter/FormFooter" import { FullPageForm } from "../../components/FullPageForm/FullPageForm" +import { Loader } from "../../components/Loader/Loader" import { Margins } from "../../components/Margins/Margins" import { ParameterInput } from "../../components/ParameterInput/ParameterInput" +import { Stack } from "../../components/Stack/Stack" import { getFormHelpers, onChangeTrimmed } from "../../util/formUtils" export const Language = { @@ -45,7 +46,6 @@ export const validationSchema = Yup.object({ }) export const CreateWorkspacePageView: React.FC = (props) => { - const styles = useStyles() const [parameterValues, setParameterValues] = React.useState>({}) const form: FormikContextType = useFormik({ initialValues: { @@ -95,68 +95,64 @@ export const CreateWorkspacePageView: React.FC = (
- {props.templates && ( - - {props.templates.map((template) => ( - - {template.name} - - ))} - - )} + {props.loadingTemplates && } - {props.selectedTemplate && props.templateSchema && ( - <> + + {props.templates && ( - {props.templateSchema.length > 0 && ( -
- {props.templateSchema.map((schema) => ( - { - setParameterValues({ - ...parameterValues, - [schema.name]: value, - }) - }} - schema={schema} - /> - ))} -
- )} + select + > + {props.templates.map((template) => ( + + {template.name} + + ))} +
+ )} - - - )} + {props.selectedTemplate && props.templateSchema && ( + <> + + + {props.templateSchema.length > 0 && ( + + {props.templateSchema.map((schema) => ( + { + setParameterValues({ + ...parameterValues, + [schema.name]: value, + }) + }} + schema={schema} + /> + ))} + + )} + + + + )} +
) } - -const useStyles = makeStyles((theme) => ({ - parameters: { - paddingTop: theme.spacing(4), - "& > *": { - marginBottom: theme.spacing(4), - }, - }, -})) From 20f8742108b64e0d42b230b3beb9f2b9196cf361 Mon Sep 17 00:00:00 2001 From: Bruno Date: Mon, 23 May 2022 19:20:35 +0000 Subject: [PATCH 06/11] Accept pre defined template --- .../CreateWorkspacePage.tsx | 6 ++- .../CreateWorkspacePageView.tsx | 13 ++++-- .../pages/TemplatesPage/TemplatesPageView.tsx | 6 ++- .../createWorkspaceXService.ts | 43 ++++++++++++++++--- 4 files changed, 55 insertions(+), 13 deletions(-) diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx index 3aee46c93d24d..639f03a10350e 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx @@ -1,6 +1,6 @@ import { useActor, useMachine } from "@xstate/react" import React, { useContext } from "react" -import { useNavigate } from "react-router-dom" +import { useNavigate, useSearchParams } from "react-router-dom" import { Template } from "../../api/typesGenerated" import { createWorkspaceMachine } from "../../xServices/createWorkspace/createWorkspaceXService" import { XServiceContext } from "../../xServices/StateContext" @@ -20,9 +20,11 @@ const useOrganizationId = () => { const CreateWorkspacePage: React.FC = () => { const organizationId = useOrganizationId() + const [searchParams] = useSearchParams() + const preSelectedTemplateName = searchParams.get("template") const navigate = useNavigate() const [createWorkspaceState, send] = useMachine(createWorkspaceMachine, { - context: { organizationId }, + context: { organizationId, preSelectedTemplateName }, actions: { onCreateWorkspace: (_, event) => { navigate("/workspaces/" + event.data.id) diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx index 6fbce139e5a9f..3370c3bef2340 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx @@ -50,8 +50,9 @@ export const CreateWorkspacePageView: React.FC = ( const form: FormikContextType = useFormik({ initialValues: { name: "", - template_id: "", + template_id: props.selectedTemplate ? props.selectedTemplate.id : "", }, + enableReinitialize: true, validationSchema, onSubmit: (request) => { if (!props.templateSchema) { @@ -84,9 +85,13 @@ export const CreateWorkspacePageView: React.FC = ( throw new Error("Templates are not loaded") } - // The TextField + MenuItem returns the index of the selected option - const templateIndex = Number(event.currentTarget.value) - const selectedTemplate = props.templates[templateIndex] + const templateId = event.target.value + const selectedTemplate = props.templates.find((template) => template.id === templateId) + + if (!selectedTemplate) { + throw new Error(`Template ${templateId} not found`) + } + form.setFieldValue("template_id", selectedTemplate.id) props.onSelectTemplate(selectedTemplate) } diff --git a/site/src/pages/TemplatesPage/TemplatesPageView.tsx b/site/src/pages/TemplatesPage/TemplatesPageView.tsx index 122863beb3aa4..a48032e39bc29 100644 --- a/site/src/pages/TemplatesPage/TemplatesPageView.tsx +++ b/site/src/pages/TemplatesPage/TemplatesPageView.tsx @@ -83,7 +83,11 @@ export const TemplatesPageView: React.FC = (props) => { {firstLetter(template.name)} - + {template.name} {template.description} diff --git a/site/src/xServices/createWorkspace/createWorkspaceXService.ts b/site/src/xServices/createWorkspace/createWorkspaceXService.ts index 9ec7b15c38c97..2988972fed76d 100644 --- a/site/src/xServices/createWorkspace/createWorkspaceXService.ts +++ b/site/src/xServices/createWorkspace/createWorkspaceXService.ts @@ -9,6 +9,10 @@ type CreateWorkspaceContext = { templateSchema?: ParameterSchema[] createWorkspaceRequest?: CreateWorkspaceRequest createdWorkspace?: Workspace + // This is useful when the user wants to create a workspace from the template + // page having it pre selected. It is string or null because of the + // useSearchQuery + preSelectedTemplateName: string | null } type CreateWorkspaceEvent = @@ -45,10 +49,17 @@ export const createWorkspaceMachine = createMachine( gettingTemplates: { invoke: { src: "getTemplates", - onDone: { - actions: ["assignTemplates"], - target: "selectingTemplate", - }, + onDone: [ + { + actions: ["assignTemplates", "assignPreSelectedTemplate"], + target: "gettingTemplateSchema", + cond: "hasValidPreSelectedTemplate", + }, + { + actions: ["assignTemplates"], + target: "selectingTemplate", + }, + ], onError: { target: "error", }, @@ -67,14 +78,14 @@ export const createWorkspaceMachine = createMachine( src: "getTemplateSchema", onDone: { actions: ["assignTemplateSchema"], - target: "fillingForm", + target: "fillingParams", }, onError: { target: "error", }, }, }, - fillingForm: { + fillingParams: { on: { CREATE_WORKSPACE: { actions: ["assignCreateWorkspaceRequest"], @@ -122,6 +133,15 @@ export const createWorkspaceMachine = createMachine( return createWorkspace(organizationId, createWorkspaceRequest) }, }, + guards: { + hasValidPreSelectedTemplate: (ctx, event) => { + if (!ctx.preSelectedTemplateName) { + return false + } + const template = event.data.find((template) => template.name === ctx.preSelectedTemplateName) + return !!template + }, + }, actions: { assignTemplates: assign({ templates: (_, event) => event.data, @@ -135,6 +155,17 @@ export const createWorkspaceMachine = createMachine( assignCreateWorkspaceRequest: assign({ createWorkspaceRequest: (_, event) => event.request, }), + assignPreSelectedTemplate: assign({ + selectedTemplate: (ctx, event) => { + const selectedTemplate = event.data.find((template) => template.name === ctx.preSelectedTemplateName) + // The proper validation happens on hasValidPreSelectedTemplate + if (!selectedTemplate) { + throw new Error("Invalid template selected") + } + + return selectedTemplate + }, + }), }, }, ) From 3b7d3d079b2110177ae135072c9ae2adf2ce46a0 Mon Sep 17 00:00:00 2001 From: Bruno Date: Mon, 23 May 2022 19:47:12 +0000 Subject: [PATCH 07/11] Fix storybooks --- site/src/components/FormFooter/FormFooter.tsx | 2 +- .../CreateWorkspacePageView.stories.tsx | 31 +++++++++++++++++-- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/site/src/components/FormFooter/FormFooter.tsx b/site/src/components/FormFooter/FormFooter.tsx index 03233f42b720a..ec41d99a38ac9 100644 --- a/site/src/components/FormFooter/FormFooter.tsx +++ b/site/src/components/FormFooter/FormFooter.tsx @@ -35,7 +35,7 @@ export const FormFooter: React.FC = ({ const styles = useStyles() return (
- diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.stories.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.stories.tsx index 48f0131f1b9dc..0f27c89a201c7 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.stories.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.stories.tsx @@ -1,9 +1,32 @@ import { ComponentMeta, Story } from "@storybook/react" import React from "react" -import { createParameterSchema } from "../../components/ParameterInput/ParameterInput.stories" +import { ParameterSchema } from "../../api/typesGenerated" import { MockTemplate } from "../../testHelpers/entities" import { CreateWorkspacePageView, CreateWorkspacePageViewProps } from "./CreateWorkspacePageView" +const createParameterSchema = (partial: Partial): ParameterSchema => { + return { + id: "000000", + job_id: "000000", + allow_override_destination: false, + allow_override_source: true, + created_at: "", + default_destination_scheme: "none", + default_refresh: "", + default_source_scheme: "data", + default_source_value: "default-value", + name: "parameter name", + description: "Some description!", + redisplay_value: false, + validation_condition: "", + validation_contains: [], + validation_error: "", + validation_type_system: "", + validation_value_type: "", + ...partial, + } +} + export default { title: "pages/CreateWorkspacePageView", component: CreateWorkspacePageView, @@ -13,13 +36,15 @@ const Template: Story = (args) => Date: Mon, 23 May 2022 19:49:56 +0000 Subject: [PATCH 08/11] Fix cancel redirect --- site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx index 639f03a10350e..ae71dd5cf4319 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx @@ -40,7 +40,9 @@ const CreateWorkspacePage: React.FC = () => { templates={createWorkspaceState.context.templates} selectedTemplate={createWorkspaceState.context.selectedTemplate} templateSchema={createWorkspaceState.context.templateSchema} - onCancel={() => navigate("/workspaces")} + onCancel={() => { + navigate(preSelectedTemplateName ? "/templates" : "/workspaces") + }} onSubmit={(request) => { send({ type: "CREATE_WORKSPACE", From eeeda45462197cc22a6abb63f8a8ba5a0b76c501 Mon Sep 17 00:00:00 2001 From: Bruno Date: Mon, 23 May 2022 19:53:18 +0000 Subject: [PATCH 09/11] Fix tests --- .../CreateWorkspacePage.test.tsx | 2 +- .../xServices/template/templateXService.ts | 167 ------------------ 2 files changed, 1 insertion(+), 168 deletions(-) delete mode 100644 site/src/xServices/template/templateXService.ts diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.test.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.test.tsx index 57b0cebdd72f7..cb82ba198b0e0 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.test.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.test.tsx @@ -20,7 +20,7 @@ const nameSchema = reach(validationSchema, "name") as StringSchema describe("CreateWorkspacePage", () => { beforeEach(() => { - history.replace("/templates/" + MockTemplate.name + "/new") + history.replace("/workspaces/new?template=" + MockTemplate.name) }) it("renders", async () => { diff --git a/site/src/xServices/template/templateXService.ts b/site/src/xServices/template/templateXService.ts deleted file mode 100644 index f2e610e4b5a24..0000000000000 --- a/site/src/xServices/template/templateXService.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { assign, createMachine } from "xstate" -import * as API from "../../api/api" -import * as TypesGen from "../../api/typesGenerated" - -interface TemplateContext { - name: string - - organizations?: TypesGen.Organization[] - organizationsError?: Error | unknown - template?: TypesGen.Template - templateError?: Error | unknown - templateVersion?: TypesGen.TemplateVersion - templateVersionError?: Error | unknown - templateSchema?: TypesGen.ParameterSchema[] - templateSchemaError?: Error | unknown -} - -export const templateMachine = createMachine( - { - tsTypes: {} as import("./templateXService.typegen").Typegen0, - schema: { - context: {} as TemplateContext, - services: {} as { - getOrganizations: { - data: TypesGen.Organization[] - } - getTemplate: { - data: TypesGen.Template - } - getTemplateVersion: { - data: TypesGen.TemplateVersion - } - getTemplateSchema: { - data: TypesGen.ParameterSchema[] - } - }, - }, - id: "templateState", - initial: "gettingOrganizations", - states: { - gettingOrganizations: { - entry: "clearOrganizationsError", - invoke: { - src: "getOrganizations", - id: "getOrganizations", - onDone: [ - { - actions: ["assignOrganizations", "clearOrganizationsError"], - target: "gettingTemplate", - }, - ], - onError: [ - { - actions: "assignOrganizationsError", - target: "error", - }, - ], - }, - tags: "loading", - }, - gettingTemplate: { - entry: "clearTemplateError", - invoke: { - src: "getTemplate", - id: "getTemplate", - onDone: { - target: "gettingTemplateVersion", - actions: ["assignTemplate", "clearTemplateError"], - }, - onError: { - target: "error", - actions: "assignTemplateError", - }, - }, - tags: "loading", - }, - gettingTemplateVersion: { - entry: "clearTemplateVersionError", - invoke: { - src: "getTemplateVersion", - id: "getTemplateVersion", - onDone: { - target: "gettingTemplateSchema", - actions: ["assignTemplateVersion", "clearTemplateVersionError"], - }, - onError: { - target: "error", - actions: "assignTemplateVersionError", - }, - }, - }, - gettingTemplateSchema: { - entry: "clearTemplateSchemaError", - invoke: { - src: "getTemplateSchema", - id: "getTemplateSchema", - onDone: { - target: "done", - actions: ["assignTemplateSchema", "clearTemplateSchemaError"], - }, - onError: { - target: "error", - actions: "assignTemplateSchemaError", - }, - }, - }, - done: {}, - error: {}, - }, - }, - { - actions: { - assignOrganizations: assign({ - organizations: (_, event) => event.data, - }), - assignOrganizationsError: assign({ - organizationsError: (_, event) => event.data, - }), - clearOrganizationsError: assign((context) => ({ - ...context, - organizationsError: undefined, - })), - assignTemplate: assign({ - template: (_, event) => event.data, - }), - assignTemplateError: assign({ - templateError: (_, event) => event.data, - }), - clearTemplateError: (context) => assign({ ...context, templateError: undefined }), - assignTemplateVersion: assign({ - templateVersion: (_, event) => event.data, - }), - assignTemplateVersionError: assign({ - templateVersionError: (_, event) => event.data, - }), - clearTemplateVersionError: (context) => assign({ ...context, templateVersionError: undefined }), - assignTemplateSchema: assign({ - templateSchema: (_, event) => event.data, - }), - assignTemplateSchemaError: assign({ - templateSchemaError: (_, event) => event.data, - }), - clearTemplateSchemaError: (context) => assign({ ...context, templateSchemaError: undefined }), - }, - services: { - getOrganizations: API.getOrganizations, - getTemplate: async (context) => { - if (!context.organizations || context.organizations.length === 0) { - throw new Error("no organizations") - } - return API.getTemplateByName(context.organizations[0].id, context.name) - }, - getTemplateVersion: async (context) => { - if (!context.template) { - throw new Error("no template") - } - return API.getTemplateVersion(context.template.active_version_id) - }, - getTemplateSchema: async (context) => { - if (!context.templateVersion) { - throw new Error("no template version") - } - return API.getTemplateVersionSchema(context.templateVersion.id) - }, - }, - }, -) From 58f92314c690ccc63ac3a9efbd20be355462dbd9 Mon Sep 17 00:00:00 2001 From: Bruno Date: Tue, 24 May 2022 12:26:07 +0000 Subject: [PATCH 10/11] Fix test --- .../pages/CreateWorkspacePage/CreateWorkspacePage.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.test.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.test.tsx index cb82ba198b0e0..38f6c243dc270 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.test.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.test.tsx @@ -5,7 +5,7 @@ import { reach, StringSchema } from "yup" import * as API from "../../api/api" import { Language as FooterLanguage } from "../../components/FormFooter/FormFooter" import { MockTemplate, MockWorkspace } from "../../testHelpers/entities" -import { history, render } from "../../testHelpers/renderHelpers" +import { history, render, renderWithAuth } from "../../testHelpers/renderHelpers" import CreateWorkspacePage from "./CreateWorkspacePage" import { Language, validationSchema } from "./CreateWorkspacePageView" @@ -24,7 +24,7 @@ describe("CreateWorkspacePage", () => { }) it("renders", async () => { - render() + renderWithAuth() const element = await screen.findByText("Create workspace") expect(element).toBeDefined() }) From 48088e30d19c87cbdb06633c66e5f85ade976c8d Mon Sep 17 00:00:00 2001 From: Bruno Date: Tue, 24 May 2022 12:44:05 +0000 Subject: [PATCH 11/11] Fix tests --- .../CreateWorkspacePage.test.tsx | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.test.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.test.tsx index 38f6c243dc270..a0f11bd6e5f1a 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.test.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.test.tsx @@ -5,10 +5,17 @@ import { reach, StringSchema } from "yup" import * as API from "../../api/api" import { Language as FooterLanguage } from "../../components/FormFooter/FormFooter" import { MockTemplate, MockWorkspace } from "../../testHelpers/entities" -import { history, render, renderWithAuth } from "../../testHelpers/renderHelpers" +import { renderWithAuth } from "../../testHelpers/renderHelpers" import CreateWorkspacePage from "./CreateWorkspacePage" import { Language, validationSchema } from "./CreateWorkspacePageView" +const renderCreateWorkspacePage = () => { + return renderWithAuth(, { + route: "/workspaces/new?template=" + MockTemplate.name, + path: "/workspaces/new", + }) +} + const fillForm = async ({ name = "example" }: { name?: string }) => { const nameField = await screen.findByLabelText(Language.nameLabel) await userEvent.type(nameField, name) @@ -19,25 +26,21 @@ const fillForm = async ({ name = "example" }: { name?: string }) => { const nameSchema = reach(validationSchema, "name") as StringSchema describe("CreateWorkspacePage", () => { - beforeEach(() => { - history.replace("/workspaces/new?template=" + MockTemplate.name) - }) - it("renders", async () => { - renderWithAuth() + renderCreateWorkspacePage() const element = await screen.findByText("Create workspace") expect(element).toBeDefined() }) it("shows validation error message", async () => { - render() + renderCreateWorkspacePage() await fillForm({ name: "$$$" }) const errorMessage = await screen.findByText(Language.nameMatches) expect(errorMessage).toBeDefined() }) it("succeeds", async () => { - render() + renderCreateWorkspacePage() // You have to spy the method before it is used. jest.spyOn(API, "createWorkspace").mockResolvedValueOnce(MockWorkspace) await fillForm({ name: "test" })