Skip to content

feat(site): Duplicate template #6853

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 11 commits into from
Mar 30, 2023
Next Next commit
Add base structure for copy template
  • Loading branch information
BrunoQuaresma committed Mar 28, 2023
commit 68ff6f8ef32262d23052f8b0864d9ec8e67366e4
8 changes: 8 additions & 0 deletions site/src/components/TemplateLayout/TemplatePageHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const Language = {
createButton: "Create workspace",
deleteButton: "Delete",
editFilesButton: "Edit files",
duplicateButton: "Duplicate",
}

const TemplateMenu: FC<{
Expand Down Expand Up @@ -67,6 +68,13 @@ const TemplateMenu: FC<{
>
{Language.settingsButton}
</MenuItem>
<MenuItem
onClick={handleClose}
component={RouterLink}
to={`/templates/new?copyTemplate=${templateName}`}
>
{Language.duplicateButton}
</MenuItem>
{canEditFiles && (
<MenuItem
component={RouterLink}
Expand Down
49 changes: 36 additions & 13 deletions site/src/pages/CreateTemplatePage/CreateTemplateForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import TextField from "@material-ui/core/TextField"
import {
ParameterSchema,
ProvisionerJobLog,
Template,
TemplateExample,
TemplateVersionVariable,
} from "api/typesGenerated"
Expand Down Expand Up @@ -108,7 +109,10 @@ const defaultInitialValues: CreateTemplateData = {

const getInitialValues = (
canSetMaxTTL: boolean,
starterTemplate?: TemplateExample,
{
fromExample,
fromCopy,
}: { fromExample?: TemplateExample; fromCopy?: Template },
) => {
let initialValues = defaultInitialValues
if (!canSetMaxTTL) {
Expand All @@ -117,17 +121,27 @@ const getInitialValues = (
max_ttl_hours: 0,
}
}
if (!starterTemplate) {
return initialValues

if (fromExample) {
return {
...initialValues,
name: fromExample.id,
display_name: fromExample.name,
icon: fromExample.icon,
description: fromExample.description,
}
}

return {
...initialValues,
name: starterTemplate.id,
display_name: starterTemplate.name,
icon: starterTemplate.icon,
description: starterTemplate.description,
if (fromCopy) {
return {
...initialValues,
...fromCopy,
name: `${fromCopy.name}-copy`,
display_name: `${fromCopy.display_name} Copy`,
}
}

return initialValues
}

export interface CreateTemplateFormProps {
Expand All @@ -142,12 +156,14 @@ export interface CreateTemplateFormProps {
jobError?: string
logs?: ProvisionerJobLog[]
canSetMaxTTL: boolean
copiedTemplate?: Template
}

export const CreateTemplateForm: FC<CreateTemplateFormProps> = ({
onCancel,
onSubmit,
starterTemplate,
copiedTemplate,
parameters,
variables,
isSubmitting,
Expand All @@ -157,9 +173,13 @@ export const CreateTemplateForm: FC<CreateTemplateFormProps> = ({
logs,
canSetMaxTTL,
}) => {
console.log(copiedTemplate)
const styles = useStyles()
const form = useFormik<CreateTemplateData>({
initialValues: getInitialValues(canSetMaxTTL, starterTemplate),
initialValues: getInitialValues(canSetMaxTTL, {
fromExample: starterTemplate,
fromCopy: copiedTemplate,
}),
validationSchema,
onSubmit,
})
Expand All @@ -177,6 +197,8 @@ export const CreateTemplateForm: FC<CreateTemplateFormProps> = ({
<FormFields>
{starterTemplate ? (
<SelectedTemplate template={starterTemplate} />
) : copiedTemplate ? (
<SelectedTemplate template={copiedTemplate} />
) : (
<TemplateUpload
{...upload}
Expand Down Expand Up @@ -329,7 +351,7 @@ export const CreateTemplateForm: FC<CreateTemplateFormProps> = ({
</FormSection>

{/* Parameters */}
{parameters && (
{parameters && parameters.length > 0 && (
<FormSection
title={t("form.parameters.title")}
description={t("form.parameters.description")}
Expand All @@ -353,21 +375,22 @@ export const CreateTemplateForm: FC<CreateTemplateFormProps> = ({
)}

{/* Variables */}
{variables && (
{variables && variables.length > 0 && (
<FormSection
title="Variables"
description="Input variables allow you to customize templates without altering their source code."
>
<FormFields>
{variables.map((variable, index) => (
<VariableInput
defaultValue={variable.value}
variable={variable}
disabled={isSubmitting}
key={variable.name}
onChange={async (value) => {
await form.setFieldValue("user_variable_values." + index, {
name: variable.name,
value: value,
value,
})
}}
/>
Expand Down
2 changes: 2 additions & 0 deletions site/src/pages/CreateTemplatePage/CreateTemplatePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const CreateTemplatePage: FC = () => {
context: {
organizationId,
exampleId: searchParams.get("exampleId"),
templateNameToCopy: searchParams.get("copyTemplate"),
},
actions: {
onCreate: (_, { data }) => {
Expand Down Expand Up @@ -67,6 +68,7 @@ const CreateTemplatePage: FC = () => {

{shouldDisplayForm && (
<CreateTemplateForm
copiedTemplate={state.context.copiedTemplate}
canSetMaxTTL={canSetMaxTTL}
error={error}
starterTemplate={starterTemplate}
Expand Down
71 changes: 71 additions & 0 deletions site/src/xServices/createTemplate/createTemplateXService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
uploadTemplateFile,
getTemplateVersionLogs,
getTemplateVersionVariables,
getTemplateByName,
} from "api/api"
import {
CreateTemplateVersionRequest,
Expand Down Expand Up @@ -61,6 +62,9 @@ interface CreateTemplateContext {
// uploadedFile is the response from the server to use in the API
file?: File
uploadResponse?: UploadResponse
// When wanting to copy a Template
templateNameToCopy: string | null // It can be null because it is passed from query string
copiedTemplate?: Template
}

export const createTemplateMachine =
Expand Down Expand Up @@ -106,6 +110,14 @@ export const createTemplateMachine =
loadVersionLogs: {
data: ProvisionerJobLog[]
}
copyTemplateData: {
data: {
template: Template
version: TemplateVersion
parameters: ParameterSchema[]
variables: TemplateVersionVariable[]
}
}
},
},
tsTypes: {} as import("./createTemplateXService.typegen").Typegen0,
Expand All @@ -114,6 +126,10 @@ export const createTemplateMachine =
starting: {
always: [
{ target: "loadingStarterTemplate", cond: "isExampleProvided" },
{
target: "copyingTemplateData",
cond: "isTemplateIdToCopyProvided",
},
{ target: "idle" },
],
tags: ["loading"],
Expand All @@ -132,6 +148,27 @@ export const createTemplateMachine =
},
tags: ["loading"],
},
copyingTemplateData: {
invoke: {
src: "copyTemplateData",
onDone: [
{
target: "creating.promptParametersAndVariables",
actions: ["assignCopiedTemplateData"],
cond: "hasParametersOrVariables",
},
{
target: "idle",
actions: ["assignCopiedTemplateData"],
},
],
onError: {
target: "idle",
actions: ["assignError"],
},
},
tags: ["loading"],
},
idle: {
on: {
CREATE: {
Expand Down Expand Up @@ -292,6 +329,29 @@ export const createTemplateMachine =
}
return starterTemplate
},
copyTemplateData: async ({ organizationId, templateNameToCopy }) => {
if (!organizationId) {
throw new Error("No organization ID provided")
}
if (!templateNameToCopy) {
throw new Error("No template name to copy provided")
}
const template = await getTemplateByName(
organizationId,
templateNameToCopy,
)
const [version, parameters, variables] = await Promise.all([
getTemplateVersion(template.active_version_id),
getTemplateVersionSchema(template.active_version_id),
getTemplateVersionVariables(template.active_version_id),
])
return {
template,
version,
parameters,
variables,
}
},
createFirstVersion: async ({
organizationId,
exampleId,
Expand Down Expand Up @@ -456,9 +516,17 @@ export const createTemplateMachine =
uploadResponse: (_) => undefined,
}),
assignJobLogs: assign({ jobLogs: (_, { data }) => data }),
assignCopiedTemplateData: assign({
copiedTemplate: (_, { data }) => data.template,
version: (_, { data }) => data.version,
parameters: (_, { data }) => data.parameters,
variables: (_, { data }) => data.variables,
}),
},
guards: {
isExampleProvided: ({ exampleId }) => Boolean(exampleId),
isTemplateIdToCopyProvided: ({ templateNameToCopy }) =>
Boolean(templateNameToCopy),
isNotUsingExample: ({ exampleId }) => !exampleId,
hasFile: ({ file }) => Boolean(file),
hasFailed: (_, { data }) =>
Expand All @@ -469,6 +537,9 @@ export const createTemplateMachine =
),
hasNoParametersOrVariables: (_, { data }) =>
data.parameters === undefined && data.variables === undefined,
hasParametersOrVariables: (_, { data }) => {
return data.parameters.length > 0 || data.variables.length > 0
},
},
},
)
Expand Down