Skip to content

Commit 68ff6f8

Browse files
committed
Add base structure for copy template
1 parent dd4e1f7 commit 68ff6f8

File tree

4 files changed

+117
-13
lines changed

4 files changed

+117
-13
lines changed

site/src/components/TemplateLayout/TemplatePageHeader.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const Language = {
2828
createButton: "Create workspace",
2929
deleteButton: "Delete",
3030
editFilesButton: "Edit files",
31+
duplicateButton: "Duplicate",
3132
}
3233

3334
const TemplateMenu: FC<{
@@ -67,6 +68,13 @@ const TemplateMenu: FC<{
6768
>
6869
{Language.settingsButton}
6970
</MenuItem>
71+
<MenuItem
72+
onClick={handleClose}
73+
component={RouterLink}
74+
to={`/templates/new?copyTemplate=${templateName}`}
75+
>
76+
{Language.duplicateButton}
77+
</MenuItem>
7078
{canEditFiles && (
7179
<MenuItem
7280
component={RouterLink}

site/src/pages/CreateTemplatePage/CreateTemplateForm.tsx

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import TextField from "@material-ui/core/TextField"
44
import {
55
ParameterSchema,
66
ProvisionerJobLog,
7+
Template,
78
TemplateExample,
89
TemplateVersionVariable,
910
} from "api/typesGenerated"
@@ -108,7 +109,10 @@ const defaultInitialValues: CreateTemplateData = {
108109

109110
const getInitialValues = (
110111
canSetMaxTTL: boolean,
111-
starterTemplate?: TemplateExample,
112+
{
113+
fromExample,
114+
fromCopy,
115+
}: { fromExample?: TemplateExample; fromCopy?: Template },
112116
) => {
113117
let initialValues = defaultInitialValues
114118
if (!canSetMaxTTL) {
@@ -117,17 +121,27 @@ const getInitialValues = (
117121
max_ttl_hours: 0,
118122
}
119123
}
120-
if (!starterTemplate) {
121-
return initialValues
124+
125+
if (fromExample) {
126+
return {
127+
...initialValues,
128+
name: fromExample.id,
129+
display_name: fromExample.name,
130+
icon: fromExample.icon,
131+
description: fromExample.description,
132+
}
122133
}
123134

124-
return {
125-
...initialValues,
126-
name: starterTemplate.id,
127-
display_name: starterTemplate.name,
128-
icon: starterTemplate.icon,
129-
description: starterTemplate.description,
135+
if (fromCopy) {
136+
return {
137+
...initialValues,
138+
...fromCopy,
139+
name: `${fromCopy.name}-copy`,
140+
display_name: `${fromCopy.display_name} Copy`,
141+
}
130142
}
143+
144+
return initialValues
131145
}
132146

133147
export interface CreateTemplateFormProps {
@@ -142,12 +156,14 @@ export interface CreateTemplateFormProps {
142156
jobError?: string
143157
logs?: ProvisionerJobLog[]
144158
canSetMaxTTL: boolean
159+
copiedTemplate?: Template
145160
}
146161

147162
export const CreateTemplateForm: FC<CreateTemplateFormProps> = ({
148163
onCancel,
149164
onSubmit,
150165
starterTemplate,
166+
copiedTemplate,
151167
parameters,
152168
variables,
153169
isSubmitting,
@@ -157,9 +173,13 @@ export const CreateTemplateForm: FC<CreateTemplateFormProps> = ({
157173
logs,
158174
canSetMaxTTL,
159175
}) => {
176+
console.log(copiedTemplate)
160177
const styles = useStyles()
161178
const form = useFormik<CreateTemplateData>({
162-
initialValues: getInitialValues(canSetMaxTTL, starterTemplate),
179+
initialValues: getInitialValues(canSetMaxTTL, {
180+
fromExample: starterTemplate,
181+
fromCopy: copiedTemplate,
182+
}),
163183
validationSchema,
164184
onSubmit,
165185
})
@@ -177,6 +197,8 @@ export const CreateTemplateForm: FC<CreateTemplateFormProps> = ({
177197
<FormFields>
178198
{starterTemplate ? (
179199
<SelectedTemplate template={starterTemplate} />
200+
) : copiedTemplate ? (
201+
<SelectedTemplate template={copiedTemplate} />
180202
) : (
181203
<TemplateUpload
182204
{...upload}
@@ -329,7 +351,7 @@ export const CreateTemplateForm: FC<CreateTemplateFormProps> = ({
329351
</FormSection>
330352

331353
{/* Parameters */}
332-
{parameters && (
354+
{parameters && parameters.length > 0 && (
333355
<FormSection
334356
title={t("form.parameters.title")}
335357
description={t("form.parameters.description")}
@@ -353,21 +375,22 @@ export const CreateTemplateForm: FC<CreateTemplateFormProps> = ({
353375
)}
354376

355377
{/* Variables */}
356-
{variables && (
378+
{variables && variables.length > 0 && (
357379
<FormSection
358380
title="Variables"
359381
description="Input variables allow you to customize templates without altering their source code."
360382
>
361383
<FormFields>
362384
{variables.map((variable, index) => (
363385
<VariableInput
386+
defaultValue={variable.value}
364387
variable={variable}
365388
disabled={isSubmitting}
366389
key={variable.name}
367390
onChange={async (value) => {
368391
await form.setFieldValue("user_variable_values." + index, {
369392
name: variable.name,
370-
value: value,
393+
value,
371394
})
372395
}}
373396
/>

site/src/pages/CreateTemplatePage/CreateTemplatePage.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const CreateTemplatePage: FC = () => {
2424
context: {
2525
organizationId,
2626
exampleId: searchParams.get("exampleId"),
27+
templateNameToCopy: searchParams.get("copyTemplate"),
2728
},
2829
actions: {
2930
onCreate: (_, { data }) => {
@@ -67,6 +68,7 @@ const CreateTemplatePage: FC = () => {
6768

6869
{shouldDisplayForm && (
6970
<CreateTemplateForm
71+
copiedTemplate={state.context.copiedTemplate}
7072
canSetMaxTTL={canSetMaxTTL}
7173
error={error}
7274
starterTemplate={starterTemplate}

site/src/xServices/createTemplate/createTemplateXService.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
uploadTemplateFile,
88
getTemplateVersionLogs,
99
getTemplateVersionVariables,
10+
getTemplateByName,
1011
} from "api/api"
1112
import {
1213
CreateTemplateVersionRequest,
@@ -61,6 +62,9 @@ interface CreateTemplateContext {
6162
// uploadedFile is the response from the server to use in the API
6263
file?: File
6364
uploadResponse?: UploadResponse
65+
// When wanting to copy a Template
66+
templateNameToCopy: string | null // It can be null because it is passed from query string
67+
copiedTemplate?: Template
6468
}
6569

6670
export const createTemplateMachine =
@@ -106,6 +110,14 @@ export const createTemplateMachine =
106110
loadVersionLogs: {
107111
data: ProvisionerJobLog[]
108112
}
113+
copyTemplateData: {
114+
data: {
115+
template: Template
116+
version: TemplateVersion
117+
parameters: ParameterSchema[]
118+
variables: TemplateVersionVariable[]
119+
}
120+
}
109121
},
110122
},
111123
tsTypes: {} as import("./createTemplateXService.typegen").Typegen0,
@@ -114,6 +126,10 @@ export const createTemplateMachine =
114126
starting: {
115127
always: [
116128
{ target: "loadingStarterTemplate", cond: "isExampleProvided" },
129+
{
130+
target: "copyingTemplateData",
131+
cond: "isTemplateIdToCopyProvided",
132+
},
117133
{ target: "idle" },
118134
],
119135
tags: ["loading"],
@@ -132,6 +148,27 @@ export const createTemplateMachine =
132148
},
133149
tags: ["loading"],
134150
},
151+
copyingTemplateData: {
152+
invoke: {
153+
src: "copyTemplateData",
154+
onDone: [
155+
{
156+
target: "creating.promptParametersAndVariables",
157+
actions: ["assignCopiedTemplateData"],
158+
cond: "hasParametersOrVariables",
159+
},
160+
{
161+
target: "idle",
162+
actions: ["assignCopiedTemplateData"],
163+
},
164+
],
165+
onError: {
166+
target: "idle",
167+
actions: ["assignError"],
168+
},
169+
},
170+
tags: ["loading"],
171+
},
135172
idle: {
136173
on: {
137174
CREATE: {
@@ -292,6 +329,29 @@ export const createTemplateMachine =
292329
}
293330
return starterTemplate
294331
},
332+
copyTemplateData: async ({ organizationId, templateNameToCopy }) => {
333+
if (!organizationId) {
334+
throw new Error("No organization ID provided")
335+
}
336+
if (!templateNameToCopy) {
337+
throw new Error("No template name to copy provided")
338+
}
339+
const template = await getTemplateByName(
340+
organizationId,
341+
templateNameToCopy,
342+
)
343+
const [version, parameters, variables] = await Promise.all([
344+
getTemplateVersion(template.active_version_id),
345+
getTemplateVersionSchema(template.active_version_id),
346+
getTemplateVersionVariables(template.active_version_id),
347+
])
348+
return {
349+
template,
350+
version,
351+
parameters,
352+
variables,
353+
}
354+
},
295355
createFirstVersion: async ({
296356
organizationId,
297357
exampleId,
@@ -456,9 +516,17 @@ export const createTemplateMachine =
456516
uploadResponse: (_) => undefined,
457517
}),
458518
assignJobLogs: assign({ jobLogs: (_, { data }) => data }),
519+
assignCopiedTemplateData: assign({
520+
copiedTemplate: (_, { data }) => data.template,
521+
version: (_, { data }) => data.version,
522+
parameters: (_, { data }) => data.parameters,
523+
variables: (_, { data }) => data.variables,
524+
}),
459525
},
460526
guards: {
461527
isExampleProvided: ({ exampleId }) => Boolean(exampleId),
528+
isTemplateIdToCopyProvided: ({ templateNameToCopy }) =>
529+
Boolean(templateNameToCopy),
462530
isNotUsingExample: ({ exampleId }) => !exampleId,
463531
hasFile: ({ file }) => Boolean(file),
464532
hasFailed: (_, { data }) =>
@@ -469,6 +537,9 @@ export const createTemplateMachine =
469537
),
470538
hasNoParametersOrVariables: (_, { data }) =>
471539
data.parameters === undefined && data.variables === undefined,
540+
hasParametersOrVariables: (_, { data }) => {
541+
return data.parameters.length > 0 || data.variables.length > 0
542+
},
472543
},
473544
},
474545
)

0 commit comments

Comments
 (0)