Skip to content

feat: Add create workspace page #1589

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 38 commits into from
May 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
a72ba40
feat: Add template page
kylecarbs May 16, 2022
c2699a8
Create xService
kylecarbs May 17, 2022
eb6644f
Update column names
kylecarbs May 17, 2022
3d70c67
Show create template conditionally
kylecarbs May 17, 2022
c63c563
Add template description
kylecarbs May 17, 2022
08069d3
Route to templates
kylecarbs May 17, 2022
584d3a3
Add empty states
kylecarbs May 17, 2022
15637bd
Add tests
kylecarbs May 17, 2022
a22f1ab
Add single template
kylecarbs May 17, 2022
09e8a27
Add tests
kylecarbs May 17, 2022
547a33f
Add state machine for single-page template
kylecarbs May 17, 2022
db9ea20
Add loading indicator
kylecarbs May 17, 2022
eb2ae84
Merge templatepage
kylecarbs May 17, 2022
9ca9db6
Merge branch 'main' into singletemplate
kylecarbs May 18, 2022
1ce3996
Merge branch 'main' into singletemplate
kylecarbs May 18, 2022
c88199a
Add endpoint for fetching latest templates
kylecarbs May 18, 2022
ec28c3d
Embed markdown
kylecarbs May 18, 2022
2f2bf0d
Add workspace create page
kylecarbs May 19, 2022
c70d6a4
feat: Expose the values contained in an HCL validation string to the API
kylecarbs May 19, 2022
c4a07a1
Update codersdk/parameters.go
kylecarbs May 19, 2022
d3e33c8
Call a spade a space
kylecarbs May 19, 2022
2843818
Add API req
kylecarbs May 19, 2022
f422e7b
Fix linting errors with type conversion
kylecarbs May 19, 2022
fc85f98
Merge branch 'paramcontains' into singletemplate
kylecarbs May 19, 2022
9cfe7d7
Add create workspace page
kylecarbs May 19, 2022
a825ece
Add stories for inputs
kylecarbs May 19, 2022
aa4c8ac
Merge branch 'main' into singletemplate
kylecarbs May 19, 2022
88f367d
Remove react-markdown dependency
kylecarbs May 19, 2022
ebf36c2
Improve tests
kylecarbs May 19, 2022
fd6aaf0
Merge branch 'main' into singletemplate
kylecarbs May 19, 2022
c522122
Remove old code
kylecarbs May 19, 2022
72df0ac
Fix test case
kylecarbs May 19, 2022
6273639
Merge branch 'main' into singletemplate
kylecarbs May 19, 2022
507f2f4
Fix linting error
kylecarbs May 19, 2022
2d67bf7
Fix test
kylecarbs May 19, 2022
e5490a7
Merge branch 'main' into singletemplate
BrunoQuaresma May 19, 2022
726ff53
Fix entities
BrunoQuaresma May 19, 2022
3f418a9
Fix storybook
BrunoQuaresma May 19, 2022
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
12 changes: 12 additions & 0 deletions site/src/AppRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { WorkspaceSettingsPage } from "./pages/WorkspaceSettingsPage/WorkspaceSe

const TerminalPage = React.lazy(() => import("./pages/TerminalPage/TerminalPage"))
const WorkspacesPage = React.lazy(() => import("./pages/WorkspacesPage/WorkspacesPage"))
const CreateWorkspacePage = React.lazy(() => import("./pages/CreateWorkspacePage/CreateWorkspacePage"))

export const AppRouter: React.FC = () => (
<React.Suspense fallback={<></>}>
Expand Down Expand Up @@ -83,6 +84,17 @@ export const AppRouter: React.FC = () => (
</AuthAndFrame>
}
/>

<Route path=":template">
<Route
path="new"
element={
<RequireAuth>
<CreateWorkspacePage />
</RequireAuth>
}
/>
</Route>
</Route>

<Route path="users">
Expand Down
52 changes: 23 additions & 29 deletions site/src/api/api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import axios, { AxiosRequestHeaders } from "axios"
import { mutate } from "swr"
import { WorkspaceBuildTransition } from "./types"
import * as TypesGen from "./typesGenerated"

Expand All @@ -22,34 +21,6 @@ export const provisioners: TypesGen.ProvisionerDaemon[] = [
},
]

export namespace Workspace {
export const create = async (
organizationId: string,
request: TypesGen.CreateWorkspaceRequest,
): Promise<TypesGen.Workspace> => {
const response = await fetch(`/api/v2/organizations/${organizationId}/workspaces`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(request),
})

const body = await response.json()
if (!response.ok) {
throw new Error(body.message)
}

// Let SWR know that both the /api/v2/workspaces/* and /api/v2/templates/*
// endpoints will need to fetch new data.
const mutateWorkspacesPromise = mutate("/api/v2/workspaces")
const mutateTemplatesPromise = mutate("/api/v2/templates")
await Promise.all([mutateWorkspacesPromise, mutateTemplatesPromise])

return body
}
}

export const login = async (email: string, password: string): Promise<TypesGen.LoginWithPasswordResponse> => {
const payload = JSON.stringify({
email,
Expand Down Expand Up @@ -115,6 +86,21 @@ export const getTemplates = async (organizationId: string): Promise<TypesGen.Tem
return response.data
}

export const getTemplateByName = async (organizationId: string, name: string): Promise<TypesGen.Template> => {
const response = await axios.get<TypesGen.Template>(`/api/v2/organizations/${organizationId}/templates/${name}`)
return response.data
}

export const getTemplateVersion = async (versionId: string): Promise<TypesGen.TemplateVersion> => {
const response = await axios.get<TypesGen.TemplateVersion>(`/api/v2/templateversions/${versionId}`)
return response.data
}

export const getTemplateVersionSchema = async (versionId: string): Promise<TypesGen.ParameterSchema[]> => {
const response = await axios.get<TypesGen.ParameterSchema[]>(`/api/v2/templateversions/${versionId}/schema`)
return response.data
}

export const getWorkspace = async (workspaceId: string): Promise<TypesGen.Workspace> => {
const response = await axios.get<TypesGen.Workspace>(`/api/v2/workspaces/${workspaceId}`)
return response.data
Expand Down Expand Up @@ -180,6 +166,14 @@ export const createUser = async (user: TypesGen.CreateUserRequest): Promise<Type
return response.data
}

export const createWorkspace = async (
organizationId: string,
workspace: TypesGen.CreateWorkspaceRequest,
): Promise<TypesGen.Workspace> => {
const response = await axios.post<TypesGen.Workspace>(`/api/v2/organizations/${organizationId}/workspaces`, workspace)
return response.data
}

export const getBuildInfo = async (): Promise<TypesGen.BuildInfoResponse> => {
const response = await axios.get("/api/v2/buildinfo")
return response.data
Expand Down
52 changes: 52 additions & 0 deletions site/src/components/ParameterInput/ParameterInput.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Story } from "@storybook/react"
import React from "react"
import { ParameterSchema } from "../../api/typesGenerated"
import { ParameterInput, ParameterInputProps } from "./ParameterInput"

export default {
title: "components/ParameterInput",
component: ParameterInput,
}

const Template: Story<ParameterInputProps> = (args: ParameterInputProps) => <ParameterInput {...args} />

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 const Basic = Template.bind({})
Basic.args = {
schema: createParameterSchema({
name: "project_name",
description: "Customize the name of a Google Cloud project that will be created!",
}),
}

export const Contains = Template.bind({})
Contains.args = {
schema: createParameterSchema({
name: "region",
default_source_value: "🏈 US Central",
description: "Where would you like your workspace to live?",
validation_contains: ["🏈 US Central", "⚽ Brazil East", "💶 EU West", "🦘 Australia South"],
}),
}
94 changes: 94 additions & 0 deletions site/src/components/ParameterInput/ParameterInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import FormControlLabel from "@material-ui/core/FormControlLabel"
import Paper from "@material-ui/core/Paper"
import Radio from "@material-ui/core/Radio"
import RadioGroup from "@material-ui/core/RadioGroup"
import { lighten, makeStyles } from "@material-ui/core/styles"
import TextField from "@material-ui/core/TextField"
import React from "react"
import { ParameterSchema } from "../../api/typesGenerated"
import { MONOSPACE_FONT_FAMILY } from "../../theme/constants"

export interface ParameterInputProps {
disabled?: boolean
schema: ParameterSchema
onChange: (value: string) => void
}

export const ParameterInput: React.FC<ParameterInputProps> = ({ disabled, onChange, schema }) => {
const styles = useStyles()
return (
<Paper className={styles.paper}>
<div className={styles.title}>
<h2>var.{schema.name}</h2>
{schema.description && <span>{schema.description}</span>}
</div>
<div className={styles.input}>
<ParameterField disabled={disabled} onChange={onChange} schema={schema} />
</div>
</Paper>
)
}

const ParameterField: React.FC<ParameterInputProps> = ({ disabled, onChange, schema }) => {
if (schema.validation_contains.length > 0) {
return (
<RadioGroup
defaultValue={schema.default_source_value}
onChange={(event) => {
onChange(event.target.value)
}}
>
{schema.validation_contains.map((item) => (
<FormControlLabel
disabled={disabled}
key={item}
value={item}
control={<Radio color="primary" size="small" disableRipple />}
label={item}
/>
))}
</RadioGroup>
)
}

// A text field can technically handle all cases!
// As other cases become more prominent (like filtering for numbers),
// we should break this out into more finely scoped input fields.
return (
<TextField
size="small"
disabled={disabled}
placeholder={schema.default_source_value}
onChange={(event) => {
onChange(event.target.value)
}}
/>
)
}

const useStyles = makeStyles((theme) => ({
paper: {
display: "flex",
flexDirection: "column",
fontFamily: MONOSPACE_FONT_FAMILY,
},
title: {
background: lighten(theme.palette.background.default, 0.1),
borderBottom: `1px solid ${theme.palette.divider}`,
padding: theme.spacing(3),
display: "flex",
flexDirection: "column",
"& h2": {
margin: 0,
},
"& span": {
paddingTop: theme.spacing(2),
},
},
input: {
padding: theme.spacing(3),
display: "flex",
flexDirection: "column",
maxWidth: 480,
},
}))
64 changes: 0 additions & 64 deletions site/src/forms/CreateWorkspaceForm.test.tsx

This file was deleted.

Loading