refactor: ♻️ Rename step to block

This commit is contained in:
Baptiste Arnaud
2022-06-11 07:27:38 +02:00
parent 8751766d0e
commit 2df8338505
297 changed files with 4292 additions and 3989 deletions

View File

@@ -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
}

View File

@@ -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: [],

View File

@@ -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 = (

View File

@@ -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)