Skip to content

feat: Pre-fill param inputs with query string values #5758

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 16 additions & 7 deletions site/src/components/ParameterInput/ParameterInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,15 @@ export interface ParameterInputProps {
disabled?: boolean
schema: ParameterSchema
onChange: (value: string) => void
defaultValue?: string
}

export const ParameterInput: FC<
React.PropsWithChildren<ParameterInputProps>
> = ({ disabled, onChange, schema }) => {
export const ParameterInput: FC<ParameterInputProps> = ({
disabled,
onChange,
schema,
defaultValue,
}) => {
const styles = useStyles()

return (
Expand All @@ -50,21 +54,25 @@ export const ParameterInput: FC<
disabled={disabled}
onChange={onChange}
schema={schema}
defaultValue={defaultValue}
/>
</div>
</Stack>
)
}

const ParameterField: React.FC<
React.PropsWithChildren<ParameterInputProps>
> = ({ disabled, onChange, schema }) => {
const ParameterField: React.FC<ParameterInputProps> = ({
disabled,
onChange,
schema,
defaultValue,
}) => {
if (schema.validation_contains && schema.validation_contains.length > 0) {
return (
<TextField
id={schema.name}
size="small"
defaultValue={schema.default_source_value}
defaultValue={defaultValue ?? schema.default_source_value}
placeholder={schema.default_source_value}
disabled={disabled}
onChange={(event) => {
Expand Down Expand Up @@ -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)
}}
Expand Down
20 changes: 20 additions & 0 deletions site/src/pages/CreateWorkspacePage/CreateWorkspacePage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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(<CreateWorkspacePage />, {
route:
"/templates/" +
MockTemplate.name +
`/workspace?param.${param}=${paramValue}`,
path: "/templates/:template/workspace",
})
await screen.findByDisplayValue(paramValue)
})
})
35 changes: 24 additions & 11 deletions site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,33 @@
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) => {
navigate(`/@${event.data.owner_name}/${event.data.name}`)
},
},
})

const {
templates,
templateSchema,
Expand All @@ -42,13 +38,16 @@ const CreateWorkspacePage: FC = () => {
permissions,
owner,
} = createWorkspaceState.context
const [searchParams] = useSearchParams()
const defaultParameterValues = getDefaultParameterValues(searchParams)

return (
<>
<Helmet>
<title>{pageTitle("Create Workspace")}</title>
</Helmet>
<CreateWorkspacePageView
defaultParameterValues={defaultParameterValues}
loadingTemplates={createWorkspaceState.matches("gettingTemplates")}
loadingTemplateSchema={createWorkspaceState.matches(
"gettingTemplateSchema",
Expand Down Expand Up @@ -89,4 +88,18 @@ const CreateWorkspacePage: FC = () => {
)
}

const getDefaultParameterValues = (
urlSearchParams: URLSearchParams,
): Record<string, string> => {
const paramValues: Record<string, string> = {}
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
Original file line number Diff line number Diff line change
@@ -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>,
): 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,
Expand All @@ -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?",
Expand All @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<TypesGen.CreateWorkspaceRequest>
defaultParameterValues?: Record<string, string>
}

const { t } = i18n
Expand All @@ -55,7 +56,7 @@ export const CreateWorkspacePageView: FC<
const formFooterStyles = useFormFooterStyles()
const [parameterValues, setParameterValues] = useState<
Record<string, string>
>({})
>(props.defaultParameterValues ?? {})

const form: FormikContextType<TypesGen.CreateWorkspaceRequest> =
useFormik<TypesGen.CreateWorkspaceRequest>({
Expand Down Expand Up @@ -234,6 +235,7 @@ export const CreateWorkspacePageView: FC<
<ParameterInput
disabled={form.isSubmitting}
key={schema.id}
defaultValue={parameterValues[schema.name]}
onChange={(value) => {
setParameterValues({
...parameterValues,
Expand Down
25 changes: 25 additions & 0 deletions site/src/testHelpers/entities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1130,3 +1130,28 @@ export const MockAppearance: TypesGen.AppearanceConfig = {
enabled: false,
},
}

export const mockParameterSchema = (
partial: Partial<TypesGen.ParameterSchema>,
): 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,
}
}