From 2e2a069b9a04f7c4fc0b49274021be31a3b50ba6 Mon Sep 17 00:00:00 2001 From: Bruno Quaresma Date: Tue, 17 Jan 2023 19:17:58 +0000 Subject: [PATCH] feat: Pre-fill param inputs with query string values --- .../ParameterInput/ParameterInput.tsx | 23 +++++++---- .../CreateWorkspacePage.test.tsx | 20 ++++++++++ .../CreateWorkspacePage.tsx | 35 +++++++++++----- .../CreateWorkspacePageView.stories.tsx | 40 +++++-------------- .../CreateWorkspacePageView.tsx | 4 +- site/src/testHelpers/entities.ts | 25 ++++++++++++ 6 files changed, 97 insertions(+), 50 deletions(-) diff --git a/site/src/components/ParameterInput/ParameterInput.tsx b/site/src/components/ParameterInput/ParameterInput.tsx index 5dd0ac3c123c2..05f263896a98b 100644 --- a/site/src/components/ParameterInput/ParameterInput.tsx +++ b/site/src/components/ParameterInput/ParameterInput.tsx @@ -35,11 +35,15 @@ export interface ParameterInputProps { disabled?: boolean schema: ParameterSchema onChange: (value: string) => void + defaultValue?: string } -export const ParameterInput: FC< - React.PropsWithChildren -> = ({ disabled, onChange, schema }) => { +export const ParameterInput: FC = ({ + disabled, + onChange, + schema, + defaultValue, +}) => { const styles = useStyles() return ( @@ -50,21 +54,25 @@ export const ParameterInput: FC< disabled={disabled} onChange={onChange} schema={schema} + defaultValue={defaultValue} /> ) } -const ParameterField: React.FC< - React.PropsWithChildren -> = ({ disabled, onChange, schema }) => { +const ParameterField: React.FC = ({ + disabled, + onChange, + schema, + defaultValue, +}) => { if (schema.validation_contains && schema.validation_contains.length > 0) { return ( { @@ -116,6 +124,7 @@ const ParameterField: React.FC< size="small" disabled={disabled} placeholder={schema.default_source_value} + defaultValue={defaultValue ?? schema.default_source_value} onChange={(event) => { onChange(event.target.value) }} diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.test.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.test.tsx index 820817b2c6d69..48735f077bdb6 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.test.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.test.tsx @@ -3,6 +3,7 @@ import userEvent from "@testing-library/user-event" import * as API from "api/api" import i18next from "i18next" import { + mockParameterSchema, MockTemplate, MockUser, MockWorkspace, @@ -62,4 +63,23 @@ describe("CreateWorkspacePage", () => { ), ) }) + + it("uses default param values passed from the URL", async () => { + const param = "dotfile_uri" + const paramValue = "localhost:3000" + jest.spyOn(API, "getTemplateVersionSchema").mockResolvedValueOnce([ + mockParameterSchema({ + name: param, + default_source_value: "", + }), + ]) + renderWithAuth(, { + route: + "/templates/" + + MockTemplate.name + + `/workspace?param.${param}=${paramValue}`, + path: "/templates/:template/workspace", + }) + await screen.findByDisplayValue(paramValue) + }) }) diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx index 5a6d790ed61aa..c0d01ebf7e5fe 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx @@ -1,29 +1,26 @@ -import { useActor, useMachine } from "@xstate/react" +import { useMachine } from "@xstate/react" +import { useMe } from "hooks/useMe" import { useOrganizationId } from "hooks/useOrganizationId" -import { FC, useContext } from "react" +import { FC } from "react" import { Helmet } from "react-helmet-async" -import { useNavigate, useParams } from "react-router-dom" +import { useNavigate, useParams, useSearchParams } from "react-router-dom" import { pageTitle } from "util/page" import { createWorkspaceMachine } from "xServices/createWorkspace/createWorkspaceXService" -import { XServiceContext } from "xServices/StateContext" import { CreateWorkspaceErrors, CreateWorkspacePageView, } from "./CreateWorkspacePageView" const CreateWorkspacePage: FC = () => { - const xServices = useContext(XServiceContext) const organizationId = useOrganizationId() - const { template } = useParams() - const templateName = template ? template : "" + const { template: templateName } = useParams() as { template: string } const navigate = useNavigate() - const [authState] = useActor(xServices.authXService) - const { me } = authState.context + const me = useMe() const [createWorkspaceState, send] = useMachine(createWorkspaceMachine, { context: { organizationId, templateName, - owner: me ?? null, + owner: me, }, actions: { onCreateWorkspace: (_, event) => { @@ -31,7 +28,6 @@ const CreateWorkspacePage: FC = () => { }, }, }) - const { templates, templateSchema, @@ -42,6 +38,8 @@ const CreateWorkspacePage: FC = () => { permissions, owner, } = createWorkspaceState.context + const [searchParams] = useSearchParams() + const defaultParameterValues = getDefaultParameterValues(searchParams) return ( <> @@ -49,6 +47,7 @@ const CreateWorkspacePage: FC = () => { Codestin Search App { ) } +const getDefaultParameterValues = ( + urlSearchParams: URLSearchParams, +): Record => { + const paramValues: Record = {} + Array.from(urlSearchParams.keys()) + .filter((key) => key.startsWith("param.")) + .forEach((key) => { + const paramName = key.replace("param.", "") + const paramValue = urlSearchParams.get(key) + paramValues[paramName] = paramValue ?? "" + }) + return paramValues +} + export default CreateWorkspacePage diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.stories.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.stories.tsx index 1d9691d9a4bd0..9fc79b659dbac 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.stories.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.stories.tsx @@ -1,37 +1,15 @@ import { ComponentMeta, Story } from "@storybook/react" -import { ParameterSchema } from "../../api/typesGenerated" -import { makeMockApiError, MockTemplate } from "../../testHelpers/entities" +import { + makeMockApiError, + mockParameterSchema, + MockTemplate, +} from "../../testHelpers/entities" import { CreateWorkspaceErrors, 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, @@ -54,7 +32,7 @@ Parameters.args = { templates: [MockTemplate], selectedTemplate: MockTemplate, templateSchema: [ - createParameterSchema({ + mockParameterSchema({ name: "region", default_source_value: "🏈 US Central", description: "Where would you like your workspace to live?", @@ -65,19 +43,19 @@ Parameters.args = { "🦘 Australia South", ], }), - createParameterSchema({ + mockParameterSchema({ name: "instance_size", default_source_value: "Big", description: "How large should you instance be?", validation_contains: ["Small", "Medium", "Big"], }), - createParameterSchema({ + mockParameterSchema({ name: "instance_size", default_source_value: "Big", description: "How large should your instance be?", validation_contains: ["Small", "Medium", "Big"], }), - createParameterSchema({ + mockParameterSchema({ name: "disable_docker", description: "Disable Docker?", validation_value_type: "bool", diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx index ed0921d70bb78..00766e0888298 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx @@ -39,6 +39,7 @@ export interface CreateWorkspacePageViewProps { onSubmit: (req: TypesGen.CreateWorkspaceRequest) => void // initialTouched is only used for testing the error state of the form. initialTouched?: FormikTouched + defaultParameterValues?: Record } const { t } = i18n @@ -55,7 +56,7 @@ export const CreateWorkspacePageView: FC< const formFooterStyles = useFormFooterStyles() const [parameterValues, setParameterValues] = useState< Record - >({}) + >(props.defaultParameterValues ?? {}) const form: FormikContextType = useFormik({ @@ -234,6 +235,7 @@ export const CreateWorkspacePageView: FC< { setParameterValues({ ...parameterValues, diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index ffcd3ea8b8b35..2dcebbf05aa40 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -1130,3 +1130,28 @@ export const MockAppearance: TypesGen.AppearanceConfig = { enabled: false, }, } + +export const mockParameterSchema = ( + partial: Partial, +): TypesGen.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, + } +}