feat(editor): ✨ Code step
This commit is contained in:
@@ -3,6 +3,7 @@ import {
|
|||||||
CalendarIcon,
|
CalendarIcon,
|
||||||
ChatIcon,
|
ChatIcon,
|
||||||
CheckSquareIcon,
|
CheckSquareIcon,
|
||||||
|
CodeIcon,
|
||||||
EditIcon,
|
EditIcon,
|
||||||
EmailIcon,
|
EmailIcon,
|
||||||
ExternalLinkIcon,
|
ExternalLinkIcon,
|
||||||
@@ -57,6 +58,8 @@ export const StepIcon = ({ type, ...props }: StepIconProps) => {
|
|||||||
return <FilterIcon color="purple.500" {...props} />
|
return <FilterIcon color="purple.500" {...props} />
|
||||||
case LogicStepType.REDIRECT:
|
case LogicStepType.REDIRECT:
|
||||||
return <ExternalLinkIcon color="purple.500" {...props} />
|
return <ExternalLinkIcon color="purple.500" {...props} />
|
||||||
|
case LogicStepType.CODE:
|
||||||
|
return <CodeIcon color="purple.500" {...props} />
|
||||||
case IntegrationStepType.GOOGLE_SHEETS:
|
case IntegrationStepType.GOOGLE_SHEETS:
|
||||||
return <GoogleSheetsLogo {...props} />
|
return <GoogleSheetsLogo {...props} />
|
||||||
case IntegrationStepType.GOOGLE_ANALYTICS:
|
case IntegrationStepType.GOOGLE_ANALYTICS:
|
||||||
|
|||||||
@@ -37,6 +37,12 @@ export const StepTypeLabel = ({ type }: Props) => {
|
|||||||
return <Text>Condition</Text>
|
return <Text>Condition</Text>
|
||||||
case LogicStepType.REDIRECT:
|
case LogicStepType.REDIRECT:
|
||||||
return <Text>Redirect</Text>
|
return <Text>Redirect</Text>
|
||||||
|
case LogicStepType.CODE:
|
||||||
|
return (
|
||||||
|
<Tooltip label="Run Javascript code">
|
||||||
|
<Text>Code</Text>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
case IntegrationStepType.GOOGLE_SHEETS:
|
case IntegrationStepType.GOOGLE_SHEETS:
|
||||||
return (
|
return (
|
||||||
<Tooltip label="Google Sheets">
|
<Tooltip label="Google Sheets">
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import {
|
|||||||
DateInputSettingsBody,
|
DateInputSettingsBody,
|
||||||
} from './bodies'
|
} from './bodies'
|
||||||
import { ChoiceInputSettingsBody } from './bodies/ChoiceInputSettingsBody'
|
import { ChoiceInputSettingsBody } from './bodies/ChoiceInputSettingsBody'
|
||||||
|
import { CodeSettings } from './bodies/CodeSettings'
|
||||||
import { ConditionSettingsBody } from './bodies/ConditionSettingsBody'
|
import { ConditionSettingsBody } from './bodies/ConditionSettingsBody'
|
||||||
import { GoogleAnalyticsSettings } from './bodies/GoogleAnalyticsSettings'
|
import { GoogleAnalyticsSettings } from './bodies/GoogleAnalyticsSettings'
|
||||||
import { GoogleSheetsSettingsBody } from './bodies/GoogleSheetsSettingsBody'
|
import { GoogleSheetsSettingsBody } from './bodies/GoogleSheetsSettingsBody'
|
||||||
@@ -177,6 +178,14 @@ export const StepSettings = ({
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
case LogicStepType.CODE: {
|
||||||
|
return (
|
||||||
|
<CodeSettings
|
||||||
|
options={step.options}
|
||||||
|
onOptionsChange={handleOptionsChange}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
case IntegrationStepType.GOOGLE_SHEETS: {
|
case IntegrationStepType.GOOGLE_SHEETS: {
|
||||||
return (
|
return (
|
||||||
<GoogleSheetsSettingsBody
|
<GoogleSheetsSettingsBody
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { FormLabel, Stack, Text } from '@chakra-ui/react'
|
||||||
|
import { CodeEditor } from 'components/shared/CodeEditor'
|
||||||
|
import { DebouncedInput } from 'components/shared/DebouncedInput'
|
||||||
|
import { CodeOptions } from 'models'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
options: CodeOptions
|
||||||
|
onOptionsChange: (options: CodeOptions) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CodeSettings = ({ options, onOptionsChange }: Props) => {
|
||||||
|
const handleNameChange = (name: string) =>
|
||||||
|
onOptionsChange({ ...options, name })
|
||||||
|
const handleCodeChange = (content: string) =>
|
||||||
|
onOptionsChange({ ...options, content })
|
||||||
|
return (
|
||||||
|
<Stack spacing={4}>
|
||||||
|
<Stack>
|
||||||
|
<FormLabel mb="0" htmlFor="name">
|
||||||
|
Name:
|
||||||
|
</FormLabel>
|
||||||
|
<DebouncedInput
|
||||||
|
id="name"
|
||||||
|
initialValue={options.name}
|
||||||
|
onChange={handleNameChange}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
<Stack>
|
||||||
|
<Text>Code:</Text>
|
||||||
|
<CodeEditor
|
||||||
|
value={options.content ?? ''}
|
||||||
|
lang="js"
|
||||||
|
onChange={handleCodeChange}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -78,6 +78,16 @@ export const StepNodeContent = ({ step, indices }: Props) => {
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
case LogicStepType.CODE: {
|
||||||
|
return (
|
||||||
|
<ConfigureContent
|
||||||
|
label={
|
||||||
|
step.options?.content ? `Run ${step.options?.name}` : undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
case IntegrationStepType.GOOGLE_SHEETS: {
|
case IntegrationStepType.GOOGLE_SHEETS: {
|
||||||
return (
|
return (
|
||||||
<ConfigureContent
|
<ConfigureContent
|
||||||
|
|||||||
101
apps/builder/playwright/fixtures/typebots/logic/code.json
Normal file
101
apps/builder/playwright/fixtures/typebots/logic/code.json
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
{
|
||||||
|
"id": "ckz8hnw7m10833no1ar12eov20",
|
||||||
|
"createdAt": "2022-02-04T14:14:21.394Z",
|
||||||
|
"updatedAt": "2022-02-04T14:14:21.394Z",
|
||||||
|
"name": "My typebot",
|
||||||
|
"ownerId": "ckz6t9iep0006k31a22j05fwq",
|
||||||
|
"publishedTypebotId": null,
|
||||||
|
"folderId": null,
|
||||||
|
"blocks": [
|
||||||
|
{
|
||||||
|
"id": "tdN9VXcdBWpuh6Gpaz3w4u",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"id": "cVRL5EuVruTK31SAaVCvNE",
|
||||||
|
"type": "start",
|
||||||
|
"label": "Start",
|
||||||
|
"blockId": "tdN9VXcdBWpuh6Gpaz3w4u",
|
||||||
|
"outgoingEdgeId": "jqZYCYGxaL8svJbM2h1QAn"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Start",
|
||||||
|
"graphCoordinates": { "x": 0, "y": 0 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "vymPUjL9AcWpkg9PkUXovk",
|
||||||
|
"graphCoordinates": { "x": 685, "y": 194 },
|
||||||
|
"title": "Block #1",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"id": "sa8WhnrMyMjYCBMeozfYRoi",
|
||||||
|
"blockId": "vymPUjL9AcWpkg9PkUXovk",
|
||||||
|
"type": "Code",
|
||||||
|
"options": { "name": "Code snippet" }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "rEJ3PhFQc7diJ23jdoF6w7",
|
||||||
|
"graphCoordinates": { "x": 294, "y": 201 },
|
||||||
|
"title": "Block #2",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"id": "s7QRApVZmVFZgS53CNruBRz",
|
||||||
|
"blockId": "rEJ3PhFQc7diJ23jdoF6w7",
|
||||||
|
"type": "choice input",
|
||||||
|
"options": { "buttonLabel": "Send", "isMultipleChoice": false },
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "5rWR3enRg6jZyFhtmgbPYo",
|
||||||
|
"stepId": "s7QRApVZmVFZgS53CNruBRz",
|
||||||
|
"type": 0,
|
||||||
|
"content": "Trigger code",
|
||||||
|
"outgoingEdgeId": "6aVDkPMEsadze2vf4mLiYt"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"variables": [],
|
||||||
|
"edges": [
|
||||||
|
{
|
||||||
|
"from": {
|
||||||
|
"blockId": "tdN9VXcdBWpuh6Gpaz3w4u",
|
||||||
|
"stepId": "cVRL5EuVruTK31SAaVCvNE"
|
||||||
|
},
|
||||||
|
"to": { "blockId": "rEJ3PhFQc7diJ23jdoF6w7" },
|
||||||
|
"id": "jqZYCYGxaL8svJbM2h1QAn"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"from": {
|
||||||
|
"blockId": "rEJ3PhFQc7diJ23jdoF6w7",
|
||||||
|
"stepId": "s7QRApVZmVFZgS53CNruBRz",
|
||||||
|
"itemId": "5rWR3enRg6jZyFhtmgbPYo"
|
||||||
|
},
|
||||||
|
"to": { "blockId": "vymPUjL9AcWpkg9PkUXovk" },
|
||||||
|
"id": "6aVDkPMEsadze2vf4mLiYt"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"theme": {
|
||||||
|
"chat": {
|
||||||
|
"inputs": {
|
||||||
|
"color": "#303235",
|
||||||
|
"backgroundColor": "#FFFFFF",
|
||||||
|
"placeholderColor": "#9095A0"
|
||||||
|
},
|
||||||
|
"buttons": { "color": "#FFFFFF", "backgroundColor": "#0042DA" },
|
||||||
|
"hostBubbles": { "color": "#303235", "backgroundColor": "#F7F8FF" },
|
||||||
|
"guestBubbles": { "color": "#FFFFFF", "backgroundColor": "#FF8E21" }
|
||||||
|
},
|
||||||
|
"general": { "font": "Open Sans", "background": { "type": "None" } }
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"general": { "isBrandingEnabled": true },
|
||||||
|
"metadata": {
|
||||||
|
"description": "Build beautiful conversational forms and embed them directly in your applications without a line of code. Triple your response rate and collect answers that has more value compared to a traditional form."
|
||||||
|
},
|
||||||
|
"typingEmulation": { "speed": 300, "enabled": true, "maxDelay": 1.5 }
|
||||||
|
},
|
||||||
|
"publicId": null
|
||||||
|
}
|
||||||
29
apps/builder/playwright/tests/logic/code.spec.ts
Normal file
29
apps/builder/playwright/tests/logic/code.spec.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import test, { expect } from '@playwright/test'
|
||||||
|
import path from 'path'
|
||||||
|
import { typebotViewer } from '../../services/selectorUtils'
|
||||||
|
import { importTypebotInDatabase } from '../../services/database'
|
||||||
|
import { generate } from 'short-uuid'
|
||||||
|
|
||||||
|
const typebotId = generate()
|
||||||
|
|
||||||
|
test.describe('Code step', () => {
|
||||||
|
test('code should trigger', async ({ page }) => {
|
||||||
|
await importTypebotInDatabase(
|
||||||
|
path.join(__dirname, '../../fixtures/typebots/logic/code.json'),
|
||||||
|
{
|
||||||
|
id: typebotId,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
await page.goto(`/typebots/${typebotId}/edit`)
|
||||||
|
await page.click('text=Configure...')
|
||||||
|
await page.fill(
|
||||||
|
'div[role="textbox"]',
|
||||||
|
'window.location.href = "https://www.google.com"'
|
||||||
|
)
|
||||||
|
|
||||||
|
await page.click('text=Preview')
|
||||||
|
await typebotViewer(page).locator('text=Trigger code').click()
|
||||||
|
await expect(page).toHaveURL('https://www.google.com')
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -27,6 +27,7 @@ import {
|
|||||||
defaultRedirectOptions,
|
defaultRedirectOptions,
|
||||||
defaultGoogleSheetsOptions,
|
defaultGoogleSheetsOptions,
|
||||||
defaultGoogleAnalyticsOptions,
|
defaultGoogleAnalyticsOptions,
|
||||||
|
defaultCodeOptions,
|
||||||
defaultWebhookOptions,
|
defaultWebhookOptions,
|
||||||
StepWithOptionsType,
|
StepWithOptionsType,
|
||||||
Item,
|
Item,
|
||||||
@@ -226,6 +227,8 @@ const parseDefaultStepOptions = (type: StepWithOptionsType): StepOptions => {
|
|||||||
return defaultSetVariablesOptions
|
return defaultSetVariablesOptions
|
||||||
case LogicStepType.REDIRECT:
|
case LogicStepType.REDIRECT:
|
||||||
return defaultRedirectOptions
|
return defaultRedirectOptions
|
||||||
|
case LogicStepType.CODE:
|
||||||
|
return defaultCodeOptions
|
||||||
case IntegrationStepType.GOOGLE_SHEETS:
|
case IntegrationStepType.GOOGLE_SHEETS:
|
||||||
return defaultGoogleSheetsOptions
|
return defaultGoogleSheetsOptions
|
||||||
case IntegrationStepType.GOOGLE_ANALYTICS:
|
case IntegrationStepType.GOOGLE_ANALYTICS:
|
||||||
|
|||||||
@@ -21,8 +21,6 @@ import { sendRequest } from 'utils'
|
|||||||
import { sendGaEvent } from '../../lib/gtag'
|
import { sendGaEvent } from '../../lib/gtag'
|
||||||
import { parseVariables, parseVariablesInObject } from './variable'
|
import { parseVariables, parseVariablesInObject } from './variable'
|
||||||
|
|
||||||
const safeEval = eval
|
|
||||||
|
|
||||||
type IntegrationContext = {
|
type IntegrationContext = {
|
||||||
apiHost: string
|
apiHost: string
|
||||||
typebotId: string
|
typebotId: string
|
||||||
@@ -222,7 +220,9 @@ const executeWebhook = async (
|
|||||||
})
|
})
|
||||||
step.options.responseVariableMapping.forEach((varMapping) => {
|
step.options.responseVariableMapping.forEach((varMapping) => {
|
||||||
if (!varMapping?.bodyPath || !varMapping.variableId) return
|
if (!varMapping?.bodyPath || !varMapping.variableId) return
|
||||||
const value = safeEval(`(${JSON.stringify(data)}).${varMapping?.bodyPath}`)
|
const value = Function(
|
||||||
|
`return (${JSON.stringify(data)}).${varMapping?.bodyPath}`
|
||||||
|
)()
|
||||||
updateVariableValue(varMapping.variableId, value)
|
updateVariableValue(varMapping.variableId, value)
|
||||||
})
|
})
|
||||||
return step.outgoingEdgeId
|
return step.outgoingEdgeId
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
SetVariableStep,
|
SetVariableStep,
|
||||||
RedirectStep,
|
RedirectStep,
|
||||||
Comparison,
|
Comparison,
|
||||||
|
CodeStep,
|
||||||
} from 'models'
|
} from 'models'
|
||||||
import { isDefined, isNotDefined } from 'utils'
|
import { isDefined, isNotDefined } from 'utils'
|
||||||
import { sanitizeUrl } from './utils'
|
import { sanitizeUrl } from './utils'
|
||||||
@@ -27,6 +28,8 @@ export const executeLogic = (
|
|||||||
return executeCondition(step, variables)
|
return executeCondition(step, variables)
|
||||||
case LogicStepType.REDIRECT:
|
case LogicStepType.REDIRECT:
|
||||||
return executeRedirect(step, variables)
|
return executeRedirect(step, variables)
|
||||||
|
case LogicStepType.CODE:
|
||||||
|
return executeCode(step)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,3 +100,9 @@ const executeRedirect = (
|
|||||||
)
|
)
|
||||||
return step.outgoingEdgeId
|
return step.outgoingEdgeId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const executeCode = (step: CodeStep) => {
|
||||||
|
if (!step.options.content) return
|
||||||
|
Function(step.options.content)()
|
||||||
|
return step.outgoingEdgeId
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { Variable } from 'models'
|
import { Variable } from 'models'
|
||||||
import { isDefined, isNotDefined } from 'utils'
|
import { isDefined, isNotDefined } from 'utils'
|
||||||
|
|
||||||
const safeEval = eval
|
|
||||||
|
|
||||||
export const stringContainsVariable = (str: string): boolean =>
|
export const stringContainsVariable = (str: string): boolean =>
|
||||||
/\{\{(.*?)\}\}/g.test(str)
|
/\{\{(.*?)\}\}/g.test(str)
|
||||||
|
|
||||||
@@ -22,7 +20,7 @@ export const parseVariables =
|
|||||||
|
|
||||||
export const evaluateExpression = (str: string) => {
|
export const evaluateExpression = (str: string) => {
|
||||||
try {
|
try {
|
||||||
const evaluatedResult = safeEval(str)
|
const evaluatedResult = Function('return' + str)()
|
||||||
return isNotDefined(evaluatedResult) ? '' : evaluatedResult.toString()
|
return isNotDefined(evaluatedResult) ? '' : evaluatedResult.toString()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err)
|
console.log(err)
|
||||||
|
|||||||
@@ -1,16 +1,24 @@
|
|||||||
import { ItemType, StepBase } from '.'
|
import { ItemType, StepBase } from '.'
|
||||||
import { ItemBase } from './item'
|
import { ItemBase } from './item'
|
||||||
|
|
||||||
export type LogicStep = SetVariableStep | ConditionStep | RedirectStep
|
export type LogicStep =
|
||||||
|
| SetVariableStep
|
||||||
|
| ConditionStep
|
||||||
|
| RedirectStep
|
||||||
|
| CodeStep
|
||||||
|
|
||||||
|
export type LogicStepOptions =
|
||||||
|
| SetVariableOptions
|
||||||
|
| RedirectOptions
|
||||||
|
| CodeOptions
|
||||||
|
|
||||||
export enum LogicStepType {
|
export enum LogicStepType {
|
||||||
SET_VARIABLE = 'Set variable',
|
SET_VARIABLE = 'Set variable',
|
||||||
CONDITION = 'Condition',
|
CONDITION = 'Condition',
|
||||||
REDIRECT = 'Redirect',
|
REDIRECT = 'Redirect',
|
||||||
|
CODE = 'Code',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LogicStepOptions = SetVariableOptions | RedirectOptions
|
|
||||||
|
|
||||||
export type SetVariableStep = StepBase & {
|
export type SetVariableStep = StepBase & {
|
||||||
type: LogicStepType.SET_VARIABLE
|
type: LogicStepType.SET_VARIABLE
|
||||||
options: SetVariableOptions
|
options: SetVariableOptions
|
||||||
@@ -31,6 +39,11 @@ export type RedirectStep = StepBase & {
|
|||||||
options: RedirectOptions
|
options: RedirectOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type CodeStep = StepBase & {
|
||||||
|
type: LogicStepType.CODE
|
||||||
|
options: CodeOptions
|
||||||
|
}
|
||||||
|
|
||||||
export enum LogicalOperator {
|
export enum LogicalOperator {
|
||||||
OR = 'OR',
|
OR = 'OR',
|
||||||
AND = 'AND',
|
AND = 'AND',
|
||||||
@@ -67,6 +80,11 @@ export type RedirectOptions = {
|
|||||||
isNewTab: boolean
|
isNewTab: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type CodeOptions = {
|
||||||
|
name: string
|
||||||
|
content?: string
|
||||||
|
}
|
||||||
|
|
||||||
export const defaultSetVariablesOptions: SetVariableOptions = {}
|
export const defaultSetVariablesOptions: SetVariableOptions = {}
|
||||||
|
|
||||||
export const defaultConditionContent: ConditionContent = {
|
export const defaultConditionContent: ConditionContent = {
|
||||||
@@ -75,3 +93,5 @@ export const defaultConditionContent: ConditionContent = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const defaultRedirectOptions: RedirectOptions = { isNewTab: false }
|
export const defaultRedirectOptions: RedirectOptions = { isNewTab: false }
|
||||||
|
|
||||||
|
export const defaultCodeOptions: CodeOptions = { name: 'Code snippet' }
|
||||||
|
|||||||
@@ -4,13 +4,11 @@ import {
|
|||||||
IntegrationStepType,
|
IntegrationStepType,
|
||||||
Item,
|
Item,
|
||||||
LogicStepOptions,
|
LogicStepOptions,
|
||||||
RedirectStep,
|
|
||||||
SetVariableStep,
|
|
||||||
} from '.'
|
} from '.'
|
||||||
import { BubbleStep, BubbleStepType } from './bubble'
|
import { BubbleStep, BubbleStepType } from './bubble'
|
||||||
import { InputStep, InputStepType } from './inputs'
|
import { InputStep, InputStepType } from './inputs'
|
||||||
import { IntegrationStep } from './integration'
|
import { IntegrationStep } from './integration'
|
||||||
import { LogicStep, LogicStepType } from './logic'
|
import { ConditionStep, LogicStep, LogicStepType } from './logic'
|
||||||
|
|
||||||
export type Step =
|
export type Step =
|
||||||
| StartStep
|
| StartStep
|
||||||
@@ -36,14 +34,12 @@ export type DraggableStepType =
|
|||||||
|
|
||||||
export type StepWithOptions =
|
export type StepWithOptions =
|
||||||
| InputStep
|
| InputStep
|
||||||
| SetVariableStep
|
| Exclude<LogicStep, ConditionStep>
|
||||||
| RedirectStep
|
|
||||||
| IntegrationStep
|
| IntegrationStep
|
||||||
|
|
||||||
export type StepWithOptionsType =
|
export type StepWithOptionsType =
|
||||||
| InputStepType
|
| InputStepType
|
||||||
| LogicStepType.REDIRECT
|
| Exclude<LogicStepType, LogicStepType.CONDITION>
|
||||||
| LogicStepType.SET_VARIABLE
|
|
||||||
| IntegrationStepType
|
| IntegrationStepType
|
||||||
|
|
||||||
export type StepOptions =
|
export type StepOptions =
|
||||||
|
|||||||
Reference in New Issue
Block a user