2
0

♻️ (webhook) Integrate webhook in typebot schema

Closes #313
This commit is contained in:
Baptiste Arnaud
2023-08-06 10:03:45 +02:00
parent 53e4bc2b75
commit fc25734689
66 changed files with 1501 additions and 876 deletions

View File

@@ -105,7 +105,6 @@ export const ConversationContainer = (props: Props) => {
})
const streamMessage = (content: string) => {
console.log('STREAM', content)
setIsSending(false)
const lastChunk = [...chatChunks()].pop()
if (!lastChunk) return

View File

@@ -0,0 +1,63 @@
import { PrismaClient, Webhook as WebhookFromDb } from '@typebot.io/prisma'
import {
Block,
Typebot,
Webhook,
defaultWebhookAttributes,
} from '@typebot.io/schemas'
import { isWebhookBlock } from '../utils'
import { HttpMethod } from '@typebot.io/schemas/features/blocks/integrations/webhook/enums'
export const migrateTypebotFromV3ToV4 =
(prisma: PrismaClient) =>
async (
typebot: Typebot
): Promise<Omit<Typebot, 'version'> & { version: '4' }> => {
if (typebot.version === '4')
return typebot as Omit<Typebot, 'version'> & { version: '4' }
const webhookBlocks = typebot.groups
.flatMap((group) => group.blocks)
.filter(isWebhookBlock)
const webhooks = await prisma.webhook.findMany({
where: {
id: {
in: webhookBlocks.map((block) => block.webhookId as string),
},
},
})
return {
...typebot,
version: '4',
groups: typebot.groups.map((group) => ({
...group,
blocks: group.blocks.map(migrateWebhookBlock(webhooks)),
})),
}
}
const migrateWebhookBlock =
(webhooks: WebhookFromDb[]) =>
(block: Block): Block => {
if (!isWebhookBlock(block)) return block
const webhook = webhooks.find((webhook) => webhook.id === block.webhookId)
return {
...block,
webhookId: undefined,
options: {
...block.options,
webhook: webhook
? {
id: webhook.id,
url: webhook.url ?? undefined,
method: (webhook.method as Webhook['method']) ?? HttpMethod.POST,
headers: (webhook.headers as Webhook['headers']) ?? [],
queryParams: (webhook.queryParams as Webhook['headers']) ?? [],
body: webhook.body ?? undefined,
}
: {
...defaultWebhookAttributes,
id: block.webhookId ?? '',
},
},
}
}

View File

@@ -125,16 +125,6 @@ export const blockTypeHasOption = (
.concat(Object.values(IntegrationBlockType))
.includes(type)
export const blockTypeHasWebhook = (
type: BlockType
): type is IntegrationBlockType.WEBHOOK =>
Object.values([
IntegrationBlockType.WEBHOOK,
IntegrationBlockType.ZAPIER,
IntegrationBlockType.MAKE_COM,
IntegrationBlockType.PABBLY_CONNECT,
] as string[]).includes(type)
export const blockTypeHasItems = (
type: BlockType
): type is

View File

@@ -5,7 +5,7 @@ export * from './googleSheets'
export * from './makeCom'
export * from './pabblyConnect'
export * from './sendEmail'
export * from './webhook'
export * from './webhook/schemas'
export * from './zapier'
export * from './pixel/schemas'
export * from './pixel/constants'

View File

@@ -1,13 +1,16 @@
import { z } from 'zod'
import { blockBaseSchema } from '../baseSchemas'
import { IntegrationBlockType } from './enums'
import { webhookOptionsSchema } from './webhook'
import { webhookOptionsSchema } from './webhook/schemas'
export const makeComBlockSchema = blockBaseSchema.merge(
z.object({
type: z.enum([IntegrationBlockType.MAKE_COM]),
options: webhookOptionsSchema,
webhookId: z.string(),
webhookId: z
.string()
.describe('Deprecated, use webhook.id instead')
.optional(),
})
)

View File

@@ -1,13 +1,16 @@
import { z } from 'zod'
import { blockBaseSchema } from '../baseSchemas'
import { IntegrationBlockType } from './enums'
import { webhookOptionsSchema } from './webhook'
import { webhookOptionsSchema } from './webhook/schemas'
export const pabblyConnectBlockSchema = blockBaseSchema.merge(
z.object({
type: z.enum([IntegrationBlockType.PABBLY_CONNECT]),
options: webhookOptionsSchema,
webhookId: z.string(),
webhookId: z
.string()
.describe('Deprecated, use webhook.id instead')
.optional(),
})
)

View File

@@ -1,45 +0,0 @@
import { z } from 'zod'
import { blockBaseSchema } from '../baseSchemas'
import { IntegrationBlockType } from './enums'
const variableForTestSchema = z.object({
id: z.string(),
variableId: z.string().optional(),
value: z.string().optional(),
})
const responseVariableMappingSchema = z.object({
id: z.string(),
variableId: z.string().optional(),
bodyPath: z.string().optional(),
})
export const webhookOptionsSchema = z.object({
variablesForTest: z.array(variableForTestSchema),
responseVariableMapping: z.array(responseVariableMappingSchema),
isAdvancedConfig: z.boolean().optional(),
isCustomBody: z.boolean().optional(),
isExecutedOnClient: z.boolean().optional(),
})
export const webhookBlockSchema = blockBaseSchema.merge(
z.object({
type: z.enum([IntegrationBlockType.WEBHOOK]),
options: webhookOptionsSchema,
webhookId: z.string(),
})
)
export const defaultWebhookOptions: Omit<WebhookOptions, 'webhookId'> = {
responseVariableMapping: [],
variablesForTest: [],
isAdvancedConfig: false,
isCustomBody: false,
}
export type WebhookBlock = z.infer<typeof webhookBlockSchema>
export type WebhookOptions = z.infer<typeof webhookOptionsSchema>
export type ResponseVariableMapping = z.infer<
typeof responseVariableMappingSchema
>
export type VariableForTest = z.infer<typeof variableForTestSchema>

View File

@@ -0,0 +1,11 @@
export enum HttpMethod {
POST = 'POST',
GET = 'GET',
PUT = 'PUT',
DELETE = 'DELETE',
PATCH = 'PATCH',
HEAD = 'HEAD',
CONNECT = 'CONNECT',
OPTIONS = 'OPTIONS',
TRACE = 'TRACE',
}

View File

@@ -0,0 +1,2 @@
export * from './enums'
export * from './schemas'

View File

@@ -0,0 +1,95 @@
import { z } from 'zod'
import { blockBaseSchema } from '../../baseSchemas'
import { IntegrationBlockType } from '../enums'
import { HttpMethod } from './enums'
const variableForTestSchema = z.object({
id: z.string(),
variableId: z.string().optional(),
value: z.string().optional(),
})
const responseVariableMappingSchema = z.object({
id: z.string(),
variableId: z.string().optional(),
bodyPath: z.string().optional(),
})
const keyValueSchema = z.object({
id: z.string(),
key: z.string().optional(),
value: z.string().optional(),
})
export const webhookSchema = z.object({
id: z.string(),
queryParams: keyValueSchema.array(),
headers: keyValueSchema.array(),
method: z.nativeEnum(HttpMethod),
url: z.string().optional(),
body: z.string().optional(),
})
export const webhookOptionsSchema = z.object({
variablesForTest: z.array(variableForTestSchema),
responseVariableMapping: z.array(responseVariableMappingSchema),
isAdvancedConfig: z.boolean().optional(),
isCustomBody: z.boolean().optional(),
isExecutedOnClient: z.boolean().optional(),
webhook: webhookSchema.optional(),
})
export const webhookBlockSchema = blockBaseSchema.merge(
z.object({
type: z.enum([IntegrationBlockType.WEBHOOK]),
options: webhookOptionsSchema,
webhookId: z
.string()
.describe('Deprecated, now integrated in webhook block options')
.optional(),
})
)
export const defaultWebhookAttributes: Omit<
Webhook,
'id' | 'body' | 'url' | 'typebotId' | 'createdAt' | 'updatedAt'
> = {
method: HttpMethod.POST,
headers: [],
queryParams: [],
}
export const defaultWebhookOptions = (webhookId: string): WebhookOptions => ({
responseVariableMapping: [],
variablesForTest: [],
isAdvancedConfig: false,
isCustomBody: false,
webhook: {
id: webhookId,
...defaultWebhookAttributes,
},
})
export const executableWebhookSchema = z.object({
url: z.string(),
headers: z.record(z.string()).optional(),
body: z.unknown().optional(),
method: z.nativeEnum(HttpMethod).optional(),
})
export type KeyValue = { id: string; key?: string; value?: string }
export type WebhookResponse = {
statusCode: number
data?: unknown
}
export type ExecutableWebhook = z.infer<typeof executableWebhookSchema>
export type Webhook = z.infer<typeof webhookSchema>
export type WebhookBlock = z.infer<typeof webhookBlockSchema>
export type WebhookOptions = z.infer<typeof webhookOptionsSchema>
export type ResponseVariableMapping = z.infer<
typeof responseVariableMappingSchema
>
export type VariableForTest = z.infer<typeof variableForTestSchema>

View File

@@ -1,13 +1,16 @@
import { z } from 'zod'
import { blockBaseSchema } from '../baseSchemas'
import { IntegrationBlockType } from './enums'
import { webhookOptionsSchema } from './webhook'
import { webhookOptionsSchema } from './webhook/schemas'
export const zapierBlockSchema = blockBaseSchema.merge(
z.object({
type: z.enum([IntegrationBlockType.ZAPIER]),
options: webhookOptionsSchema,
webhookId: z.string(),
webhookId: z
.string()
.describe('Deprecated, use webhook.id instead')
.optional(),
})
)

View File

@@ -1,5 +1,6 @@
import { z } from 'zod'
import {
executableWebhookSchema,
googleAnalyticsOptionsSchema,
paymentInputRuntimeOptionsSchema,
pixelOptionsSchema,
@@ -19,7 +20,6 @@ import { answerSchema } from './answer'
import { BubbleBlockType } from './blocks/bubbles/enums'
import { inputBlockSchemas } from './blocks/schemas'
import { chatCompletionMessageSchema } from './blocks/integrations/openai'
import { executableWebhookSchema } from './webhooks'
const typebotInSessionStateSchema = publicTypebotSchema.pick({
id: true,

View File

@@ -11,7 +11,7 @@ import { z } from 'zod'
export const publicTypebotSchema = z.object({
id: z.string(),
version: z.enum(['3']).nullable(),
version: z.enum(['3', '4']).nullable(),
createdAt: z.date(),
updatedAt: z.date(),
typebotId: z.string(),

View File

@@ -39,7 +39,7 @@ const resultsTablePreferencesSchema = z.object({
})
export const typebotSchema = z.object({
version: z.enum(['3']).nullable(),
version: z.enum(['3', '4']).nullable(),
id: z.string(),
name: z.string(),
groups: z.array(groupSchema),

View File

@@ -1,48 +0,0 @@
import { Webhook as WebhookFromPrisma } from '@typebot.io/prisma'
import { z } from 'zod'
export enum HttpMethod {
POST = 'POST',
GET = 'GET',
PUT = 'PUT',
DELETE = 'DELETE',
PATCH = 'PATCH',
HEAD = 'HEAD',
CONNECT = 'CONNECT',
OPTIONS = 'OPTIONS',
TRACE = 'TRACE',
}
export type KeyValue = { id: string; key?: string; value?: string }
export type Webhook = Omit<
WebhookFromPrisma,
'queryParams' | 'headers' | 'method' | 'createdAt' | 'updatedAt'
> & {
queryParams: KeyValue[]
headers: KeyValue[]
method: HttpMethod
}
export type WebhookResponse = {
statusCode: number
data?: unknown
}
export const defaultWebhookAttributes: Omit<
Webhook,
'id' | 'body' | 'url' | 'typebotId' | 'createdAt' | 'updatedAt'
> = {
method: HttpMethod.POST,
headers: [],
queryParams: [],
}
export const executableWebhookSchema = z.object({
url: z.string(),
headers: z.record(z.string()).optional(),
body: z.unknown().optional(),
method: z.nativeEnum(HttpMethod).optional(),
})
export type ExecutableWebhook = z.infer<typeof executableWebhookSchema>

View File

@@ -5,7 +5,6 @@ export * from './features/result'
export * from './features/answer'
export * from './features/utils'
export * from './features/credentials'
export * from './features/webhooks'
export * from './features/chat'
export * from './features/workspace'
export * from './features/items'