refactor: ♻️ Rename step to block
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
import {
|
||||
BubbleStep,
|
||||
BubbleStepType,
|
||||
InputStep,
|
||||
InputStepType,
|
||||
Step,
|
||||
BubbleBlock,
|
||||
BubbleBlockType,
|
||||
InputBlock,
|
||||
InputBlockType,
|
||||
Block,
|
||||
TypingEmulation,
|
||||
} from 'models'
|
||||
import { isBubbleStep, isInputStep } from 'utils'
|
||||
import { isBubbleBlock, isInputBlock } from 'utils'
|
||||
|
||||
export const computeTypingTimeout = (
|
||||
bubbleContent: string,
|
||||
@@ -23,11 +23,11 @@ export const computeTypingTimeout = (
|
||||
return typingTimeout
|
||||
}
|
||||
|
||||
export const getLastChatStepType = (
|
||||
steps: Step[]
|
||||
): BubbleStepType | InputStepType | undefined => {
|
||||
const displayedSteps = steps.filter(
|
||||
(s) => isBubbleStep(s) || isInputStep(s)
|
||||
) as (BubbleStep | InputStep)[]
|
||||
return displayedSteps.pop()?.type
|
||||
export const getLastChatBlockType = (
|
||||
blocks: Block[]
|
||||
): BubbleBlockType | InputBlockType | undefined => {
|
||||
const displayedBlocks = blocks.filter(
|
||||
(s) => isBubbleBlock(s) || isInputBlock(s)
|
||||
) as (BubbleBlock | InputBlock)[]
|
||||
return displayedBlocks.pop()?.type
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import {
|
||||
BubbleStep,
|
||||
BubbleStepType,
|
||||
BubbleBlock,
|
||||
BubbleBlockType,
|
||||
Edge,
|
||||
EmailInputStep,
|
||||
InputStepType,
|
||||
PhoneNumberInputStep,
|
||||
Step,
|
||||
UrlInputStep,
|
||||
EmailInputBlock,
|
||||
InputBlockType,
|
||||
PhoneNumberInputBlock,
|
||||
Block,
|
||||
UrlInputBlock,
|
||||
Variable,
|
||||
} from 'models'
|
||||
import { isPossiblePhoneNumber } from 'react-phone-number-input'
|
||||
import { isInputStep } from 'utils'
|
||||
import { isInputBlock } from 'utils'
|
||||
import { parseVariables } from './variable'
|
||||
|
||||
const emailRegex =
|
||||
@@ -20,41 +20,41 @@ const urlRegex =
|
||||
|
||||
export const isInputValid = (
|
||||
inputValue: string,
|
||||
type: InputStepType
|
||||
type: InputBlockType
|
||||
): boolean => {
|
||||
switch (type) {
|
||||
case InputStepType.EMAIL:
|
||||
case InputBlockType.EMAIL:
|
||||
return emailRegex.test(inputValue)
|
||||
case InputStepType.PHONE:
|
||||
case InputBlockType.PHONE:
|
||||
return isPossiblePhoneNumber(inputValue)
|
||||
case InputStepType.URL:
|
||||
case InputBlockType.URL:
|
||||
return urlRegex.test(inputValue)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export const stepCanBeRetried = (
|
||||
step: Step
|
||||
): step is EmailInputStep | UrlInputStep | PhoneNumberInputStep =>
|
||||
isInputStep(step) && 'retryMessageContent' in step.options
|
||||
export const blockCanBeRetried = (
|
||||
block: Block
|
||||
): block is EmailInputBlock | UrlInputBlock | PhoneNumberInputBlock =>
|
||||
isInputBlock(block) && 'retryMessageContent' in block.options
|
||||
|
||||
export const parseRetryStep = (
|
||||
step: EmailInputStep | UrlInputStep | PhoneNumberInputStep,
|
||||
export const parseRetryBlock = (
|
||||
block: EmailInputBlock | UrlInputBlock | PhoneNumberInputBlock,
|
||||
variables: Variable[],
|
||||
createEdge: (edge: Edge) => void
|
||||
): BubbleStep => {
|
||||
const content = parseVariables(variables)(step.options.retryMessageContent)
|
||||
const newStepId = step.id + Math.random() * 1000
|
||||
): BubbleBlock => {
|
||||
const content = parseVariables(variables)(block.options.retryMessageContent)
|
||||
const newBlockId = block.id + Math.random() * 1000
|
||||
const newEdge: Edge = {
|
||||
id: (Math.random() * 1000).toString(),
|
||||
from: { stepId: newStepId, blockId: step.blockId },
|
||||
to: { blockId: step.blockId, stepId: step.id },
|
||||
from: { blockId: newBlockId, groupId: block.groupId },
|
||||
to: { groupId: block.groupId, blockId: block.id },
|
||||
}
|
||||
createEdge(newEdge)
|
||||
return {
|
||||
blockId: step.blockId,
|
||||
id: newStepId,
|
||||
type: BubbleStepType.TEXT,
|
||||
groupId: block.groupId,
|
||||
id: newBlockId,
|
||||
type: BubbleBlockType.TEXT,
|
||||
content: {
|
||||
html: `<div>${content}</div>`,
|
||||
richText: [],
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import { Log } from 'db'
|
||||
import {
|
||||
IntegrationStep,
|
||||
IntegrationStepType,
|
||||
GoogleSheetsStep,
|
||||
IntegrationBlock,
|
||||
IntegrationBlockType,
|
||||
GoogleSheetsBlock,
|
||||
GoogleSheetsAction,
|
||||
GoogleSheetsInsertRowOptions,
|
||||
Variable,
|
||||
GoogleSheetsUpdateRowOptions,
|
||||
Cell,
|
||||
GoogleSheetsGetOptions,
|
||||
GoogleAnalyticsStep,
|
||||
WebhookStep,
|
||||
SendEmailStep,
|
||||
ZapierStep,
|
||||
GoogleAnalyticsBlock,
|
||||
WebhookBlock,
|
||||
SendEmailBlock,
|
||||
ZapierBlock,
|
||||
ResultValues,
|
||||
Block,
|
||||
Group,
|
||||
VariableWithValue,
|
||||
MakeComStep,
|
||||
PabblyConnectStep,
|
||||
MakeComBlock,
|
||||
PabblyConnectBlock,
|
||||
} from 'models'
|
||||
import { stringify } from 'qs'
|
||||
import { byId, sendRequest } from 'utils'
|
||||
@@ -27,12 +27,12 @@ import { parseVariables, parseVariablesInObject } from './variable'
|
||||
type IntegrationContext = {
|
||||
apiHost: string
|
||||
typebotId: string
|
||||
groupId: string
|
||||
blockId: string
|
||||
stepId: string
|
||||
isPreview: boolean
|
||||
variables: Variable[]
|
||||
resultValues: ResultValues
|
||||
blocks: Block[]
|
||||
groups: Group[]
|
||||
resultId?: string
|
||||
updateVariables: (variables: VariableWithValue[]) => void
|
||||
updateVariableValue: (variableId: string, value: string) => void
|
||||
@@ -40,55 +40,55 @@ type IntegrationContext = {
|
||||
}
|
||||
|
||||
export const executeIntegration = ({
|
||||
step,
|
||||
block,
|
||||
context,
|
||||
}: {
|
||||
step: IntegrationStep
|
||||
block: IntegrationBlock
|
||||
context: IntegrationContext
|
||||
}): Promise<string | undefined> => {
|
||||
switch (step.type) {
|
||||
case IntegrationStepType.GOOGLE_SHEETS:
|
||||
return executeGoogleSheetIntegration(step, context)
|
||||
case IntegrationStepType.GOOGLE_ANALYTICS:
|
||||
return executeGoogleAnalyticsIntegration(step, context)
|
||||
case IntegrationStepType.ZAPIER:
|
||||
case IntegrationStepType.MAKE_COM:
|
||||
case IntegrationStepType.PABBLY_CONNECT:
|
||||
case IntegrationStepType.WEBHOOK:
|
||||
return executeWebhook(step, context)
|
||||
case IntegrationStepType.EMAIL:
|
||||
return sendEmail(step, context)
|
||||
switch (block.type) {
|
||||
case IntegrationBlockType.GOOGLE_SHEETS:
|
||||
return executeGoogleSheetIntegration(block, context)
|
||||
case IntegrationBlockType.GOOGLE_ANALYTICS:
|
||||
return executeGoogleAnalyticsIntegration(block, context)
|
||||
case IntegrationBlockType.ZAPIER:
|
||||
case IntegrationBlockType.MAKE_COM:
|
||||
case IntegrationBlockType.PABBLY_CONNECT:
|
||||
case IntegrationBlockType.WEBHOOK:
|
||||
return executeWebhook(block, context)
|
||||
case IntegrationBlockType.EMAIL:
|
||||
return sendEmail(block, context)
|
||||
}
|
||||
}
|
||||
|
||||
export const executeGoogleAnalyticsIntegration = async (
|
||||
step: GoogleAnalyticsStep,
|
||||
block: GoogleAnalyticsBlock,
|
||||
{ variables }: IntegrationContext
|
||||
) => {
|
||||
if (!step.options?.trackingId) return step.outgoingEdgeId
|
||||
if (!block.options?.trackingId) return block.outgoingEdgeId
|
||||
const { default: initGoogleAnalytics } = await import('../../lib/gtag')
|
||||
await initGoogleAnalytics(step.options.trackingId)
|
||||
sendGaEvent(parseVariablesInObject(step.options, variables))
|
||||
return step.outgoingEdgeId
|
||||
await initGoogleAnalytics(block.options.trackingId)
|
||||
sendGaEvent(parseVariablesInObject(block.options, variables))
|
||||
return block.outgoingEdgeId
|
||||
}
|
||||
|
||||
const executeGoogleSheetIntegration = async (
|
||||
step: GoogleSheetsStep,
|
||||
block: GoogleSheetsBlock,
|
||||
context: IntegrationContext
|
||||
) => {
|
||||
if (!('action' in step.options)) return step.outgoingEdgeId
|
||||
switch (step.options.action) {
|
||||
if (!('action' in block.options)) return block.outgoingEdgeId
|
||||
switch (block.options.action) {
|
||||
case GoogleSheetsAction.INSERT_ROW:
|
||||
await insertRowInGoogleSheets(step.options, context)
|
||||
await insertRowInGoogleSheets(block.options, context)
|
||||
break
|
||||
case GoogleSheetsAction.UPDATE_ROW:
|
||||
await updateRowInGoogleSheets(step.options, context)
|
||||
await updateRowInGoogleSheets(block.options, context)
|
||||
break
|
||||
case GoogleSheetsAction.GET:
|
||||
await getRowFromGoogleSheets(step.options, context)
|
||||
await getRowFromGoogleSheets(block.options, context)
|
||||
break
|
||||
}
|
||||
return step.outgoingEdgeId
|
||||
return block.outgoingEdgeId
|
||||
}
|
||||
|
||||
const insertRowInGoogleSheets = async (
|
||||
@@ -216,10 +216,9 @@ const parseCellValues = (
|
||||
}, {})
|
||||
|
||||
const executeWebhook = async (
|
||||
step: WebhookStep | ZapierStep | MakeComStep | PabblyConnectStep,
|
||||
block: WebhookBlock | ZapierBlock | MakeComBlock | PabblyConnectBlock,
|
||||
{
|
||||
blockId,
|
||||
stepId,
|
||||
variables,
|
||||
updateVariableValue,
|
||||
updateVariables,
|
||||
@@ -232,7 +231,7 @@ const executeWebhook = async (
|
||||
) => {
|
||||
const params = stringify({ resultId })
|
||||
const { data, error } = await sendRequest({
|
||||
url: `${apiHost}/api/typebots/${typebotId}/blocks/${blockId}/steps/${stepId}/executeWebhook?${params}`,
|
||||
url: `${apiHost}/api/typebots/${typebotId}/blocks/${blockId}/executeWebhook?${params}`,
|
||||
method: 'POST',
|
||||
body: {
|
||||
variables,
|
||||
@@ -252,7 +251,7 @@ const executeWebhook = async (
|
||||
: 'Webhook successfuly executed',
|
||||
details: JSON.stringify(error ?? data, null, 2).substring(0, 1000),
|
||||
})
|
||||
const newVariables = step.options.responseVariableMapping.reduce<
|
||||
const newVariables = block.options.responseVariableMapping.reduce<
|
||||
VariableWithValue[]
|
||||
>((newVariables, varMapping) => {
|
||||
if (!varMapping?.bodyPath || !varMapping.variableId) return newVariables
|
||||
@@ -271,11 +270,11 @@ const executeWebhook = async (
|
||||
}
|
||||
}, [])
|
||||
updateVariables(newVariables)
|
||||
return step.outgoingEdgeId
|
||||
return block.outgoingEdgeId
|
||||
}
|
||||
|
||||
const sendEmail = async (
|
||||
step: SendEmailStep,
|
||||
block: SendEmailBlock,
|
||||
{ variables, apiHost, isPreview, onNewLog, resultId }: IntegrationContext
|
||||
) => {
|
||||
if (isPreview) {
|
||||
@@ -284,9 +283,9 @@ const sendEmail = async (
|
||||
description: 'Emails are not sent in preview mode',
|
||||
details: null,
|
||||
})
|
||||
return step.outgoingEdgeId
|
||||
return block.outgoingEdgeId
|
||||
}
|
||||
const { options } = step
|
||||
const { options } = block
|
||||
const replyTo = parseVariables(variables)(options.replyTo)
|
||||
const { error } = await sendRequest({
|
||||
url: `${apiHost}/api/integrations/email?resultId=${resultId}`,
|
||||
@@ -304,7 +303,7 @@ const sendEmail = async (
|
||||
onNewLog(
|
||||
parseLog(error, 'Succesfully sent an email', 'Failed to send an email')
|
||||
)
|
||||
return step.outgoingEdgeId
|
||||
return block.outgoingEdgeId
|
||||
}
|
||||
|
||||
const parseLog = (
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { LinkedTypebot } from 'contexts/TypebotContext'
|
||||
import { Log } from 'db'
|
||||
import {
|
||||
LogicStep,
|
||||
LogicStepType,
|
||||
LogicBlock,
|
||||
LogicBlockType,
|
||||
LogicalOperator,
|
||||
ConditionStep,
|
||||
ConditionBlock,
|
||||
Variable,
|
||||
ComparisonOperators,
|
||||
SetVariableStep,
|
||||
RedirectStep,
|
||||
SetVariableBlock,
|
||||
RedirectBlock,
|
||||
Comparison,
|
||||
CodeStep,
|
||||
TypebotLinkStep,
|
||||
CodeBlock,
|
||||
TypebotLinkBlock,
|
||||
PublicTypebot,
|
||||
Typebot,
|
||||
Edge,
|
||||
@@ -42,51 +42,53 @@ type LogicContext = {
|
||||
}
|
||||
|
||||
export const executeLogic = async (
|
||||
step: LogicStep,
|
||||
block: LogicBlock,
|
||||
context: LogicContext
|
||||
): Promise<{
|
||||
nextEdgeId?: EdgeId
|
||||
linkedTypebot?: PublicTypebot | LinkedTypebot
|
||||
}> => {
|
||||
switch (step.type) {
|
||||
case LogicStepType.SET_VARIABLE:
|
||||
return { nextEdgeId: executeSetVariable(step, context) }
|
||||
case LogicStepType.CONDITION:
|
||||
return { nextEdgeId: executeCondition(step, context) }
|
||||
case LogicStepType.REDIRECT:
|
||||
return { nextEdgeId: executeRedirect(step, context) }
|
||||
case LogicStepType.CODE:
|
||||
return { nextEdgeId: await executeCode(step, context) }
|
||||
case LogicStepType.TYPEBOT_LINK:
|
||||
return await executeTypebotLink(step, context)
|
||||
switch (block.type) {
|
||||
case LogicBlockType.SET_VARIABLE:
|
||||
return { nextEdgeId: executeSetVariable(block, context) }
|
||||
case LogicBlockType.CONDITION:
|
||||
return { nextEdgeId: executeCondition(block, context) }
|
||||
case LogicBlockType.REDIRECT:
|
||||
return { nextEdgeId: executeRedirect(block, context) }
|
||||
case LogicBlockType.CODE:
|
||||
return { nextEdgeId: await executeCode(block, context) }
|
||||
case LogicBlockType.TYPEBOT_LINK:
|
||||
return await executeTypebotLink(block, context)
|
||||
}
|
||||
}
|
||||
|
||||
const executeSetVariable = (
|
||||
step: SetVariableStep,
|
||||
block: SetVariableBlock,
|
||||
{ typebot: { variables }, updateVariableValue, updateVariables }: LogicContext
|
||||
): EdgeId | undefined => {
|
||||
if (!step.options?.variableId) return step.outgoingEdgeId
|
||||
const evaluatedExpression = step.options.expressionToEvaluate
|
||||
? evaluateExpression(variables)(step.options.expressionToEvaluate)
|
||||
if (!block.options?.variableId) return block.outgoingEdgeId
|
||||
const evaluatedExpression = block.options.expressionToEvaluate
|
||||
? evaluateExpression(variables)(block.options.expressionToEvaluate)
|
||||
: undefined
|
||||
const existingVariable = variables.find(byId(step.options.variableId))
|
||||
if (!existingVariable) return step.outgoingEdgeId
|
||||
const existingVariable = variables.find(byId(block.options.variableId))
|
||||
if (!existingVariable) return block.outgoingEdgeId
|
||||
updateVariableValue(existingVariable.id, evaluatedExpression)
|
||||
updateVariables([{ ...existingVariable, value: evaluatedExpression }])
|
||||
return step.outgoingEdgeId
|
||||
return block.outgoingEdgeId
|
||||
}
|
||||
|
||||
const executeCondition = (
|
||||
step: ConditionStep,
|
||||
block: ConditionBlock,
|
||||
{ typebot: { variables } }: LogicContext
|
||||
): EdgeId | undefined => {
|
||||
const { content } = step.items[0]
|
||||
const { content } = block.items[0]
|
||||
const isConditionPassed =
|
||||
content.logicalOperator === LogicalOperator.AND
|
||||
? content.comparisons.every(executeComparison(variables))
|
||||
: content.comparisons.some(executeComparison(variables))
|
||||
return isConditionPassed ? step.items[0].outgoingEdgeId : step.outgoingEdgeId
|
||||
return isConditionPassed
|
||||
? block.items[0].outgoingEdgeId
|
||||
: block.outgoingEdgeId
|
||||
}
|
||||
|
||||
const executeComparison =
|
||||
@@ -122,14 +124,14 @@ const executeComparison =
|
||||
}
|
||||
|
||||
const executeRedirect = (
|
||||
step: RedirectStep,
|
||||
block: RedirectBlock,
|
||||
{ typebot: { variables } }: LogicContext
|
||||
): EdgeId | undefined => {
|
||||
if (!step.options?.url) return step.outgoingEdgeId
|
||||
const formattedUrl = sanitizeUrl(parseVariables(variables)(step.options.url))
|
||||
if (!block.options?.url) return block.outgoingEdgeId
|
||||
const formattedUrl = sanitizeUrl(parseVariables(variables)(block.options.url))
|
||||
const isEmbedded = window.parent && window.location !== window.top?.location
|
||||
if (isEmbedded) {
|
||||
if (!step.options.isNewTab)
|
||||
if (!block.options.isNewTab)
|
||||
return ((window.top as Window).location.href = formattedUrl)
|
||||
|
||||
try {
|
||||
@@ -145,30 +147,30 @@ const executeRedirect = (
|
||||
)
|
||||
}
|
||||
} else {
|
||||
window.open(formattedUrl, step.options.isNewTab ? '_blank' : '_self')
|
||||
window.open(formattedUrl, block.options.isNewTab ? '_blank' : '_self')
|
||||
}
|
||||
return step.outgoingEdgeId
|
||||
return block.outgoingEdgeId
|
||||
}
|
||||
|
||||
const executeCode = async (
|
||||
step: CodeStep,
|
||||
block: CodeBlock,
|
||||
{ typebot: { variables } }: LogicContext
|
||||
) => {
|
||||
if (!step.options.content) return
|
||||
if (!block.options.content) return
|
||||
const func = Function(
|
||||
...variables.map((v) => v.id),
|
||||
parseVariables(variables, { fieldToParse: 'id' })(step.options.content)
|
||||
parseVariables(variables, { fieldToParse: 'id' })(block.options.content)
|
||||
)
|
||||
try {
|
||||
await func(...variables.map((v) => v.value))
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
return step.outgoingEdgeId
|
||||
return block.outgoingEdgeId
|
||||
}
|
||||
|
||||
const executeTypebotLink = async (
|
||||
step: TypebotLinkStep,
|
||||
block: TypebotLinkBlock,
|
||||
context: LogicContext
|
||||
): Promise<{
|
||||
nextEdgeId?: EdgeId
|
||||
@@ -184,10 +186,10 @@ const executeTypebotLink = async (
|
||||
currentTypebotId,
|
||||
} = context
|
||||
const linkedTypebot = (
|
||||
step.options.typebotId === 'current'
|
||||
block.options.typebotId === 'current'
|
||||
? typebot
|
||||
: [typebot, ...linkedTypebots].find(byId(step.options.typebotId)) ??
|
||||
(await fetchAndInjectTypebot(step, context))
|
||||
: [typebot, ...linkedTypebots].find(byId(block.options.typebotId)) ??
|
||||
(await fetchAndInjectTypebot(block, context))
|
||||
) as PublicTypebot | LinkedTypebot | undefined
|
||||
if (!linkedTypebot) {
|
||||
onNewLog({
|
||||
@@ -195,26 +197,26 @@ const executeTypebotLink = async (
|
||||
description: 'Failed to link typebot',
|
||||
details: '',
|
||||
})
|
||||
return { nextEdgeId: step.outgoingEdgeId }
|
||||
return { nextEdgeId: block.outgoingEdgeId }
|
||||
}
|
||||
if (step.outgoingEdgeId)
|
||||
if (block.outgoingEdgeId)
|
||||
pushEdgeIdInLinkedTypebotQueue({
|
||||
edgeId: step.outgoingEdgeId,
|
||||
edgeId: block.outgoingEdgeId,
|
||||
typebotId: currentTypebotId,
|
||||
})
|
||||
setCurrentTypebotId(
|
||||
'typebotId' in linkedTypebot ? linkedTypebot.typebotId : linkedTypebot.id
|
||||
)
|
||||
const nextBlockId =
|
||||
step.options.blockId ??
|
||||
linkedTypebot.blocks.find((b) => b.steps.some((s) => s.type === 'start'))
|
||||
const nextGroupId =
|
||||
block.options.groupId ??
|
||||
linkedTypebot.groups.find((b) => b.blocks.some((s) => s.type === 'start'))
|
||||
?.id
|
||||
if (!nextBlockId) return { nextEdgeId: step.outgoingEdgeId }
|
||||
if (!nextGroupId) return { nextEdgeId: block.outgoingEdgeId }
|
||||
const newEdge: Edge = {
|
||||
id: (Math.random() * 1000).toString(),
|
||||
from: { stepId: '', blockId: '' },
|
||||
from: { blockId: '', groupId: '' },
|
||||
to: {
|
||||
blockId: nextBlockId,
|
||||
groupId: nextGroupId,
|
||||
},
|
||||
}
|
||||
createEdge(newEdge)
|
||||
@@ -228,15 +230,15 @@ const executeTypebotLink = async (
|
||||
}
|
||||
|
||||
const fetchAndInjectTypebot = async (
|
||||
step: TypebotLinkStep,
|
||||
block: TypebotLinkBlock,
|
||||
{ apiHost, injectLinkedTypebot, isPreview }: LogicContext
|
||||
): Promise<LinkedTypebot | undefined> => {
|
||||
const { data, error } = isPreview
|
||||
? await sendRequest<{ typebot: Typebot }>(
|
||||
`/api/typebots/${step.options.typebotId}`
|
||||
`/api/typebots/${block.options.typebotId}`
|
||||
)
|
||||
: await sendRequest<{ typebot: PublicTypebot }>(
|
||||
`${apiHost}/api/publicTypebots/${step.options.typebotId}`
|
||||
`${apiHost}/api/publicTypebots/${block.options.typebotId}`
|
||||
)
|
||||
if (!data || error) return
|
||||
return injectLinkedTypebot(data.typebot)
|
||||
|
||||
Reference in New Issue
Block a user