Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 203102b

Browse files
committed
feat: Add create workspace page (#1589)
1 parent afc4575 commit 203102b

17 files changed

+652
-380
lines changed

site/src/AppRouter.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { WorkspaceSettingsPage } from "./pages/WorkspaceSettingsPage/WorkspaceSe
2121

2222
const TerminalPage = React.lazy(() => import("./pages/TerminalPage/TerminalPage"))
2323
const WorkspacesPage = React.lazy(() => import("./pages/WorkspacesPage/WorkspacesPage"))
24+
const CreateWorkspacePage = React.lazy(() => import("./pages/CreateWorkspacePage/CreateWorkspacePage"))
2425

2526
export const AppRouter: React.FC = () => (
2627
<React.Suspense fallback={<></>}>
@@ -84,6 +85,17 @@ export const AppRouter: React.FC = () => (
8485
</AuthAndFrame>
8586
}
8687
/>
88+
89+
<Route path=":template">
90+
<Route
91+
path="new"
92+
element={
93+
<RequireAuth>
94+
<CreateWorkspacePage />
95+
</RequireAuth>
96+
}
97+
/>
98+
</Route>
8799
</Route>
88100

89101
<Route path="users">

site/src/api/api.ts

Lines changed: 23 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import axios, { AxiosRequestHeaders } from "axios"
2-
import { mutate } from "swr"
32
import { WorkspaceBuildTransition } from "./types"
43
import * as TypesGen from "./typesGenerated"
54

@@ -22,34 +21,6 @@ export const provisioners: TypesGen.ProvisionerDaemon[] = [
2221
},
2322
]
2423

25-
export namespace Workspace {
26-
export const create = async (
27-
organizationId: string,
28-
request: TypesGen.CreateWorkspaceRequest,
29-
): Promise<TypesGen.Workspace> => {
30-
const response = await fetch(`/api/v2/organizations/${organizationId}/workspaces`, {
31-
method: "POST",
32-
headers: {
33-
"Content-Type": "application/json",
34-
},
35-
body: JSON.stringify(request),
36-
})
37-
38-
const body = await response.json()
39-
if (!response.ok) {
40-
throw new Error(body.message)
41-
}
42-
43-
// Let SWR know that both the /api/v2/workspaces/* and /api/v2/templates/*
44-
// endpoints will need to fetch new data.
45-
const mutateWorkspacesPromise = mutate("/api/v2/workspaces")
46-
const mutateTemplatesPromise = mutate("/api/v2/templates")
47-
await Promise.all([mutateWorkspacesPromise, mutateTemplatesPromise])
48-
49-
return body
50-
}
51-
}
52-
5324
export const login = async (email: string, password: string): Promise<TypesGen.LoginWithPasswordResponse> => {
5425
const payload = JSON.stringify({
5526
email,
@@ -115,6 +86,21 @@ export const getTemplates = async (organizationId: string): Promise<TypesGen.Tem
11586
return response.data
11687
}
11788

89+
export const getTemplateByName = async (organizationId: string, name: string): Promise<TypesGen.Template> => {
90+
const response = await axios.get<TypesGen.Template>(`/api/v2/organizations/${organizationId}/templates/${name}`)
91+
return response.data
92+
}
93+
94+
export const getTemplateVersion = async (versionId: string): Promise<TypesGen.TemplateVersion> => {
95+
const response = await axios.get<TypesGen.TemplateVersion>(`/api/v2/templateversions/${versionId}`)
96+
return response.data
97+
}
98+
99+
export const getTemplateVersionSchema = async (versionId: string): Promise<TypesGen.ParameterSchema[]> => {
100+
const response = await axios.get<TypesGen.ParameterSchema[]>(`/api/v2/templateversions/${versionId}/schema`)
101+
return response.data
102+
}
103+
118104
export const getWorkspace = async (workspaceId: string): Promise<TypesGen.Workspace> => {
119105
const response = await axios.get<TypesGen.Workspace>(`/api/v2/workspaces/${workspaceId}`)
120106
return response.data
@@ -180,6 +166,14 @@ export const createUser = async (user: TypesGen.CreateUserRequest): Promise<Type
180166
return response.data
181167
}
182168

169+
export const createWorkspace = async (
170+
organizationId: string,
171+
workspace: TypesGen.CreateWorkspaceRequest,
172+
): Promise<TypesGen.Workspace> => {
173+
const response = await axios.post<TypesGen.Workspace>(`/api/v2/organizations/${organizationId}/workspaces`, workspace)
174+
return response.data
175+
}
176+
183177
export const getBuildInfo = async (): Promise<TypesGen.BuildInfoResponse> => {
184178
const response = await axios.get("/api/v2/buildinfo")
185179
return response.data
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { Story } from "@storybook/react"
2+
import React from "react"
3+
import { ParameterSchema } from "../../api/typesGenerated"
4+
import { ParameterInput, ParameterInputProps } from "./ParameterInput"
5+
6+
export default {
7+
title: "components/ParameterInput",
8+
component: ParameterInput,
9+
}
10+
11+
const Template: Story<ParameterInputProps> = (args: ParameterInputProps) => <ParameterInput {...args} />
12+
13+
const createParameterSchema = (partial: Partial<ParameterSchema>): ParameterSchema => {
14+
return {
15+
id: "000000",
16+
job_id: "000000",
17+
allow_override_destination: false,
18+
allow_override_source: true,
19+
created_at: "",
20+
default_destination_scheme: "none",
21+
default_refresh: "",
22+
default_source_scheme: "data",
23+
default_source_value: "default-value",
24+
name: "parameter name",
25+
description: "Some description!",
26+
redisplay_value: false,
27+
validation_condition: "",
28+
validation_contains: [],
29+
validation_error: "",
30+
validation_type_system: "",
31+
validation_value_type: "",
32+
...partial,
33+
}
34+
}
35+
36+
export const Basic = Template.bind({})
37+
Basic.args = {
38+
schema: createParameterSchema({
39+
name: "project_name",
40+
description: "Customize the name of a Google Cloud project that will be created!",
41+
}),
42+
}
43+
44+
export const Contains = Template.bind({})
45+
Contains.args = {
46+
schema: createParameterSchema({
47+
name: "region",
48+
default_source_value: "🏈 US Central",
49+
description: "Where would you like your workspace to live?",
50+
validation_contains: ["🏈 US Central", "⚽ Brazil East", "💶 EU West", "🦘 Australia South"],
51+
}),
52+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import FormControlLabel from "@material-ui/core/FormControlLabel"
2+
import Paper from "@material-ui/core/Paper"
3+
import Radio from "@material-ui/core/Radio"
4+
import RadioGroup from "@material-ui/core/RadioGroup"
5+
import { lighten, makeStyles } from "@material-ui/core/styles"
6+
import TextField from "@material-ui/core/TextField"
7+
import React from "react"
8+
import { ParameterSchema } from "../../api/typesGenerated"
9+
import { MONOSPACE_FONT_FAMILY } from "../../theme/constants"
10+
11+
export interface ParameterInputProps {
12+
disabled?: boolean
13+
schema: ParameterSchema
14+
onChange: (value: string) => void
15+
}
16+
17+
export const ParameterInput: React.FC<ParameterInputProps> = ({ disabled, onChange, schema }) => {
18+
const styles = useStyles()
19+
return (
20+
<Paper className={styles.paper}>
21+
<div className={styles.title}>
22+
<h2>var.{schema.name}</h2>
23+
{schema.description && <span>{schema.description}</span>}
24+
</div>
25+
<div className={styles.input}>
26+
<ParameterField disabled={disabled} onChange={onChange} schema={schema} />
27+
</div>
28+
</Paper>
29+
)
30+
}
31+
32+
const ParameterField: React.FC<ParameterInputProps> = ({ disabled, onChange, schema }) => {
33+
if (schema.validation_contains.length > 0) {
34+
return (
35+
<RadioGroup
36+
defaultValue={schema.default_source_value}
37+
onChange={(event) => {
38+
onChange(event.target.value)
39+
}}
40+
>
41+
{schema.validation_contains.map((item) => (
42+
<FormControlLabel
43+
disabled={disabled}
44+
key={item}
45+
value={item}
46+
control={<Radio color="primary" size="small" disableRipple />}
47+
label={item}
48+
/>
49+
))}
50+
</RadioGroup>
51+
)
52+
}
53+
54+
// A text field can technically handle all cases!
55+
// As other cases become more prominent (like filtering for numbers),
56+
// we should break this out into more finely scoped input fields.
57+
return (
58+
<TextField
59+
size="small"
60+
disabled={disabled}
61+
placeholder={schema.default_source_value}
62+
onChange={(event) => {
63+
onChange(event.target.value)
64+
}}
65+
/>
66+
)
67+
}
68+
69+
const useStyles = makeStyles((theme) => ({
70+
paper: {
71+
display: "flex",
72+
flexDirection: "column",
73+
fontFamily: MONOSPACE_FONT_FAMILY,
74+
},
75+
title: {
76+
background: lighten(theme.palette.background.default, 0.1),
77+
borderBottom: `1px solid ${theme.palette.divider}`,
78+
padding: theme.spacing(3),
79+
display: "flex",
80+
flexDirection: "column",
81+
"& h2": {
82+
margin: 0,
83+
},
84+
"& span": {
85+
paddingTop: theme.spacing(2),
86+
},
87+
},
88+
input: {
89+
padding: theme.spacing(3),
90+
display: "flex",
91+
flexDirection: "column",
92+
maxWidth: 480,
93+
},
94+
}))

site/src/forms/CreateWorkspaceForm.test.tsx

Lines changed: 0 additions & 64 deletions
This file was deleted.

0 commit comments

Comments
 (0)