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

Skip to content

Commit fe71988

Browse files
committed
Add upload
1 parent d39cabe commit fe71988

File tree

5 files changed

+220
-58
lines changed

5 files changed

+220
-58
lines changed

site/src/api/api.ts

Lines changed: 8 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -744,60 +744,13 @@ export const getTemplateExamples = async (
744744
return response.data
745745
}
746746

747-
// for creating a new template:
748-
// 1. upload template tar or use the example ID
749-
// 2. create template version
750-
// 3. wait for it to complete
751-
// 4. if the job failed with the missing parameter error then:
752-
// a. prompt for params
753-
// b. create template version again with the same file hash
754-
// c. wait for it to complete
755-
// 5.create template with the successful template version ID
756-
// https://github.com/coder/coder/blob/b6703b11c6578b2f91a310d28b6a7e57f0069be6/cli/templatecreate.go#L169-L170
757-
export const createValidTemplate = async (
758-
organizationId: string,
759-
exampleId: string,
760-
// The template_version_id is calculated in the function
761-
data: Omit<TypesGen.CreateTemplateRequest, "template_version_id">,
762-
): Promise<TypesGen.Template> => {
763-
// Step 2.
764-
let version = await createTemplateVersion(organizationId, {
765-
storage_method: "file",
766-
example_id: exampleId,
767-
provisioner: "terraform",
768-
tags: {},
769-
})
770-
771-
// Step 3.
772-
let status = version.job.status
773-
while (["pending", "running"].includes(status)) {
774-
version = await getTemplateVersion(version.id)
775-
status = version.job.status
776-
}
777-
if (status === "failed") {
778-
console.error(version.job.error)
779-
throw new Error(version.job.error)
780-
}
781-
782-
// // Get schema and create a new template version with the parameters
783-
// const schema = await getTemplateVersionSchema(version.id)
784-
// version = await createTemplateVersion(organizationId, {
785-
// storage_method: "file",
786-
// example_id: exampleId,
787-
// provisioner: "terraform",
788-
// tags: {},
789-
// parameter_values: schema.map((parameter) => ({
790-
// name: parameter.name,
791-
// source_scheme: parameter.default_source_scheme,
792-
// destination_scheme: parameter.default_destination_scheme,
793-
// source_value: parameter.default_source_value,
794-
// })),
795-
// })
796-
797-
// Step 5.
798-
const template = await createTemplate(organizationId, {
799-
...data,
800-
template_version_id: version.id,
747+
export const uploadTemplateFile = async (
748+
file: File,
749+
): Promise<TypesGen.UploadResponse> => {
750+
const response = await axios.post("/api/v2/files", file, {
751+
headers: {
752+
"Content-Type": "application/x-tar",
753+
},
801754
})
802-
return template
755+
return response.data
803756
}

site/src/pages/CreateTemplatePage/CreateTemplateForm.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ import { FormFooter } from "components/FormFooter/FormFooter"
66
import { IconField } from "components/IconField/IconField"
77
import { ParameterInput } from "components/ParameterInput/ParameterInput"
88
import { Stack } from "components/Stack/Stack"
9+
import {
10+
TemplateUpload,
11+
TemplateUploadProps,
12+
} from "pages/CreateTemplatePage/TemplateUpload"
913
import { useFormik } from "formik"
1014
import { SelectedTemplate } from "pages/CreateWorkspacePage/SelectedTemplate"
1115
import { FC } from "react"
@@ -55,6 +59,7 @@ interface CreateTemplateFormProps {
5559
isSubmitting: boolean
5660
onCancel: () => void
5761
onSubmit: (data: CreateTemplateData) => void
62+
upload: TemplateUploadProps
5863
}
5964

6065
export const CreateTemplateForm: FC<CreateTemplateFormProps> = ({
@@ -64,6 +69,7 @@ export const CreateTemplateForm: FC<CreateTemplateFormProps> = ({
6469
isSubmitting,
6570
onCancel,
6671
onSubmit,
72+
upload,
6773
}) => {
6874
const styles = useStyles()
6975
const formFooterStyles = useFormFooterStyles()
@@ -90,7 +96,11 @@ export const CreateTemplateForm: FC<CreateTemplateFormProps> = ({
9096
</div>
9197

9298
<Stack direction="column" className={styles.formSectionFields}>
93-
{starterTemplate && <SelectedTemplate template={starterTemplate} />}
99+
{starterTemplate ? (
100+
<SelectedTemplate template={starterTemplate} />
101+
) : (
102+
<TemplateUpload {...upload} />
103+
)}
94104

95105
<TextField
96106
{...getFieldHelpers("name")}

site/src/pages/CreateTemplatePage/CreateTemplatePage.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const CreateTemplatePage: FC = () => {
2727
},
2828
},
2929
})
30-
const { starterTemplate, parameters, error } = state.context
30+
const { starterTemplate, parameters, error, file } = state.context
3131
const shouldDisplayForm = !state.hasTag("loading")
3232

3333
const onCancel = () => {
@@ -58,6 +58,16 @@ const CreateTemplatePage: FC = () => {
5858
data,
5959
})
6060
}}
61+
upload={{
62+
file,
63+
isUploading: state.matches("uploading"),
64+
onRemove: () => {
65+
send("REMOVE_FILE")
66+
},
67+
onUpload: (file) => {
68+
send({ type: "UPLOAD_FILE", file })
69+
},
70+
}}
6171
/>
6272
)}
6373
</FullPageHorizontalForm>
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import { makeStyles } from "@material-ui/core/styles"
2+
import { Stack } from "components/Stack/Stack"
3+
import { FC, useRef } from "react"
4+
import UploadIcon from "@material-ui/icons/CloudUploadOutlined"
5+
import { useClickable } from "hooks/useClickable"
6+
import CircularProgress from "@material-ui/core/CircularProgress"
7+
import { combineClasses } from "util/combineClasses"
8+
import IconButton from "@material-ui/core/IconButton"
9+
import RemoveIcon from "@material-ui/icons/DeleteOutline"
10+
import FileIcon from "@material-ui/icons/FolderOutlined"
11+
12+
export interface TemplateUploadProps {
13+
isUploading: boolean
14+
onUpload: (file: File) => void
15+
onRemove: () => void
16+
file?: File
17+
}
18+
19+
export const TemplateUpload: FC<TemplateUploadProps> = ({
20+
isUploading,
21+
onUpload,
22+
onRemove,
23+
file,
24+
}) => {
25+
const styles = useStyles()
26+
const inputRef = useRef<HTMLInputElement>(null)
27+
const clickable = useClickable(() => {
28+
if (inputRef.current) {
29+
inputRef.current.click()
30+
}
31+
})
32+
33+
if (!isUploading && file) {
34+
return (
35+
<Stack
36+
className={styles.file}
37+
direction="row"
38+
justifyContent="space-between"
39+
alignItems="center"
40+
>
41+
<Stack direction="row" alignItems="center">
42+
<FileIcon />
43+
<span>{file.name}</span>
44+
</Stack>
45+
46+
<IconButton title="Remove file" size="small" onClick={onRemove}>
47+
<RemoveIcon />
48+
</IconButton>
49+
</Stack>
50+
)
51+
}
52+
53+
return (
54+
<>
55+
<div
56+
className={combineClasses({
57+
[styles.root]: true,
58+
[styles.disabled]: isUploading,
59+
})}
60+
{...clickable}
61+
>
62+
<Stack alignItems="center" spacing={1}>
63+
{isUploading ? (
64+
<CircularProgress size={32} />
65+
) : (
66+
<UploadIcon className={styles.icon} />
67+
)}
68+
69+
<Stack alignItems="center" spacing={0.5}>
70+
<span className={styles.title}>Upload template</span>
71+
<span className={styles.description}>
72+
The template needs to be in a .tar file
73+
</span>
74+
</Stack>
75+
</Stack>
76+
</div>
77+
78+
<input
79+
type="file"
80+
ref={inputRef}
81+
className={styles.input}
82+
accept=".tar"
83+
onChange={(event) => {
84+
const file = event.currentTarget.files?.[0]
85+
if (file) {
86+
onUpload(file)
87+
}
88+
}}
89+
/>
90+
</>
91+
)
92+
}
93+
94+
const useStyles = makeStyles((theme) => ({
95+
root: {
96+
display: "flex",
97+
alignItems: "center",
98+
justifyContent: "center",
99+
borderRadius: theme.shape.borderRadius,
100+
border: `2px dashed ${theme.palette.divider}`,
101+
padding: theme.spacing(6),
102+
cursor: "pointer",
103+
104+
"&:hover": {
105+
backgroundColor: theme.palette.background.paper,
106+
},
107+
},
108+
109+
disabled: {
110+
pointerEvents: "none",
111+
opacity: 0.75,
112+
},
113+
114+
icon: {
115+
fontSize: theme.spacing(8),
116+
},
117+
118+
title: {
119+
fontSize: theme.spacing(2),
120+
},
121+
122+
description: {
123+
color: theme.palette.text.secondary,
124+
},
125+
126+
input: {
127+
display: "none",
128+
},
129+
130+
file: {
131+
borderRadius: theme.shape.borderRadius,
132+
border: `1px solid ${theme.palette.divider}`,
133+
padding: theme.spacing(2),
134+
background: theme.palette.background.paper,
135+
},
136+
}))

site/src/xServices/createTemplate/createTemplateXService.ts

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,30 @@ import {
44
getTemplateVersion,
55
createTemplate,
66
getTemplateVersionSchema,
7+
uploadTemplateFile,
78
} from "api/api"
89
import {
910
CreateTemplateRequest,
1011
ParameterSchema,
1112
Template,
1213
TemplateExample,
1314
TemplateVersion,
15+
UploadResponse,
1416
} from "api/typesGenerated"
1517
import { displayError } from "components/GlobalSnackbar/utils"
1618
import { assign, createMachine } from "xstate"
1719

20+
// for creating a new template:
21+
// 1. upload template tar or use the example ID
22+
// 2. create template version
23+
// 3. wait for it to complete
24+
// 4. if the job failed with the missing parameter error then:
25+
// a. prompt for params
26+
// b. create template version again with the same file hash
27+
// c. wait for it to complete
28+
// 5.create template with the successful template version ID
29+
// https://github.com/coder/coder/blob/b6703b11c6578b2f91a310d28b6a7e57f0069be6/cli/templatecreate.go#L169-L170
30+
1831
export interface CreateTemplateData {
1932
name: string
2033
display_name: string
@@ -33,6 +46,10 @@ interface CreateTemplateContext {
3346
version?: TemplateVersion
3447
templateData?: CreateTemplateData
3548
parameters?: ParameterSchema[]
49+
// file is used in the FE to show the filename and some other visual stuff
50+
// uploadedFile is the response from the server to use in the API
51+
file?: File
52+
uploadResponse?: UploadResponse
3653
}
3754

3855
export const createTemplateMachine =
@@ -43,8 +60,14 @@ export const createTemplateMachine =
4360
predictableActionArguments: true,
4461
schema: {
4562
context: {} as CreateTemplateContext,
46-
events: {} as { type: "CREATE"; data: CreateTemplateData },
63+
events: {} as
64+
| { type: "CREATE"; data: CreateTemplateData }
65+
| { type: "UPLOAD_FILE"; file: File }
66+
| { type: "REMOVE_FILE" },
4767
services: {} as {
68+
uploadFile: {
69+
data: UploadResponse
70+
}
4871
loadStarterTemplate: {
4972
data: TemplateExample
5073
}
@@ -92,6 +115,26 @@ export const createTemplateMachine =
92115
target: "creating",
93116
actions: ["assignTemplateData"],
94117
},
118+
UPLOAD_FILE: {
119+
actions: ["assignFile"],
120+
target: "uploading",
121+
},
122+
REMOVE_FILE: {
123+
actions: ["removeFile"],
124+
},
125+
},
126+
},
127+
uploading: {
128+
invoke: {
129+
src: "uploadFile",
130+
onDone: {
131+
target: "idle",
132+
actions: ["assignUploadResponse"],
133+
},
134+
onError: {
135+
target: "idle",
136+
actions: ["displayUploadError", "removeFile"],
137+
},
95138
},
96139
},
97140
creating: {
@@ -179,6 +222,7 @@ export const createTemplateMachine =
179222
},
180223
{
181224
services: {
225+
uploadFile: (_, { file }) => uploadTemplateFile(file),
182226
loadStarterTemplate: async ({ organizationId, exampleId }) => {
183227
if (!exampleId) {
184228
throw new Error(`Example ID is not defined.`)
@@ -270,12 +314,21 @@ export const createTemplateMachine =
270314
displayJobError: (_, { data }) => {
271315
displayError("Provisioner job failed.", data.job.error)
272316
},
317+
displayUploadError: () => {
318+
displayError("Error on upload the file.")
319+
},
273320
assignStarterTemplate: assign({
274321
starterTemplate: (_, { data }) => data,
275322
}),
276323
assignVersion: assign({ version: (_, { data }) => data }),
277324
assignTemplateData: assign({ templateData: (_, { data }) => data }),
278325
assignParameters: assign({ parameters: (_, { data }) => data }),
326+
assignFile: assign({ file: (_, { file }) => file }),
327+
assignUploadResponse: assign({ uploadResponse: (_, { data }) => data }),
328+
removeFile: assign({
329+
file: (_) => undefined,
330+
uploadResponse: (_) => undefined,
331+
}),
279332
},
280333
guards: {
281334
isExampleProvided: ({ exampleId }) => Boolean(exampleId),

0 commit comments

Comments
 (0)