diff --git a/apps/builder/services/typebots/results.tsx b/apps/builder/services/typebots/results.tsx index b53219d8f..b56b07f51 100644 --- a/apps/builder/services/typebots/results.tsx +++ b/apps/builder/services/typebots/results.tsx @@ -131,7 +131,7 @@ export const convertResultsToTableData = ( ): { [key: string]: string }[] => (results ?? []).map((result) => ({ 'Submitted at': parseDateToReadable(result.createdAt), - ...[...result.answers, ...result.prefilledVariables].reduce<{ + ...[...result.answers, ...result.variables].reduce<{ [key: string]: string }>((o, answerOrVariable) => { if ('blockId' in answerOrVariable) { diff --git a/apps/viewer/layouts/TypebotPage.tsx b/apps/viewer/layouts/TypebotPage.tsx index ed6950e68..641509c12 100644 --- a/apps/viewer/layouts/TypebotPage.tsx +++ b/apps/viewer/layouts/TypebotPage.tsx @@ -54,11 +54,9 @@ export const TypebotPage = ({ setShowTypebot(true) } - const handleVariablesPrefilled = async ( - prefilledVariables: VariableWithValue[] - ) => { + const handleNewVariables = async (variables: VariableWithValue[]) => { if (!resultId) return setError(new Error('Result was not created')) - const { error } = await updateResult(resultId, { prefilledVariables }) + const { error } = await updateResult(resultId, { variables }) if (error) setError(error) } @@ -98,7 +96,7 @@ export const TypebotPage = ({ predefinedVariables={predefinedVariables} onNewAnswer={handleNewAnswer} onCompleted={handleCompleted} - onVariablesPrefilled={handleVariablesPrefilled} + onVariablesUpdated={handleNewVariables} onNewLog={handleNewLog} /> )} diff --git a/packages/bot-engine/src/components/ChatBlock/ChatBlock.tsx b/packages/bot-engine/src/components/ChatBlock/ChatBlock.tsx index b12c4d0ea..84af38c4c 100644 --- a/packages/bot-engine/src/components/ChatBlock/ChatBlock.tsx +++ b/packages/bot-engine/src/components/ChatBlock/ChatBlock.tsx @@ -49,7 +49,7 @@ export const ChatBlock = ({ injectLinkedTypebot, linkedTypebots, } = useTypebot() - const { resultValues } = useAnswers() + const { resultValues, updateVariables } = useAnswers() const [processedSteps, setProcessedSteps] = useState([]) const [displayedChunks, setDisplayedChunks] = useState([]) @@ -104,6 +104,7 @@ export const ChatBlock = ({ typebot, linkedTypebots, updateVariableValue, + updateVariables, injectLinkedTypebot, onNewLog, createEdge, @@ -121,6 +122,7 @@ export const ChatBlock = ({ variables: typebot.variables, isPreview, updateVariableValue, + updateVariables, resultValues, blocks: typebot.blocks, onNewLog, diff --git a/packages/bot-engine/src/components/ConversationContainer.tsx b/packages/bot-engine/src/components/ConversationContainer.tsx index 652971149..a1629febb 100644 --- a/packages/bot-engine/src/components/ConversationContainer.tsx +++ b/packages/bot-engine/src/components/ConversationContainer.tsx @@ -14,21 +14,19 @@ type Props = { predefinedVariables?: { [key: string]: string | undefined } onNewBlockVisible: (edge: Edge) => void onCompleted: () => void - onVariablesPrefilled?: (prefilledVariables: VariableWithValue[]) => void } export const ConversationContainer = ({ theme, predefinedVariables, onNewBlockVisible, onCompleted, - onVariablesPrefilled, }: Props) => { const { typebot, updateVariableValue } = useTypebot() const { document: frameDocument } = useFrame() const [displayedBlocks, setDisplayedBlocks] = useState< { block: Block; startStepIndex: number }[] >([]) - const { setPrefilledVariables } = useAnswers() + const { updateVariables } = useAnswers() const bottomAnchor = useRef(null) const scrollableContainer = useRef(null) @@ -53,8 +51,7 @@ export const ConversationContainer = ({ useEffect(() => { const prefilledVariables = injectPredefinedVariables(predefinedVariables) - if (onVariablesPrefilled) onVariablesPrefilled(prefilledVariables) - setPrefilledVariables(prefilledVariables) + updateVariables(prefilledVariables) displayNextBlock(typebot.blocks[0].steps[0].outgoingEdgeId) // eslint-disable-next-line react-hooks/exhaustive-deps }, []) diff --git a/packages/bot-engine/src/components/TypebotViewer.tsx b/packages/bot-engine/src/components/TypebotViewer.tsx index b7003e975..d6df92002 100644 --- a/packages/bot-engine/src/components/TypebotViewer.tsx +++ b/packages/bot-engine/src/components/TypebotViewer.tsx @@ -31,7 +31,7 @@ export type TypebotViewerProps = { onNewAnswer?: (answer: Answer) => void onNewLog?: (log: Omit) => void onCompleted?: () => void - onVariablesPrefilled?: (prefilledVariables: VariableWithValue[]) => void + onVariablesUpdated?: (variables: VariableWithValue[]) => void } export const TypebotViewer = ({ @@ -44,7 +44,7 @@ export const TypebotViewer = ({ onNewBlockVisible, onNewAnswer, onCompleted, - onVariablesPrefilled, + onVariablesUpdated, }: TypebotViewerProps) => { const containerBgColor = useMemo( () => @@ -93,7 +93,10 @@ export const TypebotViewer = ({ isPreview={isPreview} onNewLog={handleNewLog} > - +
{typebot.settings.general.isBrandingEnabled && ( diff --git a/packages/bot-engine/src/contexts/AnswersContext.tsx b/packages/bot-engine/src/contexts/AnswersContext.tsx index 81e01a179..9d9495a72 100644 --- a/packages/bot-engine/src/contexts/AnswersContext.tsx +++ b/packages/bot-engine/src/contexts/AnswersContext.tsx @@ -4,7 +4,7 @@ import React, { createContext, ReactNode, useContext, useState } from 'react' const answersContext = createContext<{ resultValues: ResultValues addAnswer: (answer: Answer) => void - setPrefilledVariables: (variables: VariableWithValue[]) => void + updateVariables: (variables: VariableWithValue[]) => void // eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-ignore }>({}) @@ -12,13 +12,15 @@ const answersContext = createContext<{ export const AnswersContext = ({ children, onNewAnswer, + onVariablesUpdated, }: { onNewAnswer: (answer: Answer) => void + onVariablesUpdated?: (variables: VariableWithValue[]) => void children: ReactNode }) => { const [resultValues, setResultValues] = useState({ answers: [], - prefilledVariables: [], + variables: [], createdAt: new Date().toISOString(), }) @@ -30,18 +32,22 @@ export const AnswersContext = ({ onNewAnswer(answer) } - const setPrefilledVariables = (variables: VariableWithValue[]) => - setResultValues((resultValues) => ({ - ...resultValues, - prefilledVariables: variables, - })) + const updateVariables = (variables: VariableWithValue[]) => + setResultValues((resultValues) => { + const updatedVariables = [...resultValues.variables, ...variables] + if (onVariablesUpdated) onVariablesUpdated(updatedVariables) + return { + ...resultValues, + variables: updatedVariables, + } + }) return ( {children} diff --git a/packages/bot-engine/src/services/integration.ts b/packages/bot-engine/src/services/integration.ts index a3e1b0bb8..0b1f24b65 100644 --- a/packages/bot-engine/src/services/integration.ts +++ b/packages/bot-engine/src/services/integration.ts @@ -15,9 +15,10 @@ import { ZapierStep, ResultValues, Block, + VariableWithValue, } from 'models' import { stringify } from 'qs' -import { sendRequest } from 'utils' +import { byId, sendRequest } from 'utils' import { sendGaEvent } from '../../lib/gtag' import { parseVariables, parseVariablesInObject } from './variable' @@ -30,6 +31,7 @@ type IntegrationContext = { variables: Variable[] resultValues: ResultValues blocks: Block[] + updateVariables: (variables: VariableWithValue[]) => void updateVariableValue: (variableId: string, value: string) => void onNewLog: (log: Omit) => void } @@ -141,7 +143,13 @@ const updateRowInGoogleSheets = async ( const getRowFromGoogleSheets = async ( options: GoogleSheetsGetOptions, - { variables, updateVariableValue, apiHost, onNewLog }: IntegrationContext + { + variables, + updateVariableValue, + updateVariables, + apiHost, + onNewLog, + }: IntegrationContext ) => { if (!options.referenceCell || !options.cellsToExtract) return const queryParams = stringify( @@ -167,9 +175,23 @@ const getRowFromGoogleSheets = async ( ) ) if (!data) return - options.cellsToExtract.forEach((cell) => - updateVariableValue(cell.variableId ?? '', data[cell.column ?? '']) + const newVariables = options.cellsToExtract.reduce( + (newVariables, cell) => { + const existingVariable = variables.find(byId(cell.variableId)) + const value = data[cell.column ?? ''] + if (!existingVariable || !value) return newVariables + updateVariableValue(existingVariable.id, value) + return [ + ...newVariables, + { + ...existingVariable, + value, + }, + ] + }, + [] ) + updateVariables(newVariables) } const parseCellValues = ( cells: Cell[], @@ -191,6 +213,7 @@ const executeWebhook = async ( stepId, variables, updateVariableValue, + updateVariables, typebotId, apiHost, resultValues, @@ -218,13 +241,19 @@ const executeWebhook = async ( : 'Webhook successfuly executed', details: JSON.stringify(error ?? data, null, 2).substring(0, 1000), }) - step.options.responseVariableMapping.forEach((varMapping) => { - if (!varMapping?.bodyPath || !varMapping.variableId) return + const newVariables = step.options.responseVariableMapping.reduce< + VariableWithValue[] + >((newVariables, varMapping) => { + if (!varMapping?.bodyPath || !varMapping.variableId) return newVariables + const existingVariable = variables.find(byId(varMapping.variableId)) + if (!existingVariable) return newVariables const value = Function( `return (${JSON.stringify(data)}).${varMapping?.bodyPath}` )() - updateVariableValue(varMapping.variableId, value) - }) + updateVariableValue(existingVariable?.id, value) + return [...newVariables, { ...existingVariable, value }] + }, []) + updateVariables(newVariables) return step.outgoingEdgeId } diff --git a/packages/bot-engine/src/services/logic.ts b/packages/bot-engine/src/services/logic.ts index c7e6075bf..98d745dde 100644 --- a/packages/bot-engine/src/services/logic.ts +++ b/packages/bot-engine/src/services/logic.ts @@ -15,6 +15,7 @@ import { PublicTypebot, Typebot, Edge, + VariableWithValue, } from 'models' import { byId, isDefined, isNotDefined, sendRequest } from 'utils' import { sanitizeUrl } from './utils' @@ -28,6 +29,7 @@ type LogicContext = { typebot: PublicTypebot linkedTypebots: LinkedTypebot[] updateVariableValue: (variableId: string, value: string) => void + updateVariables: (variables: VariableWithValue[]) => void injectLinkedTypebot: (typebot: Typebot | PublicTypebot) => LinkedTypebot onNewLog: (log: Omit) => void createEdge: (edge: Edge) => void @@ -56,7 +58,7 @@ export const executeLogic = async ( const executeSetVariable = ( step: SetVariableStep, - { typebot: { variables }, updateVariableValue }: LogicContext + { typebot: { variables }, updateVariableValue, updateVariables }: LogicContext ): EdgeId | undefined => { if (!step.options?.variableId || !step.options.expressionToEvaluate) return step.outgoingEdgeId @@ -64,7 +66,10 @@ const executeSetVariable = ( const evaluatedExpression = evaluateExpression( parseVariables(variables)(expression) ) - updateVariableValue(step.options.variableId, evaluatedExpression) + const existingVariable = variables.find(byId(step.options.variableId)) + if (!existingVariable) return step.outgoingEdgeId + updateVariableValue(existingVariable.id, evaluatedExpression) + updateVariables([{ ...existingVariable, value: evaluatedExpression }]) return step.outgoingEdgeId } diff --git a/packages/db/prisma/migrations/20220328144519_rename_prefilled_variables/migration.sql b/packages/db/prisma/migrations/20220328144519_rename_prefilled_variables/migration.sql new file mode 100644 index 000000000..c790b3c33 --- /dev/null +++ b/packages/db/prisma/migrations/20220328144519_rename_prefilled_variables/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "Result" +RENAME COLUMN "prefilledVariables" "variables" diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma index 1d2f9e8ee..36789abfd 100644 --- a/packages/db/prisma/schema.prisma +++ b/packages/db/prisma/schema.prisma @@ -56,8 +56,8 @@ model User { customDomains CustomDomain[] apiToken String? CollaboratorsOnTypebots CollaboratorsOnTypebots[] - company String? - onboardingCategories String[] + company String? + onboardingCategories String[] } model CustomDomain { @@ -178,22 +178,22 @@ model PublicTypebot { } model Result { - id String @id @default(cuid()) - createdAt DateTime @default(now()) - updatedAt DateTime @default(now()) @updatedAt - typebotId String - typebot Typebot @relation(fields: [typebotId], references: [id], onDelete: Cascade) - answers Answer[] - prefilledVariables Json[] - isCompleted Boolean - logs Log[] + id String @id @default(cuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) @updatedAt + typebotId String + typebot Typebot @relation(fields: [typebotId], references: [id], onDelete: Cascade) + answers Answer[] + variables Json[] + isCompleted Boolean + logs Log[] } model Log { - id String @id @default(cuid()) - createdAt DateTime @default(now()) + id String @id @default(cuid()) + createdAt DateTime @default(now()) resultId String - result Result @relation(fields: [resultId], references: [id], onDelete: Cascade) + result Result @relation(fields: [resultId], references: [id], onDelete: Cascade) status String description String details String? diff --git a/packages/models/src/result.ts b/packages/models/src/result.ts index 8acd6c799..1e4829f62 100644 --- a/packages/models/src/result.ts +++ b/packages/models/src/result.ts @@ -1,16 +1,16 @@ import { Result as ResultFromPrisma } from 'db' import { Answer, InputStepType, VariableWithValue } from '.' -export type Result = Omit< - ResultFromPrisma, - 'createdAt' | 'prefilledVariables' -> & { createdAt: string; prefilledVariables: VariableWithValue[] } +export type Result = Omit & { + createdAt: string + variables: VariableWithValue[] +} export type ResultWithAnswers = Result & { answers: Answer[] } export type ResultValues = Pick< ResultWithAnswers, - 'answers' | 'createdAt' | 'prefilledVariables' + 'answers' | 'createdAt' | 'variables' > export type ResultHeaderCell = { diff --git a/packages/utils/src/results.ts b/packages/utils/src/results.ts index ad83acadd..5d90f967d 100644 --- a/packages/utils/src/results.ts +++ b/packages/utils/src/results.ts @@ -91,14 +91,14 @@ export const parseAnswers = ({ createdAt, answers, - prefilledVariables, - }: Pick): { + variables: resultVariables, + }: Pick): { [key: string]: string } => { const header = parseResultHeader({ blocks, variables }) return { submittedAt: createdAt, - ...[...answers, ...prefilledVariables].reduce<{ + ...[...answers, ...resultVariables].reduce<{ [key: string]: string }>((o, answerOrVariable) => { if ('blockId' in answerOrVariable) {