mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: Add form to modify managed Template variables (#6257)
This commit is contained in:
Generated
+70
-1
@@ -1473,7 +1473,7 @@ const docTemplate = `{
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.CreateTemplateVersionDryRunRequest"
|
||||
"$ref": "#/definitions/codersdk.CreateTemplateVersionRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -5846,6 +5846,66 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.CreateTemplateVersionRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"provisioner",
|
||||
"storage_method"
|
||||
],
|
||||
"properties": {
|
||||
"example_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"file_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"parameter_values": {
|
||||
"description": "ParameterValues allows for additional parameters to be provided\nduring the dry-run provision stage.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.CreateParameterRequest"
|
||||
}
|
||||
},
|
||||
"provisioner": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"terraform",
|
||||
"echo"
|
||||
]
|
||||
},
|
||||
"storage_method": {
|
||||
"enum": [
|
||||
"file"
|
||||
],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/codersdk.ProvisionerStorageMethod"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tags": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"template_id": {
|
||||
"description": "TemplateID optionally associates a version with a template.",
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"user_variable_values": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.VariableValue"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.CreateTestAuditLogRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -7348,6 +7408,15 @@ const docTemplate = `{
|
||||
"ProvisionerJobFailed"
|
||||
]
|
||||
},
|
||||
"codersdk.ProvisionerStorageMethod": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"file"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"ProvisionerStorageMethodFile"
|
||||
]
|
||||
},
|
||||
"codersdk.PutExtendWorkspaceRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
||||
Generated
+58
-1
@@ -1287,7 +1287,7 @@
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.CreateTemplateVersionDryRunRequest"
|
||||
"$ref": "#/definitions/codersdk.CreateTemplateVersionRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -5190,6 +5190,58 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.CreateTemplateVersionRequest": {
|
||||
"type": "object",
|
||||
"required": ["provisioner", "storage_method"],
|
||||
"properties": {
|
||||
"example_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"file_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"parameter_values": {
|
||||
"description": "ParameterValues allows for additional parameters to be provided\nduring the dry-run provision stage.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.CreateParameterRequest"
|
||||
}
|
||||
},
|
||||
"provisioner": {
|
||||
"type": "string",
|
||||
"enum": ["terraform", "echo"]
|
||||
},
|
||||
"storage_method": {
|
||||
"enum": ["file"],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/codersdk.ProvisionerStorageMethod"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tags": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"template_id": {
|
||||
"description": "TemplateID optionally associates a version with a template.",
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"user_variable_values": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.VariableValue"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.CreateTestAuditLogRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -6587,6 +6639,11 @@
|
||||
"ProvisionerJobFailed"
|
||||
]
|
||||
},
|
||||
"codersdk.ProvisionerStorageMethod": {
|
||||
"type": "string",
|
||||
"enum": ["file"],
|
||||
"x-enum-varnames": ["ProvisionerStorageMethodFile"]
|
||||
},
|
||||
"codersdk.PutExtendWorkspaceRequest": {
|
||||
"type": "object",
|
||||
"required": ["deadline"],
|
||||
|
||||
@@ -1179,7 +1179,7 @@ func (api *API) patchActiveTemplateVersion(rw http.ResponseWriter, r *http.Reque
|
||||
// @Produce json
|
||||
// @Tags Templates
|
||||
// @Param organization path string true "Organization ID" format(uuid)
|
||||
// @Param request body codersdk.CreateTemplateVersionDryRunRequest true "Create template version request"
|
||||
// @Param request body codersdk.CreateTemplateVersionRequest true "Create template version request"
|
||||
// @Success 201 {object} codersdk.TemplateVersion
|
||||
// @Router /organizations/{organization}/templateversions [post]
|
||||
func (api *API) postTemplateVersionsByOrganization(rw http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -1027,6 +1027,61 @@ CreateParameterRequest is a structure used to create a new parameter value for a
|
||||
| `user_variable_values` | array of [codersdk.VariableValue](#codersdkvariablevalue) | false | | |
|
||||
| `workspace_name` | string | false | | |
|
||||
|
||||
## codersdk.CreateTemplateVersionRequest
|
||||
|
||||
```json
|
||||
{
|
||||
"example_id": "string",
|
||||
"file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767",
|
||||
"name": "string",
|
||||
"parameter_values": [
|
||||
{
|
||||
"copy_from_parameter": "000e07d6-021d-446c-be14-48a9c20bca0b",
|
||||
"destination_scheme": "none",
|
||||
"name": "string",
|
||||
"source_scheme": "none",
|
||||
"source_value": "string"
|
||||
}
|
||||
],
|
||||
"provisioner": "terraform",
|
||||
"storage_method": "file",
|
||||
"tags": {
|
||||
"property1": "string",
|
||||
"property2": "string"
|
||||
},
|
||||
"template_id": "c6d67e98-83ea-49f0-8812-e4abae2b68bc",
|
||||
"user_variable_values": [
|
||||
{
|
||||
"name": "string",
|
||||
"value": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| ---------------------- | --------------------------------------------------------------------------- | -------- | ------------ | ---------------------------------------------------------------------------------------------------- |
|
||||
| `example_id` | string | false | | |
|
||||
| `file_id` | string | false | | |
|
||||
| `name` | string | false | | |
|
||||
| `parameter_values` | array of [codersdk.CreateParameterRequest](#codersdkcreateparameterrequest) | false | | Parameter values allows for additional parameters to be provided during the dry-run provision stage. |
|
||||
| `provisioner` | string | true | | |
|
||||
| `storage_method` | [codersdk.ProvisionerStorageMethod](#codersdkprovisionerstoragemethod) | true | | |
|
||||
| `tags` | object | false | | |
|
||||
| » `[any property]` | string | false | | |
|
||||
| `template_id` | string | false | | Template ID optionally associates a version with a template. |
|
||||
| `user_variable_values` | array of [codersdk.VariableValue](#codersdkvariablevalue) | false | | |
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Property | Value |
|
||||
| ---------------- | ----------- |
|
||||
| `provisioner` | `terraform` |
|
||||
| `provisioner` | `echo` |
|
||||
| `storage_method` | `file` |
|
||||
|
||||
## codersdk.CreateTestAuditLogRequest
|
||||
|
||||
```json
|
||||
@@ -4076,6 +4131,20 @@ Parameter represents a set value for the scope.
|
||||
| `canceled` |
|
||||
| `failed` |
|
||||
|
||||
## codersdk.ProvisionerStorageMethod
|
||||
|
||||
```json
|
||||
"file"
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Value |
|
||||
| ------ |
|
||||
| `file` |
|
||||
|
||||
## codersdk.PutExtendWorkspaceRequest
|
||||
|
||||
```json
|
||||
|
||||
+15
-12
@@ -545,6 +545,9 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/templa
|
||||
|
||||
```json
|
||||
{
|
||||
"example_id": "string",
|
||||
"file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767",
|
||||
"name": "string",
|
||||
"parameter_values": [
|
||||
{
|
||||
"copy_from_parameter": "000e07d6-021d-446c-be14-48a9c20bca0b",
|
||||
@@ -554,28 +557,28 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/templa
|
||||
"source_value": "string"
|
||||
}
|
||||
],
|
||||
"rich_parameter_values": [
|
||||
{
|
||||
"name": "string",
|
||||
"value": "string"
|
||||
}
|
||||
],
|
||||
"provisioner": "terraform",
|
||||
"storage_method": "file",
|
||||
"tags": {
|
||||
"property1": "string",
|
||||
"property2": "string"
|
||||
},
|
||||
"template_id": "c6d67e98-83ea-49f0-8812-e4abae2b68bc",
|
||||
"user_variable_values": [
|
||||
{
|
||||
"name": "string",
|
||||
"value": "string"
|
||||
}
|
||||
],
|
||||
"workspace_name": "string"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
| Name | In | Type | Required | Description |
|
||||
| -------------- | ---- | ---------------------------------------------------------------------------------------------------- | -------- | ------------------------------- |
|
||||
| `organization` | path | string(uuid) | true | Organization ID |
|
||||
| `body` | body | [codersdk.CreateTemplateVersionDryRunRequest](schemas.md#codersdkcreatetemplateversiondryrunrequest) | true | Create template version request |
|
||||
| Name | In | Type | Required | Description |
|
||||
| -------------- | ---- | ---------------------------------------------------------------------------------------- | -------- | ------------------------------- |
|
||||
| `organization` | path | string(uuid) | true | Organization ID |
|
||||
| `body` | body | [codersdk.CreateTemplateVersionRequest](schemas.md#codersdkcreatetemplateversionrequest) | true | Create template version request |
|
||||
|
||||
### Example responses
|
||||
|
||||
|
||||
@@ -123,6 +123,9 @@ const StarterTemplatePage = lazy(
|
||||
const CreateTemplatePage = lazy(
|
||||
() => import("./pages/CreateTemplatePage/CreateTemplatePage"),
|
||||
)
|
||||
const TemplateVariablesPage = lazy(
|
||||
() => import("./pages/TemplateVariablesPage/TemplateVariablesPage"),
|
||||
)
|
||||
|
||||
export const AppRouter: FC = () => {
|
||||
return (
|
||||
@@ -160,6 +163,7 @@ export const AppRouter: FC = () => {
|
||||
|
||||
<Route path="workspace" element={<CreateWorkspacePage />} />
|
||||
<Route path="settings" element={<TemplateSettingsPage />} />
|
||||
<Route path="variables" element={<TemplateVariablesPage />} />
|
||||
<Route path="versions">
|
||||
<Route path=":version">
|
||||
<Route index element={<TemplateVersionPage />} />
|
||||
|
||||
@@ -235,6 +235,15 @@ export const getTemplateVersionResources = async (
|
||||
return response.data
|
||||
}
|
||||
|
||||
export const getTemplateVersionVariables = async (
|
||||
versionId: string,
|
||||
): Promise<TypesGen.TemplateVersionVariable[]> => {
|
||||
const response = await axios.get<TypesGen.TemplateVersionVariable[]>(
|
||||
`/api/v2/templateversions/${versionId}/variables`,
|
||||
)
|
||||
return response.data
|
||||
}
|
||||
|
||||
export const getTemplateVersions = async (
|
||||
templateId: string,
|
||||
): Promise<TypesGen.TemplateVersion[]> => {
|
||||
|
||||
@@ -2,6 +2,7 @@ import Button from "@material-ui/core/Button"
|
||||
import DeleteOutlined from "@material-ui/icons/DeleteOutlined"
|
||||
import AddCircleOutline from "@material-ui/icons/AddCircleOutline"
|
||||
import SettingsOutlined from "@material-ui/icons/SettingsOutlined"
|
||||
import CodeOutlined from "@material-ui/icons/CodeOutlined"
|
||||
import { AuthorizationResponse, Template } from "api/typesGenerated"
|
||||
import { Avatar } from "components/Avatar/Avatar"
|
||||
import { Maybe } from "components/Conditionals/Maybe"
|
||||
@@ -19,6 +20,7 @@ import { Margins } from "components/Margins/Margins"
|
||||
|
||||
const Language = {
|
||||
editButton: "Edit",
|
||||
variablesButton: "Variables",
|
||||
settingsButton: "Settings",
|
||||
createButton: "Create workspace",
|
||||
deleteButton: "Delete",
|
||||
@@ -37,6 +39,19 @@ const TemplateSettingsButton: FC<{ templateName: string }> = ({
|
||||
</Button>
|
||||
)
|
||||
|
||||
const TemplateVariablesButton: FC<{ templateName: string }> = ({
|
||||
templateName,
|
||||
}) => (
|
||||
<Button
|
||||
variant="outlined"
|
||||
component={RouterLink}
|
||||
to={`/templates/${templateName}/variables`}
|
||||
startIcon={<CodeOutlined />}
|
||||
>
|
||||
{Language.variablesButton}
|
||||
</Button>
|
||||
)
|
||||
|
||||
const CreateWorkspaceButton: FC<{
|
||||
templateName: string
|
||||
className?: string
|
||||
@@ -80,6 +95,7 @@ export const TemplatePageHeader: FC<TemplatePageHeaderProps> = ({
|
||||
onClick={deleteTemplate.openDeleteConfirmation}
|
||||
/>
|
||||
<TemplateSettingsButton templateName={template.name} />
|
||||
<TemplateVariablesButton templateName={template.name} />
|
||||
</Maybe>
|
||||
<CreateWorkspaceButton templateName={template.name} />
|
||||
</>
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel"
|
||||
import Radio from "@material-ui/core/Radio"
|
||||
import RadioGroup from "@material-ui/core/RadioGroup"
|
||||
import TextField from "@material-ui/core/TextField"
|
||||
import { TemplateVersionVariable } from "api/typesGenerated"
|
||||
import { FC, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
export const SensitiveVariableHelperText = () => {
|
||||
const { t } = useTranslation("templateVariablesPage")
|
||||
return <span>{t("sensitiveVariableHelperText")}</span>
|
||||
}
|
||||
|
||||
export interface TemplateVariableFieldProps {
|
||||
templateVersionVariable: TemplateVersionVariable
|
||||
initialValue: string
|
||||
disabled: boolean
|
||||
onChange: (value: string) => void
|
||||
}
|
||||
|
||||
export const TemplateVariableField: FC<TemplateVariableFieldProps> = ({
|
||||
templateVersionVariable,
|
||||
initialValue,
|
||||
disabled,
|
||||
onChange,
|
||||
...props
|
||||
}) => {
|
||||
const [variableValue, setVariableValue] = useState(initialValue)
|
||||
if (isBoolean(templateVersionVariable)) {
|
||||
return (
|
||||
<RadioGroup
|
||||
defaultValue={variableValue}
|
||||
onChange={(event) => {
|
||||
onChange(event.target.value)
|
||||
}}
|
||||
>
|
||||
<FormControlLabel
|
||||
disabled={disabled}
|
||||
value="true"
|
||||
control={<Radio color="primary" size="small" disableRipple />}
|
||||
label="True"
|
||||
/>
|
||||
<FormControlLabel
|
||||
disabled={disabled}
|
||||
value="false"
|
||||
control={<Radio color="primary" size="small" disableRipple />}
|
||||
label="False"
|
||||
/>
|
||||
</RadioGroup>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<TextField
|
||||
{...props}
|
||||
type={
|
||||
templateVersionVariable.type === "number"
|
||||
? "number"
|
||||
: templateVersionVariable.sensitive
|
||||
? "password"
|
||||
: "string"
|
||||
}
|
||||
disabled={disabled}
|
||||
autoFocus
|
||||
fullWidth
|
||||
label={templateVersionVariable.name}
|
||||
value={variableValue}
|
||||
placeholder={
|
||||
templateVersionVariable.sensitive
|
||||
? ""
|
||||
: templateVersionVariable.default_value
|
||||
}
|
||||
onChange={(event) => {
|
||||
setVariableValue(event.target.value)
|
||||
onChange(event.target.value)
|
||||
}}
|
||||
variant="outlined"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const isBoolean = (variable: TemplateVersionVariable) => {
|
||||
return variable.type === "bool"
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import buildPage from "./buildPage.json"
|
||||
import workspacesPage from "./workspacesPage.json"
|
||||
import usersPage from "./usersPage.json"
|
||||
import templateSettingsPage from "./templateSettingsPage.json"
|
||||
import templateVariablesPage from "./templateVariablesPage.json"
|
||||
import templateVersionPage from "./templateVersionPage.json"
|
||||
import loginPage from "./loginPage.json"
|
||||
import workspaceBuildParametersPage from "./workspaceBuildParametersPage.json"
|
||||
@@ -33,6 +34,7 @@ export const en = {
|
||||
workspacesPage,
|
||||
usersPage,
|
||||
templateSettingsPage,
|
||||
templateVariablesPage,
|
||||
templateVersionPage,
|
||||
loginPage,
|
||||
workspaceBuildParametersPage,
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"title": "Template variables",
|
||||
"sensitiveVariableHelperText": "This variable is sensitive. The previous value will be used if empty.",
|
||||
"validationRequiredVariable": "Variable is required.",
|
||||
"unusedVariablesNotice": "This template does not use managed variables."
|
||||
}
|
||||
@@ -51,7 +51,7 @@ SaveTemplateSettingsError.args = {
|
||||
}),
|
||||
},
|
||||
initialTouched: {
|
||||
name: true,
|
||||
allow_user_cancel_workspace_jobs: true,
|
||||
},
|
||||
onSubmit: action("onSubmit"),
|
||||
onCancel: action("cancel"),
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
import {
|
||||
CreateTemplateVersionRequest,
|
||||
TemplateVersion,
|
||||
TemplateVersionVariable,
|
||||
VariableValue,
|
||||
} from "api/typesGenerated"
|
||||
import { FormikContextType, FormikTouched, useFormik } from "formik"
|
||||
import { FC } from "react"
|
||||
import { getFormHelpers } from "util/formUtils"
|
||||
import * as Yup from "yup"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import {
|
||||
FormFields,
|
||||
FormSection,
|
||||
HorizontalForm,
|
||||
FormFooter,
|
||||
} from "components/HorizontalForm/HorizontalForm"
|
||||
import {
|
||||
SensitiveVariableHelperText,
|
||||
TemplateVariableField,
|
||||
} from "components/TemplateVariableField/TemplateVariableField"
|
||||
|
||||
export const getValidationSchema = (): Yup.AnyObjectSchema => Yup.object()
|
||||
|
||||
export interface TemplateVariablesForm {
|
||||
templateVersion: TemplateVersion
|
||||
templateVariables: TemplateVersionVariable[]
|
||||
onSubmit: (data: CreateTemplateVersionRequest) => void
|
||||
onCancel: () => void
|
||||
isSubmitting: boolean
|
||||
error?: unknown
|
||||
// Helpful to show field errors on Storybook
|
||||
initialTouched?: FormikTouched<CreateTemplateVersionRequest>
|
||||
}
|
||||
export const TemplateVariablesForm: FC<TemplateVariablesForm> = ({
|
||||
templateVersion,
|
||||
templateVariables,
|
||||
onSubmit,
|
||||
onCancel,
|
||||
error,
|
||||
isSubmitting,
|
||||
initialTouched,
|
||||
}) => {
|
||||
const initialUserVariableValues =
|
||||
selectInitialUserVariableValues(templateVariables)
|
||||
const form: FormikContextType<CreateTemplateVersionRequest> =
|
||||
useFormik<CreateTemplateVersionRequest>({
|
||||
initialValues: {
|
||||
template_id: templateVersion.template_id,
|
||||
provisioner: "terraform",
|
||||
storage_method: "file",
|
||||
tags: templateVersion.job.tags,
|
||||
file_id: templateVersion.job.file_id,
|
||||
user_variable_values: initialUserVariableValues,
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
user_variable_values: ValidationSchemaForTemplateVariables(
|
||||
"templateVariablesPage",
|
||||
templateVariables,
|
||||
),
|
||||
}),
|
||||
onSubmit,
|
||||
initialTouched,
|
||||
})
|
||||
const getFieldHelpers = getFormHelpers<CreateTemplateVersionRequest>(
|
||||
form,
|
||||
error,
|
||||
)
|
||||
const { t } = useTranslation("templateVariablesPage")
|
||||
|
||||
return (
|
||||
<HorizontalForm
|
||||
onSubmit={form.handleSubmit}
|
||||
aria-label={t("formAriaLabel")}
|
||||
>
|
||||
{templateVariables.map((templateVariable, index) => {
|
||||
let fieldHelpers
|
||||
if (templateVariable.sensitive) {
|
||||
fieldHelpers = getFieldHelpers(
|
||||
"user_variable_values[" + index + "].value",
|
||||
<SensitiveVariableHelperText />,
|
||||
)
|
||||
} else {
|
||||
fieldHelpers = getFieldHelpers(
|
||||
"user_variable_values[" + index + "].value",
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<FormSection
|
||||
key={templateVariable.name}
|
||||
title={templateVariable.name}
|
||||
description={templateVariable.description}
|
||||
>
|
||||
<FormFields>
|
||||
<TemplateVariableField
|
||||
{...fieldHelpers}
|
||||
templateVersionVariable={templateVariable}
|
||||
initialValue={initialUserVariableValues[index].value}
|
||||
disabled={isSubmitting}
|
||||
onChange={(value) => {
|
||||
form.setFieldValue("user_variable_values." + index, {
|
||||
name: templateVariable.name,
|
||||
value: value,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</FormFields>
|
||||
</FormSection>
|
||||
)
|
||||
})}
|
||||
|
||||
<FormFooter onCancel={onCancel} isLoading={isSubmitting} />
|
||||
</HorizontalForm>
|
||||
)
|
||||
}
|
||||
|
||||
export const selectInitialUserVariableValues = (
|
||||
templateVariables: TemplateVersionVariable[],
|
||||
): VariableValue[] => {
|
||||
const defaults: VariableValue[] = []
|
||||
templateVariables.forEach((templateVariable) => {
|
||||
// Boolean variables must be always either "true" or "false"
|
||||
if (templateVariable.type === "bool" && templateVariable.value === "") {
|
||||
defaults.push({
|
||||
name: templateVariable.name,
|
||||
value: templateVariable.default_value,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (templateVariable.sensitive) {
|
||||
defaults.push({
|
||||
name: templateVariable.name,
|
||||
value: "",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (templateVariable.required && templateVariable.value === "") {
|
||||
defaults.push({
|
||||
name: templateVariable.name,
|
||||
value: templateVariable.default_value,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
defaults.push({
|
||||
name: templateVariable.name,
|
||||
value: templateVariable.value,
|
||||
})
|
||||
})
|
||||
return defaults
|
||||
}
|
||||
|
||||
const ValidationSchemaForTemplateVariables = (
|
||||
ns: string,
|
||||
templateVariables: TemplateVersionVariable[],
|
||||
): Yup.AnySchema => {
|
||||
const { t } = useTranslation(ns)
|
||||
|
||||
return Yup.array()
|
||||
.of(
|
||||
Yup.object().shape({
|
||||
name: Yup.string().required(),
|
||||
value: Yup.string().test("verify with template", (val, ctx) => {
|
||||
const name = ctx.parent.name
|
||||
const templateVariable = templateVariables.find(
|
||||
(variable) => variable.name === name,
|
||||
)
|
||||
if (templateVariable && templateVariable.sensitive) {
|
||||
// It's possible that the secret is already stored in database,
|
||||
// so we can't properly verify the "required" condition.
|
||||
return true
|
||||
}
|
||||
if (templateVariable && templateVariable.required) {
|
||||
if (!val || val.length === 0) {
|
||||
return ctx.createError({
|
||||
path: ctx.path,
|
||||
message: t("validationRequiredVariable"),
|
||||
})
|
||||
}
|
||||
}
|
||||
return true
|
||||
}),
|
||||
}),
|
||||
)
|
||||
.required()
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
import { screen, waitFor } from "@testing-library/react"
|
||||
import userEvent from "@testing-library/user-event"
|
||||
import {
|
||||
MockTemplate,
|
||||
MockTemplateVersion2,
|
||||
MockTemplateVersion,
|
||||
MockTemplateVersionVariable1,
|
||||
MockTemplateVersionVariable2,
|
||||
renderWithAuth,
|
||||
MockTemplateVersionVariable5,
|
||||
} from "testHelpers/renderHelpers"
|
||||
import * as API from "api/api"
|
||||
import i18next from "i18next"
|
||||
import TemplateVariablesPage from "./TemplateVariablesPage"
|
||||
import { Language as FooterFormLanguage } from "components/FormFooter/FormFooter"
|
||||
import { Route } from "react-router-dom"
|
||||
import * as router from "react-router"
|
||||
|
||||
const navigate = jest.fn()
|
||||
|
||||
const { t } = i18next
|
||||
|
||||
const validFormValues = {
|
||||
first_variable: "Hello world",
|
||||
second_variable: "123",
|
||||
}
|
||||
|
||||
const pageTitleText = t("title", { ns: "templateVariablesPage" })
|
||||
|
||||
const validationRequiredField = t("validationRequiredVariable", {
|
||||
ns: "templateVariablesPage",
|
||||
})
|
||||
|
||||
const renderTemplateVariablesPage = () => {
|
||||
return renderWithAuth(<TemplateVariablesPage />, {
|
||||
route: `/templates/${MockTemplate.name}/variables`,
|
||||
path: `/templates/:template/variables`,
|
||||
routes: (
|
||||
<Route path={`/templates/${MockTemplate.name}`} element={<></>}></Route>
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
describe("TemplateVariablesPage", () => {
|
||||
it("renders with variables", async () => {
|
||||
jest.spyOn(API, "getTemplateByName").mockResolvedValueOnce(MockTemplate)
|
||||
jest
|
||||
.spyOn(API, "getTemplateVersion")
|
||||
.mockResolvedValueOnce(MockTemplateVersion)
|
||||
jest
|
||||
.spyOn(API, "getTemplateVersionVariables")
|
||||
.mockResolvedValueOnce([
|
||||
MockTemplateVersionVariable1,
|
||||
MockTemplateVersionVariable2,
|
||||
])
|
||||
|
||||
renderTemplateVariablesPage()
|
||||
|
||||
const element = await screen.findByText(pageTitleText)
|
||||
expect(element).toBeDefined()
|
||||
|
||||
const firstVariable = await screen.findByLabelText(
|
||||
MockTemplateVersionVariable1.name,
|
||||
)
|
||||
expect(firstVariable).toBeDefined()
|
||||
|
||||
const secondVariable = await screen.findByLabelText(
|
||||
MockTemplateVersionVariable2.name,
|
||||
)
|
||||
expect(secondVariable).toBeDefined()
|
||||
})
|
||||
|
||||
it("user submits the form successfully", async () => {
|
||||
jest.spyOn(API, "getTemplateByName").mockResolvedValueOnce(MockTemplate)
|
||||
jest
|
||||
.spyOn(API, "getTemplateVersion")
|
||||
.mockResolvedValueOnce(MockTemplateVersion)
|
||||
jest
|
||||
.spyOn(API, "getTemplateVersionVariables")
|
||||
.mockResolvedValueOnce([
|
||||
MockTemplateVersionVariable1,
|
||||
MockTemplateVersionVariable2,
|
||||
])
|
||||
jest
|
||||
.spyOn(API, "createTemplateVersion")
|
||||
.mockResolvedValueOnce(MockTemplateVersion2)
|
||||
jest.spyOn(API, "updateActiveTemplateVersion").mockResolvedValueOnce({
|
||||
message: "done",
|
||||
})
|
||||
jest.spyOn(router, "useNavigate").mockImplementation(() => navigate)
|
||||
|
||||
renderTemplateVariablesPage()
|
||||
|
||||
const element = await screen.findByText(pageTitleText)
|
||||
expect(element).toBeDefined()
|
||||
|
||||
const firstVariable = await screen.findByLabelText(
|
||||
MockTemplateVersionVariable1.name,
|
||||
)
|
||||
expect(firstVariable).toBeDefined()
|
||||
|
||||
const secondVariable = await screen.findByLabelText(
|
||||
MockTemplateVersionVariable2.name,
|
||||
)
|
||||
expect(secondVariable).toBeDefined()
|
||||
|
||||
// Fill the form
|
||||
const firstVariableField = await screen.findByLabelText(
|
||||
MockTemplateVersionVariable1.name,
|
||||
)
|
||||
await userEvent.clear(firstVariableField)
|
||||
await userEvent.type(firstVariableField, validFormValues.first_variable)
|
||||
|
||||
const secondVariableField = await screen.findByLabelText(
|
||||
MockTemplateVersionVariable2.name,
|
||||
)
|
||||
await userEvent.clear(secondVariableField)
|
||||
await userEvent.type(secondVariableField, validFormValues.second_variable)
|
||||
|
||||
// Submit the form
|
||||
const submitButton = await screen.findByText(
|
||||
FooterFormLanguage.defaultSubmitLabel,
|
||||
)
|
||||
await userEvent.click(submitButton)
|
||||
|
||||
// Wait for redirect
|
||||
await waitFor(() =>
|
||||
expect(navigate).toHaveBeenCalledWith(`/templates/${MockTemplate.name}`),
|
||||
)
|
||||
})
|
||||
|
||||
it("user forgets to fill the required field", async () => {
|
||||
jest.spyOn(API, "getTemplateByName").mockResolvedValueOnce(MockTemplate)
|
||||
jest
|
||||
.spyOn(API, "getTemplateVersion")
|
||||
.mockResolvedValueOnce(MockTemplateVersion)
|
||||
jest
|
||||
.spyOn(API, "getTemplateVersionVariables")
|
||||
.mockResolvedValueOnce([
|
||||
MockTemplateVersionVariable1,
|
||||
MockTemplateVersionVariable5,
|
||||
])
|
||||
jest
|
||||
.spyOn(API, "createTemplateVersion")
|
||||
.mockResolvedValueOnce(MockTemplateVersion2)
|
||||
jest.spyOn(API, "updateActiveTemplateVersion").mockResolvedValueOnce({
|
||||
message: "done",
|
||||
})
|
||||
jest.spyOn(router, "useNavigate").mockImplementation(() => navigate)
|
||||
|
||||
renderTemplateVariablesPage()
|
||||
|
||||
const element = await screen.findByText(pageTitleText)
|
||||
expect(element).toBeDefined()
|
||||
|
||||
const firstVariable = await screen.findByLabelText(
|
||||
MockTemplateVersionVariable1.name,
|
||||
)
|
||||
expect(firstVariable).toBeDefined()
|
||||
|
||||
const fifthVariable = await screen.findByLabelText(
|
||||
MockTemplateVersionVariable5.name,
|
||||
)
|
||||
expect(fifthVariable).toBeDefined()
|
||||
|
||||
// Submit the form
|
||||
const submitButton = await screen.findByText(
|
||||
FooterFormLanguage.defaultSubmitLabel,
|
||||
)
|
||||
await userEvent.click(submitButton)
|
||||
|
||||
// Check validation error
|
||||
const validationError = await screen.findByText(validationRequiredField)
|
||||
expect(validationError).toBeDefined()
|
||||
})
|
||||
|
||||
it("no managed variables", async () => {
|
||||
jest.spyOn(API, "getTemplateByName").mockResolvedValueOnce(MockTemplate)
|
||||
jest
|
||||
.spyOn(API, "getTemplateVersion")
|
||||
.mockResolvedValueOnce(MockTemplateVersion)
|
||||
jest.spyOn(API, "getTemplateVersionVariables").mockResolvedValueOnce([])
|
||||
|
||||
renderTemplateVariablesPage()
|
||||
|
||||
const element = await screen.findByText(pageTitleText)
|
||||
expect(element).toBeDefined()
|
||||
|
||||
const goBackButton = await screen.findByText("Go back")
|
||||
expect(goBackButton).toBeDefined()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,103 @@
|
||||
import { useMachine } from "@xstate/react"
|
||||
import {
|
||||
CreateTemplateVersionRequest,
|
||||
TemplateVersionVariable,
|
||||
VariableValue,
|
||||
} from "api/typesGenerated"
|
||||
import { useOrganizationId } from "hooks/useOrganizationId"
|
||||
import { FC } from "react"
|
||||
import { Helmet } from "react-helmet-async"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useNavigate, useParams } from "react-router-dom"
|
||||
import { templateVariablesMachine } from "xServices/template/templateVariablesXService"
|
||||
import { pageTitle } from "../../util/page"
|
||||
import { TemplateVariablesPageView } from "./TemplateVariablesPageView"
|
||||
|
||||
export const TemplateVariablesPage: FC = () => {
|
||||
const { template: templateName } = useParams() as {
|
||||
organization: string
|
||||
template: string
|
||||
}
|
||||
const organizationId = useOrganizationId()
|
||||
const navigate = useNavigate()
|
||||
const [state, send] = useMachine(templateVariablesMachine, {
|
||||
context: {
|
||||
organizationId,
|
||||
templateName,
|
||||
},
|
||||
actions: {
|
||||
onUpdateTemplate: () => {
|
||||
navigate(`/templates/${templateName}`)
|
||||
},
|
||||
},
|
||||
})
|
||||
const {
|
||||
activeTemplateVersion,
|
||||
templateVariables,
|
||||
getTemplateDataError,
|
||||
updateTemplateError,
|
||||
} = state.context
|
||||
|
||||
const { t } = useTranslation("templateVariablesPage")
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{pageTitle(t("title"))}</title>
|
||||
</Helmet>
|
||||
|
||||
<TemplateVariablesPageView
|
||||
isSubmitting={state.hasTag("submitting")}
|
||||
templateVersion={activeTemplateVersion}
|
||||
templateVariables={templateVariables}
|
||||
errors={{
|
||||
getTemplateDataError,
|
||||
updateTemplateError,
|
||||
}}
|
||||
onCancel={() => {
|
||||
navigate(`/templates/${templateName}`)
|
||||
}}
|
||||
onSubmit={(formData) => {
|
||||
const request = filterEmptySensitiveVariables(
|
||||
formData,
|
||||
templateVariables,
|
||||
)
|
||||
send({ type: "UPDATE_TEMPLATE_EVENT", request: request })
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const filterEmptySensitiveVariables = (
|
||||
request: CreateTemplateVersionRequest,
|
||||
templateVariables?: TemplateVersionVariable[],
|
||||
): CreateTemplateVersionRequest => {
|
||||
const filtered: VariableValue[] = []
|
||||
|
||||
if (!templateVariables) {
|
||||
return request
|
||||
}
|
||||
|
||||
if (request.user_variable_values) {
|
||||
request.user_variable_values.forEach((variableValue) => {
|
||||
const templateVariable = templateVariables.find(
|
||||
(t) => t.name === variableValue.name,
|
||||
)
|
||||
if (
|
||||
templateVariable &&
|
||||
templateVariable.sensitive &&
|
||||
variableValue.value === ""
|
||||
) {
|
||||
return
|
||||
}
|
||||
filtered.push(variableValue)
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
...request,
|
||||
user_variable_values: filtered,
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplateVariablesPage
|
||||
@@ -0,0 +1,78 @@
|
||||
import { action } from "@storybook/addon-actions"
|
||||
import { Story } from "@storybook/react"
|
||||
import {
|
||||
makeMockApiError,
|
||||
MockTemplateVersion,
|
||||
MockTemplateVersionVariable1,
|
||||
MockTemplateVersionVariable2,
|
||||
MockTemplateVersionVariable3,
|
||||
MockTemplateVersionVariable4,
|
||||
MockTemplateVersionVariable5,
|
||||
} from "testHelpers/entities"
|
||||
import {
|
||||
TemplateVariablesPageView,
|
||||
TemplateVariablesPageViewProps,
|
||||
} from "./TemplateVariablesPageView"
|
||||
|
||||
export default {
|
||||
title: "pages/TemplateVariablesPageView",
|
||||
component: TemplateVariablesPageView,
|
||||
}
|
||||
|
||||
const TemplateVariables: Story<TemplateVariablesPageViewProps> = (args) => (
|
||||
<TemplateVariablesPageView {...args} />
|
||||
)
|
||||
|
||||
export const Loading = TemplateVariables.bind({})
|
||||
Loading.args = {
|
||||
onSubmit: action("onSubmit"),
|
||||
onCancel: action("cancel"),
|
||||
}
|
||||
|
||||
export const Basic = TemplateVariables.bind({})
|
||||
Basic.args = {
|
||||
templateVersion: MockTemplateVersion,
|
||||
templateVariables: [
|
||||
MockTemplateVersionVariable1,
|
||||
MockTemplateVersionVariable2,
|
||||
MockTemplateVersionVariable3,
|
||||
MockTemplateVersionVariable4,
|
||||
],
|
||||
onSubmit: action("onSubmit"),
|
||||
onCancel: action("cancel"),
|
||||
}
|
||||
|
||||
// This example isn't fully supported. As "user_variable_values" is an array,
|
||||
// FormikTouched can't properly handle this.
|
||||
// See: https://github.com/jaredpalmer/formik/issues/2022
|
||||
export const RequiredVariable = TemplateVariables.bind({})
|
||||
RequiredVariable.args = {
|
||||
templateVersion: MockTemplateVersion,
|
||||
templateVariables: [
|
||||
MockTemplateVersionVariable4,
|
||||
MockTemplateVersionVariable5,
|
||||
],
|
||||
onSubmit: action("onSubmit"),
|
||||
onCancel: action("cancel"),
|
||||
initialTouched: {
|
||||
user_variable_values: true,
|
||||
},
|
||||
}
|
||||
|
||||
export const WithUpdateTemplateError = TemplateVariables.bind({})
|
||||
WithUpdateTemplateError.args = {
|
||||
templateVersion: MockTemplateVersion,
|
||||
templateVariables: [
|
||||
MockTemplateVersionVariable1,
|
||||
MockTemplateVersionVariable2,
|
||||
MockTemplateVersionVariable3,
|
||||
MockTemplateVersionVariable4,
|
||||
],
|
||||
errors: {
|
||||
updateTemplateError: makeMockApiError({
|
||||
message: "Something went wrong.",
|
||||
}),
|
||||
},
|
||||
onSubmit: action("onSubmit"),
|
||||
onCancel: action("cancel"),
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import {
|
||||
CreateTemplateVersionRequest,
|
||||
TemplateVersion,
|
||||
TemplateVersionVariable,
|
||||
} from "api/typesGenerated"
|
||||
import { AlertBanner } from "components/AlertBanner/AlertBanner"
|
||||
import { Loader } from "components/Loader/Loader"
|
||||
import { ComponentProps, FC } from "react"
|
||||
import { TemplateVariablesForm } from "./TemplateVariablesForm"
|
||||
import { Stack } from "components/Stack/Stack"
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { FullPageHorizontalForm } from "components/FullPageForm/FullPageHorizontalForm"
|
||||
import { GoBackButton } from "components/GoBackButton/GoBackButton"
|
||||
|
||||
export interface TemplateVariablesPageViewProps {
|
||||
templateVersion?: TemplateVersion
|
||||
templateVariables?: TemplateVersionVariable[]
|
||||
onSubmit: (data: CreateTemplateVersionRequest) => void
|
||||
onCancel: () => void
|
||||
isSubmitting: boolean
|
||||
errors?: {
|
||||
getTemplateDataError?: unknown
|
||||
updateTemplateError?: unknown
|
||||
}
|
||||
initialTouched?: ComponentProps<
|
||||
typeof TemplateVariablesForm
|
||||
>["initialTouched"]
|
||||
}
|
||||
|
||||
export const TemplateVariablesPageView: FC<TemplateVariablesPageViewProps> = ({
|
||||
templateVersion,
|
||||
templateVariables,
|
||||
onCancel,
|
||||
onSubmit,
|
||||
isSubmitting,
|
||||
errors = {},
|
||||
initialTouched,
|
||||
}) => {
|
||||
const classes = useStyles()
|
||||
const isLoading =
|
||||
!templateVersion &&
|
||||
!templateVariables &&
|
||||
!errors.getTemplateDataError &&
|
||||
!errors.updateTemplateError
|
||||
const { t } = useTranslation("templateVariablesPage")
|
||||
|
||||
return (
|
||||
<FullPageHorizontalForm title={t("title")} onCancel={onCancel}>
|
||||
{Boolean(errors.getTemplateDataError) && (
|
||||
<Stack className={classes.errorContainer}>
|
||||
<AlertBanner severity="error" error={errors.getTemplateDataError} />
|
||||
</Stack>
|
||||
)}
|
||||
{Boolean(errors.updateTemplateError) && (
|
||||
<Stack className={classes.errorContainer}>
|
||||
<AlertBanner severity="error" error={errors.updateTemplateError} />
|
||||
</Stack>
|
||||
)}
|
||||
{isLoading && <Loader />}
|
||||
{templateVersion && templateVariables && templateVariables.length > 0 && (
|
||||
<TemplateVariablesForm
|
||||
initialTouched={initialTouched}
|
||||
isSubmitting={isSubmitting}
|
||||
templateVersion={templateVersion}
|
||||
templateVariables={templateVariables}
|
||||
onSubmit={onSubmit}
|
||||
onCancel={onCancel}
|
||||
error={errors.updateTemplateError}
|
||||
/>
|
||||
)}
|
||||
{templateVariables && templateVariables.length === 0 && (
|
||||
<div>
|
||||
<AlertBanner severity="info" text={t("unusedVariablesNotice")} />
|
||||
<div className={classes.goBackSection}>
|
||||
<GoBackButton onClick={onCancel} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</FullPageHorizontalForm>
|
||||
)
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
errorContainer: {
|
||||
marginBottom: theme.spacing(2),
|
||||
},
|
||||
goBackSection: {
|
||||
display: "flex",
|
||||
width: "100%",
|
||||
marginTop: 32,
|
||||
},
|
||||
}))
|
||||
@@ -787,6 +787,56 @@ export const MockTemplateVersionParameter5: TypesGen.TemplateVersionParameter =
|
||||
validation_monotonic: "decreasing",
|
||||
}
|
||||
|
||||
export const MockTemplateVersionVariable1: TypesGen.TemplateVersionVariable = {
|
||||
name: "first_variable",
|
||||
description: "This is first variable.",
|
||||
type: "string",
|
||||
value: "",
|
||||
default_value: "abc",
|
||||
required: false,
|
||||
sensitive: false,
|
||||
}
|
||||
|
||||
export const MockTemplateVersionVariable2: TypesGen.TemplateVersionVariable = {
|
||||
name: "second_variable",
|
||||
description: "This is second variable.",
|
||||
type: "number",
|
||||
value: "5",
|
||||
default_value: "3",
|
||||
required: false,
|
||||
sensitive: false,
|
||||
}
|
||||
|
||||
export const MockTemplateVersionVariable3: TypesGen.TemplateVersionVariable = {
|
||||
name: "third_variable",
|
||||
description: "This is third variable.",
|
||||
type: "bool",
|
||||
value: "",
|
||||
default_value: "false",
|
||||
required: false,
|
||||
sensitive: false,
|
||||
}
|
||||
|
||||
export const MockTemplateVersionVariable4: TypesGen.TemplateVersionVariable = {
|
||||
name: "fourth_variable",
|
||||
description: "This is fourth variable.",
|
||||
type: "string",
|
||||
value: "defghijk",
|
||||
default_value: "",
|
||||
required: true,
|
||||
sensitive: true,
|
||||
}
|
||||
|
||||
export const MockTemplateVersionVariable5: TypesGen.TemplateVersionVariable = {
|
||||
name: "fifth_variable",
|
||||
description: "This is fifth variable.",
|
||||
type: "string",
|
||||
value: "",
|
||||
default_value: "",
|
||||
required: true,
|
||||
sensitive: false,
|
||||
}
|
||||
|
||||
// requests the MockWorkspace
|
||||
export const MockWorkspaceRequest: TypesGen.CreateWorkspaceRequest = {
|
||||
name: "test",
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
import {
|
||||
createTemplateVersion,
|
||||
getTemplateByName,
|
||||
getTemplateVersion,
|
||||
getTemplateVersionVariables,
|
||||
updateActiveTemplateVersion,
|
||||
} from "api/api"
|
||||
import {
|
||||
CreateTemplateVersionRequest,
|
||||
Template,
|
||||
TemplateVersion,
|
||||
TemplateVersionVariable,
|
||||
} from "api/typesGenerated"
|
||||
import { assign, createMachine } from "xstate"
|
||||
import { delay } from "util/delay"
|
||||
import { Message } from "api/types"
|
||||
|
||||
type TemplateVariablesContext = {
|
||||
organizationId: string
|
||||
templateName: string
|
||||
|
||||
template?: Template
|
||||
activeTemplateVersion?: TemplateVersion
|
||||
templateVariables?: TemplateVersionVariable[]
|
||||
|
||||
createTemplateVersionRequest?: CreateTemplateVersionRequest
|
||||
newTemplateVersion?: TemplateVersion
|
||||
|
||||
getTemplateDataError?: Error | unknown
|
||||
updateTemplateError?: Error | unknown
|
||||
}
|
||||
|
||||
type UpdateTemplateEvent = {
|
||||
type: "UPDATE_TEMPLATE_EVENT"
|
||||
request: CreateTemplateVersionRequest
|
||||
}
|
||||
|
||||
export const templateVariablesMachine = createMachine(
|
||||
{
|
||||
id: "templateVariablesState",
|
||||
predictableActionArguments: true,
|
||||
tsTypes: {} as import("./templateVariablesXService.typegen").Typegen0,
|
||||
schema: {
|
||||
context: {} as TemplateVariablesContext,
|
||||
events: {} as UpdateTemplateEvent,
|
||||
services: {} as {
|
||||
getTemplate: {
|
||||
data: Template
|
||||
}
|
||||
getActiveTemplateVersion: {
|
||||
data: TemplateVersion
|
||||
}
|
||||
getTemplateVariables: {
|
||||
data: TemplateVersionVariable[]
|
||||
}
|
||||
createNewTemplateVersion: {
|
||||
data: TemplateVersion
|
||||
}
|
||||
waitForJobToBeCompleted: {
|
||||
data: TemplateVersion
|
||||
}
|
||||
updateTemplate: {
|
||||
data: Message
|
||||
}
|
||||
},
|
||||
},
|
||||
initial: "gettingTemplate",
|
||||
states: {
|
||||
gettingTemplate: {
|
||||
entry: "clearGetTemplateDataError",
|
||||
invoke: {
|
||||
src: "getTemplate",
|
||||
onDone: [
|
||||
{
|
||||
actions: ["assignTemplate"],
|
||||
target: "gettingActiveTemplateVersion",
|
||||
},
|
||||
],
|
||||
onError: {
|
||||
actions: ["assignGetTemplateDataError"],
|
||||
target: "error",
|
||||
},
|
||||
},
|
||||
},
|
||||
gettingActiveTemplateVersion: {
|
||||
entry: "clearGetTemplateDataError",
|
||||
invoke: {
|
||||
src: "getActiveTemplateVersion",
|
||||
onDone: [
|
||||
{
|
||||
actions: ["assignActiveTemplateVersion"],
|
||||
target: "gettingTemplateVariables",
|
||||
},
|
||||
],
|
||||
onError: {
|
||||
actions: ["assignGetTemplateDataError"],
|
||||
target: "error",
|
||||
},
|
||||
},
|
||||
},
|
||||
gettingTemplateVariables: {
|
||||
entry: "clearGetTemplateDataError",
|
||||
invoke: {
|
||||
src: "getTemplateVariables",
|
||||
onDone: [
|
||||
{
|
||||
actions: ["assignTemplateVariables"],
|
||||
target: "fillingParams",
|
||||
},
|
||||
],
|
||||
onError: {
|
||||
actions: ["assignGetTemplateDataError"],
|
||||
target: "error",
|
||||
},
|
||||
},
|
||||
},
|
||||
fillingParams: {
|
||||
on: {
|
||||
UPDATE_TEMPLATE_EVENT: {
|
||||
actions: ["assignCreateTemplateVersionRequest"],
|
||||
target: "creatingTemplateVersion",
|
||||
},
|
||||
},
|
||||
},
|
||||
creatingTemplateVersion: {
|
||||
entry: "clearUpdateTemplateError",
|
||||
invoke: {
|
||||
src: "createNewTemplateVersion",
|
||||
onDone: {
|
||||
actions: ["assignNewTemplateVersion"],
|
||||
target: "waitingForJobToBeCompleted",
|
||||
},
|
||||
onError: {
|
||||
actions: ["assignGetTemplateDataError"],
|
||||
target: "fillingParams",
|
||||
},
|
||||
},
|
||||
tags: ["submitting"],
|
||||
},
|
||||
waitingForJobToBeCompleted: {
|
||||
invoke: {
|
||||
src: "waitForJobToBeCompleted",
|
||||
onDone: [
|
||||
{
|
||||
actions: ["assignNewTemplateVersion"],
|
||||
target: "updatingTemplate",
|
||||
},
|
||||
],
|
||||
onError: {
|
||||
actions: ["assignUpdateTemplateError"],
|
||||
target: "fillingParams",
|
||||
},
|
||||
},
|
||||
tags: ["submitting"],
|
||||
},
|
||||
updatingTemplate: {
|
||||
invoke: {
|
||||
src: "updateTemplate",
|
||||
onDone: {
|
||||
target: "updated",
|
||||
actions: ["onUpdateTemplate"],
|
||||
},
|
||||
onError: {
|
||||
actions: ["assignUpdateTemplateError"],
|
||||
target: "fillingParams",
|
||||
},
|
||||
},
|
||||
tags: ["submitting"],
|
||||
},
|
||||
updated: {
|
||||
entry: "onUpdateTemplate",
|
||||
type: "final",
|
||||
},
|
||||
error: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
services: {
|
||||
getTemplate: ({ organizationId, templateName }) => {
|
||||
return getTemplateByName(organizationId, templateName)
|
||||
},
|
||||
getActiveTemplateVersion: ({ template }) => {
|
||||
if (!template) {
|
||||
throw new Error("No template selected")
|
||||
}
|
||||
return getTemplateVersion(template.active_version_id)
|
||||
},
|
||||
getTemplateVariables: ({ template }) => {
|
||||
if (!template) {
|
||||
throw new Error("No template selected")
|
||||
}
|
||||
return getTemplateVersionVariables(template.active_version_id)
|
||||
},
|
||||
createNewTemplateVersion: ({
|
||||
organizationId,
|
||||
createTemplateVersionRequest,
|
||||
}) => {
|
||||
if (!createTemplateVersionRequest) {
|
||||
throw new Error("Missing request body")
|
||||
}
|
||||
return createTemplateVersion(
|
||||
organizationId,
|
||||
createTemplateVersionRequest,
|
||||
)
|
||||
},
|
||||
waitForJobToBeCompleted: async ({ newTemplateVersion }) => {
|
||||
if (!newTemplateVersion) {
|
||||
throw new Error("Template version is undefined")
|
||||
}
|
||||
|
||||
let status = newTemplateVersion.job.status
|
||||
while (["pending", "running"].includes(status)) {
|
||||
newTemplateVersion = await getTemplateVersion(newTemplateVersion.id)
|
||||
status = newTemplateVersion.job.status
|
||||
await delay(2_000)
|
||||
}
|
||||
return newTemplateVersion
|
||||
},
|
||||
updateTemplate: ({ template, newTemplateVersion }) => {
|
||||
if (!template) {
|
||||
throw new Error("No template selected")
|
||||
}
|
||||
|
||||
if (!newTemplateVersion) {
|
||||
throw new Error("New template version is undefined")
|
||||
}
|
||||
|
||||
return updateActiveTemplateVersion(template.id, {
|
||||
id: newTemplateVersion.id,
|
||||
})
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
assignTemplate: assign({
|
||||
template: (_, event) => event.data,
|
||||
}),
|
||||
assignActiveTemplateVersion: assign({
|
||||
activeTemplateVersion: (_, event) => event.data,
|
||||
}),
|
||||
assignTemplateVariables: assign({
|
||||
templateVariables: (_, event) => event.data,
|
||||
}),
|
||||
assignCreateTemplateVersionRequest: assign({
|
||||
createTemplateVersionRequest: (_, event) => event.request,
|
||||
}),
|
||||
assignNewTemplateVersion: assign({
|
||||
newTemplateVersion: (_, event) => event.data,
|
||||
}),
|
||||
assignGetTemplateDataError: assign({
|
||||
getTemplateDataError: (_, event) => event.data,
|
||||
}),
|
||||
clearGetTemplateDataError: assign({
|
||||
getTemplateDataError: (_) => undefined,
|
||||
}),
|
||||
assignUpdateTemplateError: assign({
|
||||
updateTemplateError: (_, event) => event.data,
|
||||
}),
|
||||
clearUpdateTemplateError: assign({
|
||||
updateTemplateError: (_) => undefined,
|
||||
}),
|
||||
},
|
||||
},
|
||||
)
|
||||
Reference in New Issue
Block a user