Skip to content

Commit

Permalink
feat(editor): ✨ Code step
Browse files Browse the repository at this point in the history
  • Loading branch information
baptisteArno committed Mar 7, 2022
1 parent b2784f1 commit e3e07dd
Show file tree
Hide file tree
Showing 13 changed files with 239 additions and 16 deletions.
3 changes: 3 additions & 0 deletions apps/builder/components/editor/StepsSideBar/StepIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
CalendarIcon,
ChatIcon,
CheckSquareIcon,
CodeIcon,
EditIcon,
EmailIcon,
ExternalLinkIcon,
Expand Down Expand Up @@ -57,6 +58,8 @@ export const StepIcon = ({ type, ...props }: StepIconProps) => {
return <FilterIcon color="purple.500" {...props} />
case LogicStepType.REDIRECT:
return <ExternalLinkIcon color="purple.500" {...props} />
case LogicStepType.CODE:
return <CodeIcon color="purple.500" {...props} />
case IntegrationStepType.GOOGLE_SHEETS:
return <GoogleSheetsLogo {...props} />
case IntegrationStepType.GOOGLE_ANALYTICS:
Expand Down
6 changes: 6 additions & 0 deletions apps/builder/components/editor/StepsSideBar/StepTypeLabel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ export const StepTypeLabel = ({ type }: Props) => {
return <Text>Condition</Text>
case LogicStepType.REDIRECT:
return <Text>Redirect</Text>
case LogicStepType.CODE:
return (
<Tooltip label="Run Javascript code">
<Text>Code</Text>
</Tooltip>
)
case IntegrationStepType.GOOGLE_SHEETS:
return (
<Tooltip label="Google Sheets">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
DateInputSettingsBody,
} from './bodies'
import { ChoiceInputSettingsBody } from './bodies/ChoiceInputSettingsBody'
import { CodeSettings } from './bodies/CodeSettings'
import { ConditionSettingsBody } from './bodies/ConditionSettingsBody'
import { GoogleAnalyticsSettings } from './bodies/GoogleAnalyticsSettings'
import { GoogleSheetsSettingsBody } from './bodies/GoogleSheetsSettingsBody'
Expand Down Expand Up @@ -177,6 +178,14 @@ export const StepSettings = ({
/>
)
}
case LogicStepType.CODE: {
return (
<CodeSettings
options={step.options}
onOptionsChange={handleOptionsChange}
/>
)
}
case IntegrationStepType.GOOGLE_SHEETS: {
return (
<GoogleSheetsSettingsBody
Expand Down
Original file line number Diff line number Diff line change
@@ -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>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
return (
<ConfigureContent
Expand Down
101 changes: 101 additions & 0 deletions apps/builder/playwright/fixtures/typebots/logic/code.json
Original file line number Diff line number Diff line change
@@ -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 changes: 29 additions & 0 deletions apps/builder/playwright/tests/logic/code.spec.ts
Original file line number Diff line number Diff line change
@@ -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')
})
})
3 changes: 3 additions & 0 deletions apps/builder/services/typebots/typebots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
defaultRedirectOptions,
defaultGoogleSheetsOptions,
defaultGoogleAnalyticsOptions,
defaultCodeOptions,
defaultWebhookOptions,
StepWithOptionsType,
Item,
Expand Down Expand Up @@ -226,6 +227,8 @@ const parseDefaultStepOptions = (type: StepWithOptionsType): StepOptions => {
return defaultSetVariablesOptions
case LogicStepType.REDIRECT:
return defaultRedirectOptions
case LogicStepType.CODE:
return defaultCodeOptions
case IntegrationStepType.GOOGLE_SHEETS:
return defaultGoogleSheetsOptions
case IntegrationStepType.GOOGLE_ANALYTICS:
Expand Down
6 changes: 3 additions & 3 deletions packages/bot-engine/src/services/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ import { sendRequest } from 'utils'
import { sendGaEvent } from '../../lib/gtag'
import { parseVariables, parseVariablesInObject } from './variable'

const safeEval = eval

type IntegrationContext = {
apiHost: string
typebotId: string
Expand Down Expand Up @@ -222,7 +220,9 @@ const executeWebhook = async (
})
step.options.responseVariableMapping.forEach((varMapping) => {
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)
})
return step.outgoingEdgeId
Expand Down
9 changes: 9 additions & 0 deletions packages/bot-engine/src/services/logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
SetVariableStep,
RedirectStep,
Comparison,
CodeStep,
} from 'models'
import { isDefined, isNotDefined } from 'utils'
import { sanitizeUrl } from './utils'
Expand All @@ -27,6 +28,8 @@ export const executeLogic = (
return executeCondition(step, variables)
case LogicStepType.REDIRECT:
return executeRedirect(step, variables)
case LogicStepType.CODE:
return executeCode(step)
}
}

Expand Down Expand Up @@ -97,3 +100,9 @@ const executeRedirect = (
)
return step.outgoingEdgeId
}

const executeCode = (step: CodeStep) => {
if (!step.options.content) return
Function(step.options.content)()
return step.outgoingEdgeId
}
4 changes: 1 addition & 3 deletions packages/bot-engine/src/services/variable.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { Variable } from 'models'
import { isDefined, isNotDefined } from 'utils'

const safeEval = eval

export const stringContainsVariable = (str: string): boolean =>
/\{\{(.*?)\}\}/g.test(str)

Expand All @@ -22,7 +20,7 @@ export const parseVariables =

export const evaluateExpression = (str: string) => {
try {
const evaluatedResult = safeEval(str)
const evaluatedResult = Function('return' + str)()
return isNotDefined(evaluatedResult) ? '' : evaluatedResult.toString()
} catch (err) {
console.log(err)
Expand Down
26 changes: 23 additions & 3 deletions packages/models/src/typebot/steps/logic.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
import { ItemType, StepBase } from '.'
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 {
SET_VARIABLE = 'Set variable',
CONDITION = 'Condition',
REDIRECT = 'Redirect',
CODE = 'Code',
}

export type LogicStepOptions = SetVariableOptions | RedirectOptions

export type SetVariableStep = StepBase & {
type: LogicStepType.SET_VARIABLE
options: SetVariableOptions
Expand All @@ -31,6 +39,11 @@ export type RedirectStep = StepBase & {
options: RedirectOptions
}

export type CodeStep = StepBase & {
type: LogicStepType.CODE
options: CodeOptions
}

export enum LogicalOperator {
OR = 'OR',
AND = 'AND',
Expand Down Expand Up @@ -67,6 +80,11 @@ export type RedirectOptions = {
isNewTab: boolean
}

export type CodeOptions = {
name: string
content?: string
}

export const defaultSetVariablesOptions: SetVariableOptions = {}

export const defaultConditionContent: ConditionContent = {
Expand All @@ -75,3 +93,5 @@ export const defaultConditionContent: ConditionContent = {
}

export const defaultRedirectOptions: RedirectOptions = { isNewTab: false }

export const defaultCodeOptions: CodeOptions = { name: 'Code snippet' }
Loading

2 comments on commit e3e07dd

@vercel
Copy link

@vercel vercel bot commented on e3e07dd Mar 7, 2022

@vercel
Copy link

@vercel vercel bot commented on e3e07dd Mar 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

builder-v2 – ./apps/builder

builder-v2-git-main-typebot-io.vercel.app
app.typebot.io
builder-v2-typebot-io.vercel.app

Please sign in to comment.