2022-03-01 11:40:22 +01:00
|
|
|
import { Log } from 'db'
|
2022-01-18 18:25:18 +01:00
|
|
|
import {
|
|
|
|
IntegrationStep,
|
|
|
|
IntegrationStepType,
|
|
|
|
GoogleSheetsStep,
|
|
|
|
GoogleSheetsAction,
|
|
|
|
GoogleSheetsInsertRowOptions,
|
|
|
|
Variable,
|
|
|
|
GoogleSheetsUpdateRowOptions,
|
|
|
|
Cell,
|
|
|
|
GoogleSheetsGetOptions,
|
2022-01-19 14:25:15 +01:00
|
|
|
GoogleAnalyticsStep,
|
2022-01-22 18:24:57 +01:00
|
|
|
WebhookStep,
|
2022-02-07 18:06:37 +01:00
|
|
|
SendEmailStep,
|
2022-02-22 08:03:38 +01:00
|
|
|
ZapierStep,
|
2022-02-22 10:16:35 +01:00
|
|
|
ResultValues,
|
2022-03-01 07:13:09 +01:00
|
|
|
Block,
|
2022-03-28 17:07:47 +02:00
|
|
|
VariableWithValue,
|
2022-01-18 18:25:18 +01:00
|
|
|
} from 'models'
|
|
|
|
import { stringify } from 'qs'
|
2022-03-28 17:07:47 +02:00
|
|
|
import { byId, sendRequest } from 'utils'
|
2022-01-19 14:25:15 +01:00
|
|
|
import { sendGaEvent } from '../../lib/gtag'
|
|
|
|
import { parseVariables, parseVariablesInObject } from './variable'
|
2022-01-18 18:25:18 +01:00
|
|
|
|
2022-02-10 14:31:44 +01:00
|
|
|
type IntegrationContext = {
|
|
|
|
apiHost: string
|
|
|
|
typebotId: string
|
2022-02-15 12:36:08 +01:00
|
|
|
blockId: string
|
|
|
|
stepId: string
|
2022-02-13 07:49:56 +01:00
|
|
|
isPreview: boolean
|
2022-02-10 14:31:44 +01:00
|
|
|
variables: Variable[]
|
2022-02-22 06:55:15 +01:00
|
|
|
resultValues: ResultValues
|
2022-03-01 07:13:09 +01:00
|
|
|
blocks: Block[]
|
2022-03-28 17:07:47 +02:00
|
|
|
updateVariables: (variables: VariableWithValue[]) => void
|
2022-01-18 18:25:18 +01:00
|
|
|
updateVariableValue: (variableId: string, value: string) => void
|
2022-03-01 11:40:22 +01:00
|
|
|
onNewLog: (log: Omit<Log, 'id' | 'createdAt' | 'resultId'>) => void
|
2022-02-10 14:31:44 +01:00
|
|
|
}
|
2022-02-22 06:55:15 +01:00
|
|
|
|
2022-02-10 14:31:44 +01:00
|
|
|
export const executeIntegration = ({
|
|
|
|
step,
|
|
|
|
context,
|
|
|
|
}: {
|
|
|
|
step: IntegrationStep
|
|
|
|
context: IntegrationContext
|
|
|
|
}): Promise<string | undefined> => {
|
2022-01-18 18:25:18 +01:00
|
|
|
switch (step.type) {
|
|
|
|
case IntegrationStepType.GOOGLE_SHEETS:
|
2022-02-10 14:31:44 +01:00
|
|
|
return executeGoogleSheetIntegration(step, context)
|
2022-01-19 14:25:15 +01:00
|
|
|
case IntegrationStepType.GOOGLE_ANALYTICS:
|
2022-02-10 14:31:44 +01:00
|
|
|
return executeGoogleAnalyticsIntegration(step, context)
|
2022-02-22 08:03:38 +01:00
|
|
|
case IntegrationStepType.ZAPIER:
|
2022-01-22 18:24:57 +01:00
|
|
|
case IntegrationStepType.WEBHOOK:
|
2022-02-10 14:31:44 +01:00
|
|
|
return executeWebhook(step, context)
|
2022-02-07 18:06:37 +01:00
|
|
|
case IntegrationStepType.EMAIL:
|
2022-02-10 14:31:44 +01:00
|
|
|
return sendEmail(step, context)
|
2022-01-18 18:25:18 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-19 14:25:15 +01:00
|
|
|
export const executeGoogleAnalyticsIntegration = async (
|
|
|
|
step: GoogleAnalyticsStep,
|
2022-02-10 14:31:44 +01:00
|
|
|
{ variables }: IntegrationContext
|
2022-01-19 14:25:15 +01:00
|
|
|
) => {
|
2022-02-07 18:06:37 +01:00
|
|
|
if (!step.options?.trackingId) return step.outgoingEdgeId
|
2022-01-19 14:25:15 +01:00
|
|
|
const { default: initGoogleAnalytics } = await import('../../lib/gtag')
|
|
|
|
await initGoogleAnalytics(step.options.trackingId)
|
|
|
|
sendGaEvent(parseVariablesInObject(step.options, variables))
|
2022-02-07 18:06:37 +01:00
|
|
|
return step.outgoingEdgeId
|
2022-01-19 14:25:15 +01:00
|
|
|
}
|
|
|
|
|
2022-01-18 18:25:18 +01:00
|
|
|
const executeGoogleSheetIntegration = async (
|
|
|
|
step: GoogleSheetsStep,
|
2022-02-10 14:31:44 +01:00
|
|
|
context: IntegrationContext
|
2022-01-18 18:25:18 +01:00
|
|
|
) => {
|
2022-02-04 19:00:08 +01:00
|
|
|
if (!('action' in step.options)) return step.outgoingEdgeId
|
2022-02-02 08:05:02 +01:00
|
|
|
switch (step.options.action) {
|
2022-01-18 18:25:18 +01:00
|
|
|
case GoogleSheetsAction.INSERT_ROW:
|
2022-02-10 14:31:44 +01:00
|
|
|
await insertRowInGoogleSheets(step.options, context)
|
2022-01-18 18:25:18 +01:00
|
|
|
break
|
|
|
|
case GoogleSheetsAction.UPDATE_ROW:
|
2022-02-10 14:31:44 +01:00
|
|
|
await updateRowInGoogleSheets(step.options, context)
|
2022-01-18 18:25:18 +01:00
|
|
|
break
|
|
|
|
case GoogleSheetsAction.GET:
|
2022-02-10 14:31:44 +01:00
|
|
|
await getRowFromGoogleSheets(step.options, context)
|
2022-01-18 18:25:18 +01:00
|
|
|
break
|
|
|
|
}
|
2022-02-04 19:00:08 +01:00
|
|
|
return step.outgoingEdgeId
|
2022-01-18 18:25:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const insertRowInGoogleSheets = async (
|
|
|
|
options: GoogleSheetsInsertRowOptions,
|
2022-03-01 11:40:22 +01:00
|
|
|
{ variables, apiHost, onNewLog }: IntegrationContext
|
2022-01-18 18:25:18 +01:00
|
|
|
) => {
|
2022-03-01 11:40:22 +01:00
|
|
|
if (!options.cellsToInsert) {
|
|
|
|
onNewLog({
|
|
|
|
status: 'warning',
|
|
|
|
description: 'Cells to insert are undefined',
|
|
|
|
details: null,
|
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
const { error } = await sendRequest({
|
2022-02-10 14:31:44 +01:00
|
|
|
url: `${apiHost}/api/integrations/google-sheets/spreadsheets/${options.spreadsheetId}/sheets/${options.sheetId}`,
|
2022-01-18 18:25:18 +01:00
|
|
|
method: 'POST',
|
|
|
|
body: {
|
|
|
|
credentialsId: options.credentialsId,
|
|
|
|
values: parseCellValues(options.cellsToInsert, variables),
|
|
|
|
},
|
|
|
|
})
|
2022-03-01 11:40:22 +01:00
|
|
|
onNewLog(
|
|
|
|
parseLog(
|
|
|
|
error,
|
|
|
|
'Succesfully inserted a row in the sheet',
|
|
|
|
'Failed to insert a row in the sheet'
|
|
|
|
)
|
|
|
|
)
|
2022-01-18 18:25:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const updateRowInGoogleSheets = async (
|
|
|
|
options: GoogleSheetsUpdateRowOptions,
|
2022-03-01 11:40:22 +01:00
|
|
|
{ variables, apiHost, onNewLog }: IntegrationContext
|
2022-01-18 18:25:18 +01:00
|
|
|
) => {
|
|
|
|
if (!options.cellsToUpsert || !options.referenceCell) return
|
2022-03-01 11:40:22 +01:00
|
|
|
const { error } = await sendRequest({
|
2022-02-10 14:31:44 +01:00
|
|
|
url: `${apiHost}/api/integrations/google-sheets/spreadsheets/${options.spreadsheetId}/sheets/${options.sheetId}`,
|
2022-01-18 18:25:18 +01:00
|
|
|
method: 'PATCH',
|
|
|
|
body: {
|
|
|
|
credentialsId: options.credentialsId,
|
|
|
|
values: parseCellValues(options.cellsToUpsert, variables),
|
|
|
|
referenceCell: {
|
|
|
|
column: options.referenceCell.column,
|
2022-02-07 18:06:37 +01:00
|
|
|
value: parseVariables(variables)(options.referenceCell.value ?? ''),
|
2022-01-18 18:25:18 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
2022-03-01 11:40:22 +01:00
|
|
|
onNewLog(
|
|
|
|
parseLog(
|
|
|
|
error,
|
|
|
|
'Succesfully updated a row in the sheet',
|
|
|
|
'Failed to update a row in the sheet'
|
|
|
|
)
|
|
|
|
)
|
2022-01-18 18:25:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const getRowFromGoogleSheets = async (
|
|
|
|
options: GoogleSheetsGetOptions,
|
2022-03-28 17:07:47 +02:00
|
|
|
{
|
|
|
|
variables,
|
|
|
|
updateVariableValue,
|
|
|
|
updateVariables,
|
|
|
|
apiHost,
|
|
|
|
onNewLog,
|
|
|
|
}: IntegrationContext
|
2022-01-18 18:25:18 +01:00
|
|
|
) => {
|
|
|
|
if (!options.referenceCell || !options.cellsToExtract) return
|
|
|
|
const queryParams = stringify(
|
|
|
|
{
|
|
|
|
credentialsId: options.credentialsId,
|
|
|
|
referenceCell: {
|
|
|
|
column: options.referenceCell.column,
|
2022-02-07 18:06:37 +01:00
|
|
|
value: parseVariables(variables)(options.referenceCell.value ?? ''),
|
2022-01-18 18:25:18 +01:00
|
|
|
},
|
2022-02-04 19:00:08 +01:00
|
|
|
columns: options.cellsToExtract.map((cell) => cell.column),
|
2022-01-18 18:25:18 +01:00
|
|
|
},
|
|
|
|
{ indices: false }
|
|
|
|
)
|
2022-03-01 11:40:22 +01:00
|
|
|
const { data, error } = await sendRequest<{ [key: string]: string }>({
|
2022-02-10 14:31:44 +01:00
|
|
|
url: `${apiHost}/api/integrations/google-sheets/spreadsheets/${options.spreadsheetId}/sheets/${options.sheetId}?${queryParams}`,
|
2022-01-18 18:25:18 +01:00
|
|
|
method: 'GET',
|
|
|
|
})
|
2022-03-01 11:40:22 +01:00
|
|
|
onNewLog(
|
|
|
|
parseLog(
|
|
|
|
error,
|
|
|
|
'Succesfully fetched data from sheet',
|
|
|
|
'Failed to fetch data from sheet'
|
|
|
|
)
|
|
|
|
)
|
2022-01-18 18:25:18 +01:00
|
|
|
if (!data) return
|
2022-03-28 17:07:47 +02:00
|
|
|
const newVariables = options.cellsToExtract.reduce<VariableWithValue[]>(
|
|
|
|
(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,
|
|
|
|
},
|
|
|
|
]
|
|
|
|
},
|
|
|
|
[]
|
2022-02-04 19:00:08 +01:00
|
|
|
)
|
2022-03-28 17:07:47 +02:00
|
|
|
updateVariables(newVariables)
|
2022-01-18 18:25:18 +01:00
|
|
|
}
|
|
|
|
const parseCellValues = (
|
2022-02-04 19:00:08 +01:00
|
|
|
cells: Cell[],
|
|
|
|
variables: Variable[]
|
2022-01-18 18:25:18 +01:00
|
|
|
): { [key: string]: string } =>
|
2022-02-04 19:00:08 +01:00
|
|
|
cells.reduce((row, cell) => {
|
2022-01-18 18:25:18 +01:00
|
|
|
return !cell.column || !cell.value
|
|
|
|
? row
|
2022-01-19 14:25:15 +01:00
|
|
|
: {
|
|
|
|
...row,
|
2022-02-07 18:06:37 +01:00
|
|
|
[cell.column]: parseVariables(variables)(cell.value),
|
2022-01-19 14:25:15 +01:00
|
|
|
}
|
2022-01-18 18:25:18 +01:00
|
|
|
}, {})
|
2022-01-22 18:24:57 +01:00
|
|
|
|
|
|
|
const executeWebhook = async (
|
2022-02-22 08:03:38 +01:00
|
|
|
step: WebhookStep | ZapierStep,
|
2022-02-10 14:31:44 +01:00
|
|
|
{
|
2022-02-15 12:36:08 +01:00
|
|
|
blockId,
|
|
|
|
stepId,
|
2022-02-10 14:31:44 +01:00
|
|
|
variables,
|
|
|
|
updateVariableValue,
|
2022-03-28 17:07:47 +02:00
|
|
|
updateVariables,
|
2022-02-10 14:31:44 +01:00
|
|
|
typebotId,
|
|
|
|
apiHost,
|
2022-02-22 10:16:35 +01:00
|
|
|
resultValues,
|
2022-03-01 11:40:22 +01:00
|
|
|
onNewLog,
|
2022-02-10 14:31:44 +01:00
|
|
|
}: IntegrationContext
|
2022-01-22 18:24:57 +01:00
|
|
|
) => {
|
|
|
|
const { data, error } = await sendRequest({
|
2022-02-15 12:36:08 +01:00
|
|
|
url: `${apiHost}/api/typebots/${typebotId}/blocks/${blockId}/steps/${stepId}/executeWebhook`,
|
2022-01-22 18:24:57 +01:00
|
|
|
method: 'POST',
|
|
|
|
body: {
|
|
|
|
variables,
|
2022-02-22 10:16:35 +01:00
|
|
|
resultValues,
|
2022-01-22 18:24:57 +01:00
|
|
|
},
|
|
|
|
})
|
2022-03-01 15:01:27 +01:00
|
|
|
const statusCode = (
|
|
|
|
data as Record<string, string> | undefined
|
|
|
|
)?.statusCode.toString()
|
|
|
|
const isError = statusCode
|
|
|
|
? statusCode?.startsWith('4') || statusCode?.startsWith('5')
|
|
|
|
: true
|
2022-03-01 11:40:22 +01:00
|
|
|
onNewLog({
|
|
|
|
status: error ? 'error' : isError ? 'warning' : 'success',
|
|
|
|
description: isError
|
|
|
|
? 'Webhook returned an error'
|
|
|
|
: 'Webhook successfuly executed',
|
|
|
|
details: JSON.stringify(error ?? data, null, 2).substring(0, 1000),
|
|
|
|
})
|
2022-03-28 17:07:47 +02:00
|
|
|
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
|
2022-03-07 17:39:57 +01:00
|
|
|
const value = Function(
|
|
|
|
`return (${JSON.stringify(data)}).${varMapping?.bodyPath}`
|
|
|
|
)()
|
2022-03-28 17:07:47 +02:00
|
|
|
updateVariableValue(existingVariable?.id, value)
|
|
|
|
return [...newVariables, { ...existingVariable, value }]
|
|
|
|
}, [])
|
|
|
|
updateVariables(newVariables)
|
2022-03-01 07:13:09 +01:00
|
|
|
return step.outgoingEdgeId
|
2022-01-22 18:24:57 +01:00
|
|
|
}
|
2022-02-07 18:06:37 +01:00
|
|
|
|
2022-02-10 14:31:44 +01:00
|
|
|
const sendEmail = async (
|
|
|
|
step: SendEmailStep,
|
2022-03-01 11:40:22 +01:00
|
|
|
{ variables, apiHost, isPreview, onNewLog }: IntegrationContext
|
2022-02-10 14:31:44 +01:00
|
|
|
) => {
|
2022-03-01 11:40:22 +01:00
|
|
|
if (isPreview) {
|
|
|
|
onNewLog({
|
|
|
|
status: 'info',
|
|
|
|
description: 'Emails are not sent in preview mode',
|
|
|
|
details: null,
|
|
|
|
})
|
|
|
|
return step.outgoingEdgeId
|
|
|
|
}
|
2022-02-07 18:06:37 +01:00
|
|
|
const { options } = step
|
2022-03-22 10:54:11 +01:00
|
|
|
const replyTo = parseVariables(variables)(options.replyTo)
|
2022-02-07 18:06:37 +01:00
|
|
|
const { error } = await sendRequest({
|
2022-02-10 14:31:44 +01:00
|
|
|
url: `${apiHost}/api/integrations/email`,
|
2022-02-07 18:06:37 +01:00
|
|
|
method: 'POST',
|
|
|
|
body: {
|
|
|
|
credentialsId: options.credentialsId,
|
|
|
|
recipients: options.recipients.map(parseVariables(variables)),
|
|
|
|
subject: parseVariables(variables)(options.subject ?? ''),
|
2022-02-22 10:16:35 +01:00
|
|
|
body: parseVariables(variables)(options.body ?? ''),
|
2022-02-19 10:58:56 +01:00
|
|
|
cc: (options.cc ?? []).map(parseVariables(variables)),
|
|
|
|
bcc: (options.bcc ?? []).map(parseVariables(variables)),
|
2022-03-22 10:54:11 +01:00
|
|
|
replyTo: replyTo !== '' ? replyTo : undefined,
|
2022-02-07 18:06:37 +01:00
|
|
|
},
|
|
|
|
})
|
2022-03-01 11:40:22 +01:00
|
|
|
onNewLog(
|
|
|
|
parseLog(error, 'Succesfully sent an email', 'Failed to send an email')
|
|
|
|
)
|
2022-02-07 18:06:37 +01:00
|
|
|
return step.outgoingEdgeId
|
|
|
|
}
|
2022-03-01 11:40:22 +01:00
|
|
|
|
|
|
|
const parseLog = (
|
|
|
|
error: Error | undefined,
|
|
|
|
successMessage: string,
|
|
|
|
errorMessage: string
|
|
|
|
): Omit<Log, 'id' | 'createdAt' | 'resultId'> => ({
|
|
|
|
status: error ? 'error' : 'success',
|
|
|
|
description: error ? errorMessage : successMessage,
|
|
|
|
details: (error && JSON.stringify(error, null, 2).substring(0, 1000)) ?? null,
|
|
|
|
})
|