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

Skip to content

Commit 9cfe7d7

Browse files
committed
Add create workspace page
1 parent fc85f98 commit 9cfe7d7

File tree

15 files changed

+285
-207
lines changed

15 files changed

+285
-207
lines changed

site/src/AppRouter.tsx

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import { OrgsPage } from "./pages/OrgsPage/OrgsPage"
1212
import { SettingsPage } from "./pages/SettingsPage/SettingsPage"
1313
import { AccountPage } from "./pages/SettingsPages/AccountPage/AccountPage"
1414
import { SSHKeysPage } from "./pages/SettingsPages/SSHKeysPage/SSHKeysPage"
15-
import TemplatePage from "./pages/TemplatePage/TemplatePage"
1615
import TemplatesPage from "./pages/TemplatesPage/TemplatesPage"
1716
import { CreateUserPage } from "./pages/UsersPage/CreateUserPage/CreateUserPage"
1817
import { UsersPage } from "./pages/UsersPage/UsersPage"
@@ -86,28 +85,16 @@ export const AppRouter: React.FC = () => (
8685
}
8786
/>
8887

88+
<Route path=":template">
8989
<Route
90-
path=":template">
91-
<Route
92-
index
93-
element={
94-
<AuthAndFrame>
95-
<TemplatePage />
96-
</AuthAndFrame>
97-
}
98-
/>
99-
100-
<Route
101-
path="new"
102-
element={
103-
<AuthAndFrame>
104-
<CreateWorkspacePage />
105-
</AuthAndFrame>
106-
}
107-
/>
108-
</Route>
109-
110-
90+
path="new"
91+
element={
92+
<RequireAuth>
93+
<CreateWorkspacePage />
94+
</RequireAuth>
95+
}
96+
/>
97+
</Route>
11198
</Route>
11299

113100
<Route path="users">

site/src/api/api.ts

Lines changed: 13 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,
@@ -125,6 +96,11 @@ export const getTemplateVersion = async (versionId: string): Promise<TypesGen.Te
12596
return response.data
12697
}
12798

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+
128104
export const getWorkspace = async (workspaceId: string): Promise<TypesGen.Workspace> => {
129105
const response = await axios.get<TypesGen.Workspace>(`/api/v2/workspaces/${workspaceId}`)
130106
return response.data
@@ -175,6 +151,14 @@ export const createUser = async (user: TypesGen.CreateUserRequest): Promise<Type
175151
return response.data
176152
}
177153

154+
export const createWorkspace = async (
155+
organizationId: string,
156+
workspace: TypesGen.CreateWorkspaceRequest,
157+
): Promise<TypesGen.Workspace> => {
158+
const response = await axios.post<TypesGen.Workspace>(`/api/v2/organizations/${organizationId}/workspaces`, workspace)
159+
return response.data
160+
}
161+
178162
export const getBuildInfo = async (): Promise<TypesGen.BuildInfoResponse> => {
179163
const response = await axios.get("/api/v2/buildinfo")
180164
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: "",
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: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { FormControlLabel, lighten, Paper, Radio, RadioGroup, TextField } from "@material-ui/core"
2+
import { makeStyles } from "@material-ui/core/styles"
3+
import React from "react"
4+
import { ParameterSchema } from "../../api/typesGenerated"
5+
import { MONOSPACE_FONT_FAMILY } from "../../theme/constants"
6+
7+
export interface ParameterInputProps {
8+
disabled?: boolean
9+
schema: ParameterSchema
10+
onChange: (value: string) => void
11+
}
12+
13+
export const ParameterInput: React.FC<ParameterInputProps> = ({ disabled, onChange, schema }) => {
14+
const styles = useStyles()
15+
return (
16+
<Paper className={styles.paper}>
17+
<div className={styles.title}>
18+
<h2>var.{schema.name}</h2>
19+
{schema.description && <span>{schema.description}</span>}
20+
</div>
21+
<div className={styles.input}>
22+
<ParameterField disabled={disabled} onChange={onChange} schema={schema} />
23+
</div>
24+
</Paper>
25+
)
26+
}
27+
28+
const ParameterField: React.FC<ParameterInputProps> = ({ disabled, onChange, schema }) => {
29+
if (schema.validation_contains.length > 0) {
30+
return (
31+
<RadioGroup
32+
defaultValue={schema.default_source_value}
33+
onChange={(event) => {
34+
onChange(event.target.value)
35+
}}
36+
>
37+
{schema.validation_contains.map((item) => (
38+
<FormControlLabel
39+
disabled={disabled}
40+
key={item}
41+
value={item}
42+
control={<Radio color="primary" size="small" disableRipple />}
43+
label={item}
44+
/>
45+
))}
46+
</RadioGroup>
47+
)
48+
}
49+
50+
// A text field can technically handle all cases!
51+
// As other cases become more prominent (like filtering for numbers),
52+
// we should break this out into more finely scoped input fields.
53+
return (
54+
<TextField
55+
size="small"
56+
disabled={disabled}
57+
placeholder={schema.default_source_value}
58+
onChange={(event) => {
59+
onChange(event.target.value)
60+
}}
61+
/>
62+
)
63+
}
64+
65+
const useStyles = makeStyles((theme) => ({
66+
paper: {
67+
display: "flex",
68+
flexDirection: "column",
69+
fontFamily: MONOSPACE_FONT_FAMILY,
70+
},
71+
title: {
72+
background: lighten(theme.palette.background.default, 0.1),
73+
borderBottom: `1px solid ${theme.palette.divider}`,
74+
padding: theme.spacing(3),
75+
display: "flex",
76+
flexDirection: "column",
77+
"& h2": {
78+
margin: 0,
79+
},
80+
"& span": {
81+
paddingTop: theme.spacing(2),
82+
},
83+
},
84+
input: {
85+
padding: theme.spacing(3),
86+
display: "flex",
87+
flexDirection: "column",
88+
maxWidth: 480,
89+
},
90+
}))

site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { useMachine } from "@xstate/react"
22
import React from "react"
3+
import { useNavigate } from "react-router"
34
import { useParams } from "react-router-dom"
5+
import { createWorkspace } from "../../api/api"
46
import { templateMachine } from "../../xServices/template/templateXService"
57
import { CreateWorkspacePageView } from "./CreateWorkspacePageView"
68

@@ -11,11 +13,25 @@ const CreateWorkspacePage: React.FC = () => {
1113
name: template,
1214
},
1315
})
16+
const navigate = useNavigate()
17+
const loading = templateState.hasTag("loading")
18+
if (!templateState.context.template || !templateState.context.templateSchema) {
19+
return null
20+
}
1421

1522
return (
1623
<CreateWorkspacePageView
1724
template={templateState.context.template}
18-
templateVersion={templateState.context.templateVersion}
25+
templateSchema={templateState.context.templateSchema}
26+
loading={loading}
27+
onCancel={() => navigate("/templates/" + templateState.context.template?.name)}
28+
onSubmit={async (req) => {
29+
if (!templateState.context.template) {
30+
throw new Error("template isn't valid")
31+
}
32+
const workspace = await createWorkspace(templateState.context.template.organization_id, req)
33+
navigate("/workspaces/" + workspace.id)
34+
}}
1935
/>
2036
)
2137
}

site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.stories.tsx

Whitespace-only changes.

0 commit comments

Comments
 (0)