feat(results): ⚡️ Improve logs details
This commit is contained in:
@ -51,7 +51,7 @@ export const UserContext = ({ children }: { children: ReactNode }) => {
|
|||||||
if (isDefined(user) || isNotDefined(session)) return
|
if (isDefined(user) || isNotDefined(session)) return
|
||||||
const parsedUser = session.user as User
|
const parsedUser = session.user as User
|
||||||
setUser(parsedUser)
|
setUser(parsedUser)
|
||||||
setSentryUser({ id: parsedUser.id })
|
if (parsedUser?.id) setSentryUser({ id: parsedUser.id })
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [session])
|
}, [session])
|
||||||
|
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import { TypebotViewer } from 'bot-engine'
|
import { TypebotViewer } from 'bot-engine'
|
||||||
import { Log } from 'db'
|
|
||||||
import { Answer, PublicTypebot, VariableWithValue } from 'models'
|
import { Answer, PublicTypebot, VariableWithValue } from 'models'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { upsertAnswer } from 'services/answer'
|
import { upsertAnswer } from 'services/answer'
|
||||||
import { SEO } from '../components/Seo'
|
import { SEO } from '../components/Seo'
|
||||||
import { createLog, createResult, updateResult } from '../services/result'
|
import { createResult, updateResult } from '../services/result'
|
||||||
import { ErrorPage } from './ErrorPage'
|
import { ErrorPage } from './ErrorPage'
|
||||||
|
|
||||||
export type TypebotPageProps = {
|
export type TypebotPageProps = {
|
||||||
@ -74,14 +73,6 @@ export const TypebotPage = ({
|
|||||||
if (error) setError(error)
|
if (error) setError(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleNewLog = async (
|
|
||||||
log: Omit<Log, 'id' | 'createdAt' | 'resultId'>
|
|
||||||
) => {
|
|
||||||
if (!resultId) return setError(new Error('Result was not created'))
|
|
||||||
const { error } = await createLog(resultId, log)
|
|
||||||
if (error) setError(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return <ErrorPage error={error} />
|
return <ErrorPage error={error} />
|
||||||
}
|
}
|
||||||
@ -95,11 +86,11 @@ export const TypebotPage = ({
|
|||||||
{showTypebot && (
|
{showTypebot && (
|
||||||
<TypebotViewer
|
<TypebotViewer
|
||||||
typebot={typebot}
|
typebot={typebot}
|
||||||
|
resultId={resultId}
|
||||||
predefinedVariables={predefinedVariables}
|
predefinedVariables={predefinedVariables}
|
||||||
onNewAnswer={handleNewAnswer}
|
onNewAnswer={handleNewAnswer}
|
||||||
onCompleted={handleCompleted}
|
onCompleted={handleCompleted}
|
||||||
onVariablesUpdated={handleNewVariables}
|
onVariablesUpdated={handleNewVariables}
|
||||||
onNewLog={handleNewLog}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,10 +2,11 @@ import prisma from 'libs/prisma'
|
|||||||
import { SendEmailOptions, SmtpCredentialsData } from 'models'
|
import { SendEmailOptions, SmtpCredentialsData } from 'models'
|
||||||
import { NextApiRequest, NextApiResponse } from 'next'
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
import { createTransport, getTestMessageUrl } from 'nodemailer'
|
import { createTransport, getTestMessageUrl } from 'nodemailer'
|
||||||
import { decrypt, initMiddleware } from 'utils'
|
import { decrypt, initMiddleware, methodNotAllowed } from 'utils'
|
||||||
|
|
||||||
import Cors from 'cors'
|
import Cors from 'cors'
|
||||||
import { withSentry } from '@sentry/nextjs'
|
import { withSentry } from '@sentry/nextjs'
|
||||||
|
import { saveErrorLog, saveSuccessLog } from 'services/api/utils'
|
||||||
|
|
||||||
const cors = initMiddleware(Cors())
|
const cors = initMiddleware(Cors())
|
||||||
|
|
||||||
@ -27,6 +28,7 @@ const defaultFrom = {
|
|||||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
await cors(req, res)
|
await cors(req, res)
|
||||||
if (req.method === 'POST') {
|
if (req.method === 'POST') {
|
||||||
|
const resultId = req.query.resultId as string | undefined
|
||||||
const { credentialsId, recipients, body, subject, cc, bcc, replyTo } = (
|
const { credentialsId, recipients, body, subject, cc, bcc, replyTo } = (
|
||||||
typeof req.body === 'string' ? JSON.parse(req.body) : req.body
|
typeof req.body === 'string' ? JSON.parse(req.body) : req.body
|
||||||
) as SendEmailOptions
|
) as SendEmailOptions
|
||||||
@ -36,7 +38,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
if (!from)
|
if (!from)
|
||||||
return res.status(404).send({ message: "Couldn't find credentials" })
|
return res.status(404).send({ message: "Couldn't find credentials" })
|
||||||
|
|
||||||
const transporter = createTransport({
|
const transportConfig = {
|
||||||
host,
|
host,
|
||||||
port,
|
port,
|
||||||
secure: isTlsEnabled ?? undefined,
|
secure: isTlsEnabled ?? undefined,
|
||||||
@ -44,8 +46,9 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
user: username,
|
user: username,
|
||||||
pass: password,
|
pass: password,
|
||||||
},
|
},
|
||||||
})
|
}
|
||||||
const info = await transporter.sendMail({
|
const transporter = createTransport(transportConfig)
|
||||||
|
const email = {
|
||||||
from: `"${from.name}" <${from.email}>`,
|
from: `"${from.name}" <${from.email}>`,
|
||||||
cc,
|
cc,
|
||||||
bcc,
|
bcc,
|
||||||
@ -53,14 +56,26 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
replyTo,
|
replyTo,
|
||||||
subject,
|
subject,
|
||||||
text: body,
|
text: body,
|
||||||
})
|
}
|
||||||
|
try {
|
||||||
res.status(200).send({
|
const info = await transporter.sendMail(email)
|
||||||
message: 'Email sent!',
|
await saveSuccessLog(resultId, 'Email successfully sent')
|
||||||
info,
|
return res.status(200).send({
|
||||||
previewUrl: getTestMessageUrl(info),
|
message: 'Email sent!',
|
||||||
})
|
info,
|
||||||
|
previewUrl: getTestMessageUrl(info),
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
await saveErrorLog(resultId, 'Email not sent', {
|
||||||
|
transportConfig,
|
||||||
|
email,
|
||||||
|
})
|
||||||
|
return res.status(500).send({
|
||||||
|
message: `Email not sent. Error: ${err}`,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return methodNotAllowed(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getEmailInfo = async (
|
const getEmailInfo = async (
|
||||||
|
@ -5,10 +5,12 @@ import { getAuthenticatedGoogleClient } from 'libs/google-sheets'
|
|||||||
import { Cell } from 'models'
|
import { Cell } from 'models'
|
||||||
import Cors from 'cors'
|
import Cors from 'cors'
|
||||||
import { withSentry } from '@sentry/nextjs'
|
import { withSentry } from '@sentry/nextjs'
|
||||||
|
import { saveErrorLog, saveSuccessLog } from 'services/api/utils'
|
||||||
|
|
||||||
const cors = initMiddleware(Cors())
|
const cors = initMiddleware(Cors())
|
||||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
await cors(req, res)
|
await cors(req, res)
|
||||||
|
const resultId = req.query.resultId as string | undefined
|
||||||
if (req.method === 'GET') {
|
if (req.method === 'GET') {
|
||||||
const spreadsheetId = req.query.spreadsheetId.toString()
|
const spreadsheetId = req.query.spreadsheetId.toString()
|
||||||
const sheetId = req.query.sheetId.toString()
|
const sheetId = req.query.sheetId.toString()
|
||||||
@ -29,17 +31,27 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
doc.useOAuth2Client(client)
|
doc.useOAuth2Client(client)
|
||||||
await doc.loadInfo()
|
await doc.loadInfo()
|
||||||
const sheet = doc.sheetsById[sheetId]
|
const sheet = doc.sheetsById[sheetId]
|
||||||
const rows = await sheet.getRows()
|
try {
|
||||||
const row = rows.find(
|
const rows = await sheet.getRows()
|
||||||
(row) => row[referenceCell.column as string] === referenceCell.value
|
const row = rows.find(
|
||||||
)
|
(row) => row[referenceCell.column as string] === referenceCell.value
|
||||||
if (!row) return res.status(404).send({ message: "Couldn't find row" })
|
)
|
||||||
return res.send({
|
if (!row) {
|
||||||
...extractingColumns.reduce(
|
await saveErrorLog(resultId, "Couldn't find reference cell")
|
||||||
(obj, column) => ({ ...obj, [column]: row[column] }),
|
return res.status(404).send({ message: "Couldn't find row" })
|
||||||
{}
|
}
|
||||||
),
|
const response = {
|
||||||
})
|
...extractingColumns.reduce(
|
||||||
|
(obj, column) => ({ ...obj, [column]: row[column] }),
|
||||||
|
{}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
await saveSuccessLog(resultId, 'Succesfully fetched spreadsheet data')
|
||||||
|
return res.send(response)
|
||||||
|
} catch (err) {
|
||||||
|
await saveErrorLog(resultId, "Couldn't fetch spreadsheet data", err)
|
||||||
|
return res.status(500).send(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (req.method === 'POST') {
|
if (req.method === 'POST') {
|
||||||
const spreadsheetId = req.query.spreadsheetId.toString()
|
const spreadsheetId = req.query.spreadsheetId.toString()
|
||||||
@ -55,10 +67,16 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
if (!auth)
|
if (!auth)
|
||||||
return res.status(404).send("Couldn't find credentials in database")
|
return res.status(404).send("Couldn't find credentials in database")
|
||||||
doc.useOAuth2Client(auth)
|
doc.useOAuth2Client(auth)
|
||||||
await doc.loadInfo()
|
try {
|
||||||
const sheet = doc.sheetsById[sheetId]
|
await doc.loadInfo()
|
||||||
await sheet.addRow(values)
|
const sheet = doc.sheetsById[sheetId]
|
||||||
return res.send({ message: 'Success' })
|
await sheet.addRow(values)
|
||||||
|
await saveSuccessLog(resultId, 'Succesfully inserted row')
|
||||||
|
return res.send({ message: 'Success' })
|
||||||
|
} catch (err) {
|
||||||
|
await saveErrorLog(resultId, "Couldn't fetch spreadsheet data", err)
|
||||||
|
return res.status(500).send(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (req.method === 'PATCH') {
|
if (req.method === 'PATCH') {
|
||||||
const spreadsheetId = req.query.spreadsheetId.toString()
|
const spreadsheetId = req.query.spreadsheetId.toString()
|
||||||
@ -75,19 +93,25 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
if (!auth)
|
if (!auth)
|
||||||
return res.status(404).send("Couldn't find credentials in database")
|
return res.status(404).send("Couldn't find credentials in database")
|
||||||
doc.useOAuth2Client(auth)
|
doc.useOAuth2Client(auth)
|
||||||
await doc.loadInfo()
|
try {
|
||||||
const sheet = doc.sheetsById[sheetId]
|
await doc.loadInfo()
|
||||||
const rows = await sheet.getRows()
|
const sheet = doc.sheetsById[sheetId]
|
||||||
const updatingRowIndex = rows.findIndex(
|
const rows = await sheet.getRows()
|
||||||
(row) => row[referenceCell.column as string] === referenceCell.value
|
const updatingRowIndex = rows.findIndex(
|
||||||
)
|
(row) => row[referenceCell.column as string] === referenceCell.value
|
||||||
if (updatingRowIndex === -1)
|
)
|
||||||
return res.status(404).send({ message: "Couldn't find row to update" })
|
if (updatingRowIndex === -1)
|
||||||
for (const key in values) {
|
return res.status(404).send({ message: "Couldn't find row to update" })
|
||||||
rows[updatingRowIndex][key] = values[key]
|
for (const key in values) {
|
||||||
|
rows[updatingRowIndex][key] = values[key]
|
||||||
|
}
|
||||||
|
await rows[updatingRowIndex].save()
|
||||||
|
await saveSuccessLog(resultId, 'Succesfully updated row')
|
||||||
|
return res.send({ message: 'Success' })
|
||||||
|
} catch (err) {
|
||||||
|
await saveErrorLog(resultId, "Couldn't fetch spreadsheet data", err)
|
||||||
|
return res.status(500).send(err)
|
||||||
}
|
}
|
||||||
await rows[updatingRowIndex].save()
|
|
||||||
return res.send({ message: 'Success' })
|
|
||||||
}
|
}
|
||||||
return methodNotAllowed(res)
|
return methodNotAllowed(res)
|
||||||
}
|
}
|
||||||
|
@ -20,12 +20,14 @@ import {
|
|||||||
initMiddleware,
|
initMiddleware,
|
||||||
methodNotAllowed,
|
methodNotAllowed,
|
||||||
notFound,
|
notFound,
|
||||||
|
omit,
|
||||||
parseAnswers,
|
parseAnswers,
|
||||||
} from 'utils'
|
} from 'utils'
|
||||||
import { stringify } from 'qs'
|
import { stringify } from 'qs'
|
||||||
import { withSentry } from '@sentry/nextjs'
|
import { withSentry } from '@sentry/nextjs'
|
||||||
import Cors from 'cors'
|
import Cors from 'cors'
|
||||||
import { parseSampleResult } from 'services/api/webhooks'
|
import { parseSampleResult } from 'services/api/webhooks'
|
||||||
|
import { saveErrorLog, saveSuccessLog } from 'services/api/utils'
|
||||||
|
|
||||||
const cors = initMiddleware(Cors())
|
const cors = initMiddleware(Cors())
|
||||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
@ -33,6 +35,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
if (req.method === 'POST') {
|
if (req.method === 'POST') {
|
||||||
const typebotId = req.query.typebotId.toString()
|
const typebotId = req.query.typebotId.toString()
|
||||||
const stepId = req.query.blockId.toString()
|
const stepId = req.query.blockId.toString()
|
||||||
|
const resultId = req.query.resultId as string | undefined
|
||||||
const { resultValues, variables } = (
|
const { resultValues, variables } = (
|
||||||
typeof req.body === 'string' ? JSON.parse(req.body) : req.body
|
typeof req.body === 'string' ? JSON.parse(req.body) : req.body
|
||||||
) as {
|
) as {
|
||||||
@ -57,7 +60,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
preparedWebhook,
|
preparedWebhook,
|
||||||
variables,
|
variables,
|
||||||
step.blockId,
|
step.blockId,
|
||||||
resultValues
|
resultValues,
|
||||||
|
resultId
|
||||||
)
|
)
|
||||||
return res.status(200).send(result)
|
return res.status(200).send(result)
|
||||||
}
|
}
|
||||||
@ -76,13 +80,14 @@ const prepareWebhookAttributes = (
|
|||||||
return webhook
|
return webhook
|
||||||
}
|
}
|
||||||
|
|
||||||
const executeWebhook =
|
export const executeWebhook =
|
||||||
(typebot: Typebot) =>
|
(typebot: Typebot) =>
|
||||||
async (
|
async (
|
||||||
webhook: Webhook,
|
webhook: Webhook,
|
||||||
variables: Variable[],
|
variables: Variable[],
|
||||||
blockId: string,
|
blockId: string,
|
||||||
resultValues?: ResultValues
|
resultValues?: ResultValues,
|
||||||
|
resultId?: string
|
||||||
): Promise<WebhookResponse> => {
|
): Promise<WebhookResponse> => {
|
||||||
if (!webhook.url || !webhook.method)
|
if (!webhook.url || !webhook.method)
|
||||||
return {
|
return {
|
||||||
@ -120,41 +125,55 @@ const executeWebhook =
|
|||||||
blockId,
|
blockId,
|
||||||
})
|
})
|
||||||
: undefined
|
: undefined
|
||||||
|
const request = {
|
||||||
|
url: parseVariables(variables)(
|
||||||
|
webhook.url + (queryParams !== '' ? `?${queryParams}` : '')
|
||||||
|
),
|
||||||
|
method: webhook.method as Method,
|
||||||
|
headers,
|
||||||
|
...basicAuth,
|
||||||
|
json:
|
||||||
|
contentType !== 'x-www-form-urlencoded' && body
|
||||||
|
? JSON.parse(parseVariables(variables)(body))
|
||||||
|
: undefined,
|
||||||
|
form:
|
||||||
|
contentType === 'x-www-form-urlencoded' && body
|
||||||
|
? JSON.parse(parseVariables(variables)(body))
|
||||||
|
: undefined,
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const response = await got(
|
const response = await got(request.url, omit(request, 'url'))
|
||||||
parseVariables(variables)(
|
await saveSuccessLog(resultId, 'Webhook successfuly executed.', {
|
||||||
webhook.url + (queryParams !== '' ? `?${queryParams}` : '')
|
statusCode: response.statusCode,
|
||||||
),
|
request,
|
||||||
{
|
response: parseBody(response.body),
|
||||||
method: webhook.method as Method,
|
})
|
||||||
headers,
|
|
||||||
...basicAuth,
|
|
||||||
json:
|
|
||||||
contentType !== 'x-www-form-urlencoded' && body
|
|
||||||
? JSON.parse(parseVariables(variables)(body))
|
|
||||||
: undefined,
|
|
||||||
form:
|
|
||||||
contentType === 'x-www-form-urlencoded' && body
|
|
||||||
? JSON.parse(parseVariables(variables)(body))
|
|
||||||
: undefined,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return {
|
return {
|
||||||
statusCode: response.statusCode,
|
statusCode: response.statusCode,
|
||||||
data: parseBody(response.body),
|
data: parseBody(response.body),
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof HTTPError) {
|
if (error instanceof HTTPError) {
|
||||||
return {
|
const response = {
|
||||||
statusCode: error.response.statusCode,
|
statusCode: error.response.statusCode,
|
||||||
data: parseBody(error.response.body as string),
|
data: parseBody(error.response.body as string),
|
||||||
}
|
}
|
||||||
|
await saveErrorLog(resultId, 'Webhook returned an error', {
|
||||||
|
request,
|
||||||
|
response,
|
||||||
|
})
|
||||||
|
return response
|
||||||
}
|
}
|
||||||
console.error(error)
|
const response = {
|
||||||
return {
|
|
||||||
statusCode: 500,
|
statusCode: 500,
|
||||||
data: { message: `Error from Typebot server: ${error}` },
|
data: { message: `Error from Typebot server: ${error}` },
|
||||||
}
|
}
|
||||||
|
console.error(error)
|
||||||
|
await saveErrorLog(resultId, 'Webhook failed to execute', {
|
||||||
|
request,
|
||||||
|
response,
|
||||||
|
})
|
||||||
|
return response
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,31 +1,18 @@
|
|||||||
import prisma from 'libs/prisma'
|
import prisma from 'libs/prisma'
|
||||||
import {
|
import {
|
||||||
defaultWebhookAttributes,
|
defaultWebhookAttributes,
|
||||||
HttpMethod,
|
|
||||||
KeyValue,
|
|
||||||
PublicTypebot,
|
|
||||||
ResultValues,
|
ResultValues,
|
||||||
Typebot,
|
Typebot,
|
||||||
Variable,
|
Variable,
|
||||||
Webhook,
|
Webhook,
|
||||||
WebhookOptions,
|
WebhookOptions,
|
||||||
WebhookResponse,
|
|
||||||
WebhookStep,
|
WebhookStep,
|
||||||
} from 'models'
|
} from 'models'
|
||||||
import { parseVariables } from 'bot-engine'
|
|
||||||
import { NextApiRequest, NextApiResponse } from 'next'
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
import got, { Method, Headers, HTTPError } from 'got'
|
import { byId, initMiddleware, methodNotAllowed, notFound } from 'utils'
|
||||||
import {
|
|
||||||
byId,
|
|
||||||
initMiddleware,
|
|
||||||
methodNotAllowed,
|
|
||||||
notFound,
|
|
||||||
parseAnswers,
|
|
||||||
} from 'utils'
|
|
||||||
import { stringify } from 'qs'
|
|
||||||
import { withSentry } from '@sentry/nextjs'
|
import { withSentry } from '@sentry/nextjs'
|
||||||
import Cors from 'cors'
|
import Cors from 'cors'
|
||||||
import { parseSampleResult } from 'services/api/webhooks'
|
import { executeWebhook } from '../../executeWebhook'
|
||||||
|
|
||||||
const cors = initMiddleware(Cors())
|
const cors = initMiddleware(Cors())
|
||||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
@ -34,6 +21,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
const typebotId = req.query.typebotId.toString()
|
const typebotId = req.query.typebotId.toString()
|
||||||
const blockId = req.query.blockId.toString()
|
const blockId = req.query.blockId.toString()
|
||||||
const stepId = req.query.stepId.toString()
|
const stepId = req.query.stepId.toString()
|
||||||
|
const resultId = req.query.resultId as string | undefined
|
||||||
const { resultValues, variables } = (
|
const { resultValues, variables } = (
|
||||||
typeof req.body === 'string' ? JSON.parse(req.body) : req.body
|
typeof req.body === 'string' ? JSON.parse(req.body) : req.body
|
||||||
) as {
|
) as {
|
||||||
@ -58,7 +46,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
preparedWebhook,
|
preparedWebhook,
|
||||||
variables,
|
variables,
|
||||||
blockId,
|
blockId,
|
||||||
resultValues
|
resultValues,
|
||||||
|
resultId
|
||||||
)
|
)
|
||||||
return res.status(200).send(result)
|
return res.status(200).send(result)
|
||||||
}
|
}
|
||||||
@ -77,129 +66,4 @@ const prepareWebhookAttributes = (
|
|||||||
return webhook
|
return webhook
|
||||||
}
|
}
|
||||||
|
|
||||||
const executeWebhook =
|
|
||||||
(typebot: Typebot) =>
|
|
||||||
async (
|
|
||||||
webhook: Webhook,
|
|
||||||
variables: Variable[],
|
|
||||||
blockId: string,
|
|
||||||
resultValues?: ResultValues
|
|
||||||
): Promise<WebhookResponse> => {
|
|
||||||
if (!webhook.url || !webhook.method)
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
data: { message: `Webhook doesn't have url or method` },
|
|
||||||
}
|
|
||||||
const basicAuth: { username?: string; password?: string } = {}
|
|
||||||
const basicAuthHeaderIdx = webhook.headers.findIndex(
|
|
||||||
(h) =>
|
|
||||||
h.key?.toLowerCase() === 'authorization' &&
|
|
||||||
h.value?.toLowerCase()?.includes('basic')
|
|
||||||
)
|
|
||||||
const isUsernamePasswordBasicAuth =
|
|
||||||
basicAuthHeaderIdx !== -1 &&
|
|
||||||
webhook.headers[basicAuthHeaderIdx].value?.includes(':')
|
|
||||||
if (isUsernamePasswordBasicAuth) {
|
|
||||||
const [username, password] =
|
|
||||||
webhook.headers[basicAuthHeaderIdx].value?.slice(6).split(':') ?? []
|
|
||||||
basicAuth.username = username
|
|
||||||
basicAuth.password = password
|
|
||||||
webhook.headers.splice(basicAuthHeaderIdx, 1)
|
|
||||||
}
|
|
||||||
const headers = convertKeyValueTableToObject(webhook.headers, variables) as
|
|
||||||
| Headers
|
|
||||||
| undefined
|
|
||||||
const queryParams = stringify(
|
|
||||||
convertKeyValueTableToObject(webhook.queryParams, variables)
|
|
||||||
)
|
|
||||||
const contentType = headers ? headers['Content-Type'] : undefined
|
|
||||||
const body =
|
|
||||||
webhook.method !== HttpMethod.GET
|
|
||||||
? getBodyContent(typebot)({
|
|
||||||
body: webhook.body,
|
|
||||||
resultValues,
|
|
||||||
blockId,
|
|
||||||
})
|
|
||||||
: undefined
|
|
||||||
try {
|
|
||||||
const response = await got(
|
|
||||||
parseVariables(variables)(
|
|
||||||
webhook.url + (queryParams !== '' ? `?${queryParams}` : '')
|
|
||||||
),
|
|
||||||
{
|
|
||||||
method: webhook.method as Method,
|
|
||||||
headers,
|
|
||||||
...basicAuth,
|
|
||||||
json:
|
|
||||||
contentType !== 'x-www-form-urlencoded' && body
|
|
||||||
? JSON.parse(parseVariables(variables)(body))
|
|
||||||
: undefined,
|
|
||||||
form:
|
|
||||||
contentType === 'x-www-form-urlencoded' && body
|
|
||||||
? JSON.parse(parseVariables(variables)(body))
|
|
||||||
: undefined,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
statusCode: response.statusCode,
|
|
||||||
data: parseBody(response.body),
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof HTTPError) {
|
|
||||||
return {
|
|
||||||
statusCode: error.response.statusCode,
|
|
||||||
data: parseBody(error.response.body as string),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.error(error)
|
|
||||||
return {
|
|
||||||
statusCode: 500,
|
|
||||||
data: { message: `Error from Typebot server: ${error}` },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getBodyContent =
|
|
||||||
(typebot: Pick<Typebot | PublicTypebot, 'blocks' | 'variables' | 'edges'>) =>
|
|
||||||
({
|
|
||||||
body,
|
|
||||||
resultValues,
|
|
||||||
blockId,
|
|
||||||
}: {
|
|
||||||
body?: string | null
|
|
||||||
resultValues?: ResultValues
|
|
||||||
blockId: string
|
|
||||||
}): string | undefined => {
|
|
||||||
if (!body) return
|
|
||||||
return body === '{{state}}'
|
|
||||||
? JSON.stringify(
|
|
||||||
resultValues
|
|
||||||
? parseAnswers(typebot)(resultValues)
|
|
||||||
: parseSampleResult(typebot)(blockId)
|
|
||||||
)
|
|
||||||
: body
|
|
||||||
}
|
|
||||||
|
|
||||||
const parseBody = (body: string) => {
|
|
||||||
try {
|
|
||||||
return JSON.parse(body)
|
|
||||||
} catch (err) {
|
|
||||||
return body
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const convertKeyValueTableToObject = (
|
|
||||||
keyValues: KeyValue[] | undefined,
|
|
||||||
variables: Variable[]
|
|
||||||
) => {
|
|
||||||
if (!keyValues) return
|
|
||||||
return keyValues.reduce((object, item) => {
|
|
||||||
if (!item.key) return {}
|
|
||||||
return {
|
|
||||||
...object,
|
|
||||||
[item.key]: parseVariables(variables)(item.value ?? ''),
|
|
||||||
}
|
|
||||||
}, {})
|
|
||||||
}
|
|
||||||
|
|
||||||
export default withSentry(handler)
|
export default withSentry(handler)
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
import { withSentry } from '@sentry/nextjs'
|
|
||||||
import { Log } from 'db'
|
|
||||||
import prisma from 'libs/prisma'
|
|
||||||
import { NextApiRequest, NextApiResponse } from 'next'
|
|
||||||
import { badRequest, methodNotAllowed } from 'utils'
|
|
||||||
|
|
||||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|
||||||
if (req.method === 'POST') {
|
|
||||||
const resultId = req.query.resultId as string
|
|
||||||
const log = req.body as
|
|
||||||
| Omit<Log, 'id' | 'createdAt' | 'resultId'>
|
|
||||||
| undefined
|
|
||||||
if (!log) return badRequest(res)
|
|
||||||
const createdLog = await prisma.log.create({ data: { ...log, resultId } })
|
|
||||||
return res.send(createdLog)
|
|
||||||
}
|
|
||||||
methodNotAllowed(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default withSentry(handler)
|
|
130
apps/viewer/playwright/fixtures/typebots/webhook.json
Normal file
130
apps/viewer/playwright/fixtures/typebots/webhook.json
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
{
|
||||||
|
"id": "cl26li8fl0407iez0w2tlw8fn",
|
||||||
|
"createdAt": "2022-04-19T20:25:30.417Z",
|
||||||
|
"updatedAt": "2022-04-19T20:40:48.366Z",
|
||||||
|
"icon": null,
|
||||||
|
"name": "My typebot",
|
||||||
|
"ownerId": "proUser",
|
||||||
|
"publishedTypebotId": null,
|
||||||
|
"folderId": null,
|
||||||
|
"blocks": [
|
||||||
|
{
|
||||||
|
"id": "cl26li8fj0000iez05x7razkg",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"id": "cl26li8fj0001iez0bqfraw9h",
|
||||||
|
"type": "start",
|
||||||
|
"label": "Start",
|
||||||
|
"blockId": "cl26li8fj0000iez05x7razkg",
|
||||||
|
"outgoingEdgeId": "cl26liqj6000g2e6ed2cwkvse"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Start",
|
||||||
|
"graphCoordinates": { "x": 0, "y": 0 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cl26lidjz000a2e6etf4v03hv",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"id": "cl26lidk4000b2e6es2fos0nl",
|
||||||
|
"type": "choice input",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "cl26lidk5000c2e6e39wyc7wq",
|
||||||
|
"type": 0,
|
||||||
|
"stepId": "cl26lidk4000b2e6es2fos0nl",
|
||||||
|
"content": "Send success webhook"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"blockId": "cl26lidjz000a2e6etf4v03hv",
|
||||||
|
"options": { "buttonLabel": "Send", "isMultipleChoice": false }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cl26lip76000e2e6ebmph843a",
|
||||||
|
"type": "Webhook",
|
||||||
|
"blockId": "cl26lidjz000a2e6etf4v03hv",
|
||||||
|
"options": {
|
||||||
|
"isCustomBody": false,
|
||||||
|
"isAdvancedConfig": false,
|
||||||
|
"variablesForTest": [],
|
||||||
|
"responseVariableMapping": []
|
||||||
|
},
|
||||||
|
"webhookId": "success-webhook"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cl26m0pdz00042e6ebjdoclaa",
|
||||||
|
"blockId": "cl26lidjz000a2e6etf4v03hv",
|
||||||
|
"type": "choice input",
|
||||||
|
"options": { "buttonLabel": "Send", "isMultipleChoice": false },
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "cl26m0pdz00052e6ecmxwfz44",
|
||||||
|
"stepId": "cl26m0pdz00042e6ebjdoclaa",
|
||||||
|
"type": 0,
|
||||||
|
"content": "Send failed webhook"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cl26m0w9b00072e6eld1ei291",
|
||||||
|
"blockId": "cl26lidjz000a2e6etf4v03hv",
|
||||||
|
"type": "Webhook",
|
||||||
|
"options": {
|
||||||
|
"responseVariableMapping": [],
|
||||||
|
"variablesForTest": [],
|
||||||
|
"isAdvancedConfig": false,
|
||||||
|
"isCustomBody": false
|
||||||
|
},
|
||||||
|
"webhookId": "failed-webhook"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Group #1",
|
||||||
|
"graphCoordinates": { "x": 386, "y": 117 }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"variables": [
|
||||||
|
{ "id": "vcl26lzmg100012e6e9rn57c3o", "name": "var1" },
|
||||||
|
{ "id": "vcl26lzo7q00022e6edw3pe7lf", "name": "var2" },
|
||||||
|
{ "id": "vcl26lzq6s00032e6ecuhh80qz", "name": "var3" }
|
||||||
|
],
|
||||||
|
"edges": [
|
||||||
|
{
|
||||||
|
"id": "cl26liqj6000g2e6ed2cwkvse",
|
||||||
|
"to": { "blockId": "cl26lidjz000a2e6etf4v03hv" },
|
||||||
|
"from": {
|
||||||
|
"stepId": "cl26li8fj0001iez0bqfraw9h",
|
||||||
|
"blockId": "cl26li8fj0000iez05x7razkg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"theme": {
|
||||||
|
"chat": {
|
||||||
|
"inputs": {
|
||||||
|
"color": "#303235",
|
||||||
|
"backgroundColor": "#FFFFFF",
|
||||||
|
"placeholderColor": "#9095A0"
|
||||||
|
},
|
||||||
|
"buttons": { "color": "#FFFFFF", "backgroundColor": "#0042DA" },
|
||||||
|
"hostAvatar": {
|
||||||
|
"url": "https://avatars.githubusercontent.com/u/16015833?v=4",
|
||||||
|
"isEnabled": true
|
||||||
|
},
|
||||||
|
"hostBubbles": { "color": "#303235", "backgroundColor": "#F7F8FF" },
|
||||||
|
"guestBubbles": { "color": "#FFFFFF", "backgroundColor": "#FF8E21" }
|
||||||
|
},
|
||||||
|
"general": { "font": "Open Sans", "background": { "type": "None" } }
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"general": {
|
||||||
|
"isBrandingEnabled": true,
|
||||||
|
"isInputPrefillEnabled": true,
|
||||||
|
"isNewResultOnRefreshEnabled": false
|
||||||
|
},
|
||||||
|
"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,
|
||||||
|
"customDomain": null
|
||||||
|
}
|
@ -6,6 +6,7 @@ import {
|
|||||||
SmtpCredentialsData,
|
SmtpCredentialsData,
|
||||||
Step,
|
Step,
|
||||||
Typebot,
|
Typebot,
|
||||||
|
Webhook,
|
||||||
} from 'models'
|
} from 'models'
|
||||||
import { PrismaClient } from 'db'
|
import { PrismaClient } from 'db'
|
||||||
import { readFileSync } from 'fs'
|
import { readFileSync } from 'fs'
|
||||||
@ -36,12 +37,13 @@ export const createUser = () =>
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export const createWebhook = (typebotId: string) =>
|
export const createWebhook = (typebotId: string, webhook?: Partial<Webhook>) =>
|
||||||
prisma.webhook.create({
|
prisma.webhook.create({
|
||||||
data: {
|
data: {
|
||||||
id: 'webhook1',
|
id: 'webhook1',
|
||||||
typebotId: typebotId,
|
typebotId,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
...webhook,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -42,4 +42,7 @@ test('should send an email', async ({ page }) => {
|
|||||||
await expect(
|
await expect(
|
||||||
page.locator('text="<baptiste.arnaud95@gmail.com>" >> nth=0')
|
page.locator('text="<baptiste.arnaud95@gmail.com>" >> nth=0')
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
|
await page.goto(`http://localhost:3000/typebots/${typebotId}/results`)
|
||||||
|
await page.click('text="See logs"')
|
||||||
|
await expect(page.locator('text="Email successfully sent"')).toBeVisible()
|
||||||
})
|
})
|
||||||
|
45
apps/viewer/playwright/tests/webhook.spec.ts
Normal file
45
apps/viewer/playwright/tests/webhook.spec.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import test, { expect } from '@playwright/test'
|
||||||
|
import { createWebhook, importTypebotInDatabase } from '../services/database'
|
||||||
|
import cuid from 'cuid'
|
||||||
|
import path from 'path'
|
||||||
|
import { typebotViewer } from '../services/selectorUtils'
|
||||||
|
import { HttpMethod } from 'models'
|
||||||
|
|
||||||
|
test('should execute webhooks properly', async ({ page }) => {
|
||||||
|
const typebotId = cuid()
|
||||||
|
await importTypebotInDatabase(
|
||||||
|
path.join(__dirname, '../fixtures/typebots/webhook.json'),
|
||||||
|
{ id: typebotId, publicId: `${typebotId}-public` }
|
||||||
|
)
|
||||||
|
await createWebhook(typebotId, {
|
||||||
|
id: 'success-webhook',
|
||||||
|
url: 'https://webhook.site/912bafb0-b92f-4be8-ae6a-186b5879a17a',
|
||||||
|
method: HttpMethod.POST,
|
||||||
|
})
|
||||||
|
await createWebhook(typebotId, {
|
||||||
|
id: 'failed-webhook',
|
||||||
|
url: 'https://webhook.site/8be94c01-141e-4792-b3c6-cf45137481d6',
|
||||||
|
method: HttpMethod.POST,
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.goto(`/${typebotId}-public`)
|
||||||
|
await typebotViewer(page).locator('text=Send success webhook').click()
|
||||||
|
await page.waitForResponse(
|
||||||
|
(resp) =>
|
||||||
|
resp.request().url().includes(`/api/typebots/${typebotId}/blocks`) &&
|
||||||
|
resp.status() === 200
|
||||||
|
)
|
||||||
|
await typebotViewer(page).locator('text=Send failed webhook').click()
|
||||||
|
await page.waitForResponse(
|
||||||
|
async (resp) =>
|
||||||
|
resp.request().url().includes(`/api/typebots/${typebotId}/blocks`) &&
|
||||||
|
resp.status() === 200 &&
|
||||||
|
(await resp.json()).statusCode === 500
|
||||||
|
)
|
||||||
|
await page.goto(`http://localhost:3000/typebots/${typebotId}/results`)
|
||||||
|
await page.click('text="See logs"')
|
||||||
|
await expect(
|
||||||
|
page.locator('text="Webhook successfuly executed."')
|
||||||
|
).toBeVisible()
|
||||||
|
await expect(page.locator('text="Webhook returned an error"')).toBeVisible()
|
||||||
|
})
|
@ -15,3 +15,40 @@ const authenticateByToken = async (
|
|||||||
|
|
||||||
const extractBearerToken = (req: NextApiRequest) =>
|
const extractBearerToken = (req: NextApiRequest) =>
|
||||||
req.headers['authorization']?.slice(7)
|
req.headers['authorization']?.slice(7)
|
||||||
|
|
||||||
|
export const saveErrorLog = (
|
||||||
|
resultId: string | undefined,
|
||||||
|
message: string,
|
||||||
|
details?: any
|
||||||
|
) => saveLog('error', resultId, message, details)
|
||||||
|
|
||||||
|
export const saveSuccessLog = (
|
||||||
|
resultId: string | undefined,
|
||||||
|
message: string,
|
||||||
|
details?: any
|
||||||
|
) => saveLog('success', resultId, message, details)
|
||||||
|
|
||||||
|
const saveLog = (
|
||||||
|
status: 'error' | 'success',
|
||||||
|
resultId: string | undefined,
|
||||||
|
message: string,
|
||||||
|
details?: any
|
||||||
|
) => {
|
||||||
|
if (!resultId) return
|
||||||
|
return prisma.log.create({
|
||||||
|
data: {
|
||||||
|
resultId,
|
||||||
|
status,
|
||||||
|
description: message,
|
||||||
|
details: formatDetails(details),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatDetails = (details: any) => {
|
||||||
|
try {
|
||||||
|
return JSON.stringify(details, null, 2).substring(0, 1000)
|
||||||
|
} catch {
|
||||||
|
return details
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -14,13 +14,3 @@ export const updateResult = async (resultId: string, result: Partial<Result>) =>
|
|||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
body: result,
|
body: result,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const createLog = (
|
|
||||||
resultId: string,
|
|
||||||
log: Omit<Log, 'id' | 'createdAt' | 'resultId'>
|
|
||||||
) =>
|
|
||||||
sendRequest<Result>({
|
|
||||||
url: `/api/typebots/t/results/${resultId}/logs`,
|
|
||||||
method: 'POST',
|
|
||||||
body: log,
|
|
||||||
})
|
|
||||||
|
@ -54,7 +54,7 @@ export const ChatBlock = ({
|
|||||||
setCurrentTypebotId,
|
setCurrentTypebotId,
|
||||||
pushEdgeIdInLinkedTypebotQueue,
|
pushEdgeIdInLinkedTypebotQueue,
|
||||||
} = useTypebot()
|
} = useTypebot()
|
||||||
const { resultValues, updateVariables } = useAnswers()
|
const { resultValues, updateVariables, resultId } = useAnswers()
|
||||||
const [processedSteps, setProcessedSteps] = useState<Step[]>([])
|
const [processedSteps, setProcessedSteps] = useState<Step[]>([])
|
||||||
const [displayedChunks, setDisplayedChunks] = useState<ChatDisplayChunk[]>([])
|
const [displayedChunks, setDisplayedChunks] = useState<ChatDisplayChunk[]>([])
|
||||||
|
|
||||||
@ -134,6 +134,7 @@ export const ChatBlock = ({
|
|||||||
resultValues,
|
resultValues,
|
||||||
blocks: typebot.blocks,
|
blocks: typebot.blocks,
|
||||||
onNewLog,
|
onNewLog,
|
||||||
|
resultId,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
nextEdgeId ? onBlockEnd(nextEdgeId) : displayNextStep()
|
nextEdgeId ? onBlockEnd(nextEdgeId) : displayNextStep()
|
||||||
|
@ -28,6 +28,7 @@ export type TypebotViewerProps = {
|
|||||||
apiHost?: string
|
apiHost?: string
|
||||||
style?: CSSProperties
|
style?: CSSProperties
|
||||||
predefinedVariables?: { [key: string]: string | undefined }
|
predefinedVariables?: { [key: string]: string | undefined }
|
||||||
|
resultId?: string
|
||||||
onNewBlockVisible?: (edge: Edge) => void
|
onNewBlockVisible?: (edge: Edge) => void
|
||||||
onNewAnswer?: (answer: Answer) => void
|
onNewAnswer?: (answer: Answer) => void
|
||||||
onNewLog?: (log: Omit<Log, 'id' | 'createdAt' | 'resultId'>) => void
|
onNewLog?: (log: Omit<Log, 'id' | 'createdAt' | 'resultId'>) => void
|
||||||
@ -40,6 +41,7 @@ export const TypebotViewer = ({
|
|||||||
apiHost = process.env.NEXT_PUBLIC_VIEWER_URL?.split(',')[0],
|
apiHost = process.env.NEXT_PUBLIC_VIEWER_URL?.split(',')[0],
|
||||||
isPreview = false,
|
isPreview = false,
|
||||||
style,
|
style,
|
||||||
|
resultId,
|
||||||
predefinedVariables,
|
predefinedVariables,
|
||||||
onNewLog,
|
onNewLog,
|
||||||
onNewBlockVisible,
|
onNewBlockVisible,
|
||||||
@ -95,6 +97,7 @@ export const TypebotViewer = ({
|
|||||||
onNewLog={handleNewLog}
|
onNewLog={handleNewLog}
|
||||||
>
|
>
|
||||||
<AnswersContext
|
<AnswersContext
|
||||||
|
resultId={resultId}
|
||||||
onNewAnswer={handleNewAnswer}
|
onNewAnswer={handleNewAnswer}
|
||||||
onVariablesUpdated={onVariablesUpdated}
|
onVariablesUpdated={onVariablesUpdated}
|
||||||
>
|
>
|
||||||
|
@ -2,6 +2,7 @@ import { Answer, ResultValues, VariableWithValue } from 'models'
|
|||||||
import React, { createContext, ReactNode, useContext, useState } from 'react'
|
import React, { createContext, ReactNode, useContext, useState } from 'react'
|
||||||
|
|
||||||
const answersContext = createContext<{
|
const answersContext = createContext<{
|
||||||
|
resultId?: string
|
||||||
resultValues: ResultValues
|
resultValues: ResultValues
|
||||||
addAnswer: (answer: Answer) => void
|
addAnswer: (answer: Answer) => void
|
||||||
updateVariables: (variables: VariableWithValue[]) => void
|
updateVariables: (variables: VariableWithValue[]) => void
|
||||||
@ -11,9 +12,11 @@ const answersContext = createContext<{
|
|||||||
|
|
||||||
export const AnswersContext = ({
|
export const AnswersContext = ({
|
||||||
children,
|
children,
|
||||||
|
resultId,
|
||||||
onNewAnswer,
|
onNewAnswer,
|
||||||
onVariablesUpdated,
|
onVariablesUpdated,
|
||||||
}: {
|
}: {
|
||||||
|
resultId?: string
|
||||||
onNewAnswer: (answer: Answer) => void
|
onNewAnswer: (answer: Answer) => void
|
||||||
onVariablesUpdated?: (variables: VariableWithValue[]) => void
|
onVariablesUpdated?: (variables: VariableWithValue[]) => void
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
@ -45,6 +48,7 @@ export const AnswersContext = ({
|
|||||||
return (
|
return (
|
||||||
<answersContext.Provider
|
<answersContext.Provider
|
||||||
value={{
|
value={{
|
||||||
|
resultId,
|
||||||
resultValues,
|
resultValues,
|
||||||
addAnswer,
|
addAnswer,
|
||||||
updateVariables,
|
updateVariables,
|
||||||
|
@ -33,6 +33,7 @@ type IntegrationContext = {
|
|||||||
variables: Variable[]
|
variables: Variable[]
|
||||||
resultValues: ResultValues
|
resultValues: ResultValues
|
||||||
blocks: Block[]
|
blocks: Block[]
|
||||||
|
resultId?: string
|
||||||
updateVariables: (variables: VariableWithValue[]) => void
|
updateVariables: (variables: VariableWithValue[]) => void
|
||||||
updateVariableValue: (variableId: string, value: string) => void
|
updateVariableValue: (variableId: string, value: string) => void
|
||||||
onNewLog: (log: Omit<Log, 'id' | 'createdAt' | 'resultId'>) => void
|
onNewLog: (log: Omit<Log, 'id' | 'createdAt' | 'resultId'>) => void
|
||||||
@ -92,7 +93,7 @@ const executeGoogleSheetIntegration = async (
|
|||||||
|
|
||||||
const insertRowInGoogleSheets = async (
|
const insertRowInGoogleSheets = async (
|
||||||
options: GoogleSheetsInsertRowOptions,
|
options: GoogleSheetsInsertRowOptions,
|
||||||
{ variables, apiHost, onNewLog }: IntegrationContext
|
{ variables, apiHost, onNewLog, resultId }: IntegrationContext
|
||||||
) => {
|
) => {
|
||||||
if (!options.cellsToInsert) {
|
if (!options.cellsToInsert) {
|
||||||
onNewLog({
|
onNewLog({
|
||||||
@ -102,8 +103,9 @@ const insertRowInGoogleSheets = async (
|
|||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
const params = stringify({ resultId })
|
||||||
const { error } = await sendRequest({
|
const { error } = await sendRequest({
|
||||||
url: `${apiHost}/api/integrations/google-sheets/spreadsheets/${options.spreadsheetId}/sheets/${options.sheetId}`,
|
url: `${apiHost}/api/integrations/google-sheets/spreadsheets/${options.spreadsheetId}/sheets/${options.sheetId}?${params}`,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: {
|
body: {
|
||||||
credentialsId: options.credentialsId,
|
credentialsId: options.credentialsId,
|
||||||
@ -121,11 +123,12 @@ const insertRowInGoogleSheets = async (
|
|||||||
|
|
||||||
const updateRowInGoogleSheets = async (
|
const updateRowInGoogleSheets = async (
|
||||||
options: GoogleSheetsUpdateRowOptions,
|
options: GoogleSheetsUpdateRowOptions,
|
||||||
{ variables, apiHost, onNewLog }: IntegrationContext
|
{ variables, apiHost, onNewLog, resultId }: IntegrationContext
|
||||||
) => {
|
) => {
|
||||||
if (!options.cellsToUpsert || !options.referenceCell) return
|
if (!options.cellsToUpsert || !options.referenceCell) return
|
||||||
|
const params = stringify({ resultId })
|
||||||
const { error } = await sendRequest({
|
const { error } = await sendRequest({
|
||||||
url: `${apiHost}/api/integrations/google-sheets/spreadsheets/${options.spreadsheetId}/sheets/${options.sheetId}`,
|
url: `${apiHost}/api/integrations/google-sheets/spreadsheets/${options.spreadsheetId}/sheets/${options.sheetId}?${params}`,
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
body: {
|
body: {
|
||||||
credentialsId: options.credentialsId,
|
credentialsId: options.credentialsId,
|
||||||
@ -153,6 +156,7 @@ const getRowFromGoogleSheets = async (
|
|||||||
updateVariables,
|
updateVariables,
|
||||||
apiHost,
|
apiHost,
|
||||||
onNewLog,
|
onNewLog,
|
||||||
|
resultId,
|
||||||
}: IntegrationContext
|
}: IntegrationContext
|
||||||
) => {
|
) => {
|
||||||
if (!options.referenceCell || !options.cellsToExtract) return
|
if (!options.referenceCell || !options.cellsToExtract) return
|
||||||
@ -164,6 +168,7 @@ const getRowFromGoogleSheets = async (
|
|||||||
value: parseVariables(variables)(options.referenceCell.value ?? ''),
|
value: parseVariables(variables)(options.referenceCell.value ?? ''),
|
||||||
},
|
},
|
||||||
columns: options.cellsToExtract.map((cell) => cell.column),
|
columns: options.cellsToExtract.map((cell) => cell.column),
|
||||||
|
resultId,
|
||||||
},
|
},
|
||||||
{ indices: false }
|
{ indices: false }
|
||||||
)
|
)
|
||||||
@ -222,10 +227,12 @@ const executeWebhook = async (
|
|||||||
apiHost,
|
apiHost,
|
||||||
resultValues,
|
resultValues,
|
||||||
onNewLog,
|
onNewLog,
|
||||||
|
resultId,
|
||||||
}: IntegrationContext
|
}: IntegrationContext
|
||||||
) => {
|
) => {
|
||||||
|
const params = stringify({ resultId })
|
||||||
const { data, error } = await sendRequest({
|
const { data, error } = await sendRequest({
|
||||||
url: `${apiHost}/api/typebots/${typebotId}/blocks/${blockId}/steps/${stepId}/executeWebhook`,
|
url: `${apiHost}/api/typebots/${typebotId}/blocks/${blockId}/steps/${stepId}/executeWebhook?${params}`,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: {
|
body: {
|
||||||
variables,
|
variables,
|
||||||
@ -266,7 +273,7 @@ const executeWebhook = async (
|
|||||||
|
|
||||||
const sendEmail = async (
|
const sendEmail = async (
|
||||||
step: SendEmailStep,
|
step: SendEmailStep,
|
||||||
{ variables, apiHost, isPreview, onNewLog }: IntegrationContext
|
{ variables, apiHost, isPreview, onNewLog, resultId }: IntegrationContext
|
||||||
) => {
|
) => {
|
||||||
if (isPreview) {
|
if (isPreview) {
|
||||||
onNewLog({
|
onNewLog({
|
||||||
@ -279,7 +286,7 @@ const sendEmail = async (
|
|||||||
const { options } = step
|
const { options } = step
|
||||||
const replyTo = parseVariables(variables)(options.replyTo)
|
const replyTo = parseVariables(variables)(options.replyTo)
|
||||||
const { error } = await sendRequest({
|
const { error } = await sendRequest({
|
||||||
url: `${apiHost}/api/integrations/email`,
|
url: `${apiHost}/api/integrations/email?resultId=${resultId}`,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: {
|
body: {
|
||||||
credentialsId: options.credentialsId,
|
credentialsId: options.credentialsId,
|
||||||
|
Reference in New Issue
Block a user