feat(editor): ✨ Restore published version button
Had to migrate webhooks into a standalone table
This commit is contained in:
@@ -3,7 +3,6 @@ import { TransitionGroup, CSSTransition } from 'react-transition-group'
|
||||
import { ChatStep } from './ChatStep'
|
||||
import { AvatarSideContainer } from './AvatarSideContainer'
|
||||
import { HostAvatarsContext } from '../../contexts/HostAvatarsContext'
|
||||
import { PublicStep } from 'models'
|
||||
import { useTypebot } from '../../contexts/TypebotContext'
|
||||
import {
|
||||
isBubbleStep,
|
||||
@@ -17,9 +16,10 @@ import { executeIntegration } from 'services/integration'
|
||||
import { parseRetryStep, stepCanBeRetried } from 'services/inputs'
|
||||
import { parseVariables } from 'index'
|
||||
import { useAnswers } from 'contexts/AnswersContext'
|
||||
import { Step } from 'models'
|
||||
|
||||
type ChatBlockProps = {
|
||||
steps: PublicStep[]
|
||||
steps: Step[]
|
||||
startStepIndex: number
|
||||
onScroll: () => void
|
||||
onBlockEnd: (edgeId?: string) => void
|
||||
@@ -34,7 +34,7 @@ export const ChatBlock = ({
|
||||
const { typebot, updateVariableValue, createEdge, apiHost, isPreview } =
|
||||
useTypebot()
|
||||
const { resultValues } = useAnswers()
|
||||
const [displayedSteps, setDisplayedSteps] = useState<PublicStep[]>([])
|
||||
const [displayedSteps, setDisplayedSteps] = useState<Step[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
const nextStep = steps[startStepIndex]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useAnswers } from '../../../contexts/AnswersContext'
|
||||
import { useHostAvatars } from '../../../contexts/HostAvatarsContext'
|
||||
import { InputStep, InputStepType, PublicStep } from 'models'
|
||||
import { InputStep, InputStepType, Step } from 'models'
|
||||
import { GuestBubble } from './bubbles/GuestBubble'
|
||||
import { TextForm } from './inputs/TextForm'
|
||||
import { byId, isBubbleStep, isInputStep } from 'utils'
|
||||
@@ -16,7 +16,7 @@ export const ChatStep = ({
|
||||
step,
|
||||
onTransitionEnd,
|
||||
}: {
|
||||
step: PublicStep
|
||||
step: Step
|
||||
onTransitionEnd: (answerContent?: string, isRetry?: boolean) => void
|
||||
}) => {
|
||||
const { addAnswer } = useAnswers()
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useFrame } from 'react-frame-component'
|
||||
import { setCssVariablesValue } from '../services/theme'
|
||||
import { useAnswers } from '../contexts/AnswersContext'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import { Answer, Edge, PublicBlock, Theme, VariableWithValue } from 'models'
|
||||
import { Answer, Block, Edge, Theme, VariableWithValue } from 'models'
|
||||
import { byId, isNotDefined } from 'utils'
|
||||
import { animateScroll as scroll } from 'react-scroll'
|
||||
import { useTypebot } from 'contexts/TypebotContext'
|
||||
@@ -27,7 +27,7 @@ export const ConversationContainer = ({
|
||||
const { typebot, updateVariableValue } = useTypebot()
|
||||
const { document: frameDocument } = useFrame()
|
||||
const [displayedBlocks, setDisplayedBlocks] = useState<
|
||||
{ block: PublicBlock; startStepIndex: number }[]
|
||||
{ block: Block; startStepIndex: number }[]
|
||||
>([])
|
||||
const [localAnswer, setLocalAnswer] = useState<Answer | undefined>()
|
||||
const {
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
EmailInputStep,
|
||||
InputStepType,
|
||||
PhoneNumberInputStep,
|
||||
PublicStep,
|
||||
Step,
|
||||
UrlInputStep,
|
||||
Variable,
|
||||
} from 'models'
|
||||
@@ -34,7 +34,7 @@ export const isInputValid = (
|
||||
}
|
||||
|
||||
export const stepCanBeRetried = (
|
||||
step: PublicStep
|
||||
step: Step
|
||||
): step is EmailInputStep | UrlInputStep | PhoneNumberInputStep =>
|
||||
isInputStep(step) && 'retryMessageContent' in step.options
|
||||
|
||||
|
||||
@@ -11,9 +11,9 @@ import {
|
||||
GoogleAnalyticsStep,
|
||||
WebhookStep,
|
||||
SendEmailStep,
|
||||
PublicBlock,
|
||||
ZapierStep,
|
||||
ResultValues,
|
||||
Block,
|
||||
} from 'models'
|
||||
import { stringify } from 'qs'
|
||||
import { sendRequest } from 'utils'
|
||||
@@ -31,7 +31,7 @@ type IntegrationContext = {
|
||||
isPreview: boolean
|
||||
variables: Variable[]
|
||||
resultValues: ResultValues
|
||||
blocks: PublicBlock[]
|
||||
blocks: Block[]
|
||||
updateVariableValue: (variableId: string, value: string) => void
|
||||
}
|
||||
|
||||
@@ -170,7 +170,6 @@ const executeWebhook = async (
|
||||
isPreview,
|
||||
}: IntegrationContext
|
||||
) => {
|
||||
if (!step.webhook) return step.outgoingEdgeId
|
||||
const { data, error } = await sendRequest({
|
||||
url: `${apiHost}/api/typebots/${typebotId}/blocks/${blockId}/steps/${stepId}/executeWebhook`,
|
||||
method: 'POST',
|
||||
@@ -186,6 +185,7 @@ const executeWebhook = async (
|
||||
const value = safeEval(`(${JSON.stringify(data)}).${varMapping?.bodyPath}`)
|
||||
updateVariableValue(varMapping.variableId, value)
|
||||
})
|
||||
return step.outgoingEdgeId
|
||||
}
|
||||
|
||||
const sendEmail = async (
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "Webhook" (
|
||||
"id" TEXT NOT NULL,
|
||||
"url" TEXT,
|
||||
"method" TEXT NOT NULL,
|
||||
"queryParams" JSONB[],
|
||||
"headers" JSONB[],
|
||||
"body" TEXT,
|
||||
"typebotId" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "Webhook_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Webhook" ADD CONSTRAINT "Webhook_typebotId_fkey" FOREIGN KEY ("typebotId") REFERENCES "Typebot"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -129,6 +129,7 @@ model Typebot {
|
||||
customDomain String? @unique
|
||||
collaborators CollaboratorsOnTypebots[]
|
||||
invitations Invitation[]
|
||||
webhooks Webhook[]
|
||||
|
||||
@@unique([id, ownerId])
|
||||
}
|
||||
@@ -202,3 +203,14 @@ model Coupon {
|
||||
code String @id @unique
|
||||
dateRedeemed DateTime?
|
||||
}
|
||||
|
||||
model Webhook {
|
||||
id String @id @default(cuid())
|
||||
url String?
|
||||
method String
|
||||
queryParams Json[]
|
||||
headers Json[]
|
||||
body String?
|
||||
typebotId String
|
||||
typebot Typebot @relation(fields: [typebotId], references: [id], onDelete: Cascade)
|
||||
}
|
||||
|
||||
@@ -4,3 +4,4 @@ export * from './result'
|
||||
export * from './answer'
|
||||
export * from './utils'
|
||||
export * from './credentials'
|
||||
export * from './webhooks'
|
||||
|
||||
@@ -1,22 +1,13 @@
|
||||
import { Block, Edge, Settings, Step, Theme, Variable } from './typebot'
|
||||
import { Block, Edge, Settings, Theme, Variable } from './typebot'
|
||||
import { PublicTypebot as PublicTypebotFromPrisma } from 'db'
|
||||
|
||||
export type PublicTypebot = Omit<
|
||||
PublicTypebotFromPrisma,
|
||||
| 'blocks'
|
||||
| 'theme'
|
||||
| 'settings'
|
||||
| 'variables'
|
||||
| 'edges'
|
||||
| 'createdAt'
|
||||
| 'updatedAt'
|
||||
'blocks' | 'theme' | 'settings' | 'variables' | 'edges'
|
||||
> & {
|
||||
blocks: PublicBlock[]
|
||||
blocks: Block[]
|
||||
variables: Variable[]
|
||||
edges: Edge[]
|
||||
theme: Theme
|
||||
settings: Settings
|
||||
}
|
||||
|
||||
export type PublicBlock = Omit<Block, 'steps'> & { steps: PublicStep[] }
|
||||
export type PublicStep = Omit<Step, 'webhook'> & { webhook?: string }
|
||||
|
||||
@@ -34,7 +34,7 @@ export type GoogleAnalyticsStep = StepBase & {
|
||||
export type WebhookStep = StepBase & {
|
||||
type: IntegrationStepType.WEBHOOK
|
||||
options: WebhookOptions
|
||||
webhook: Webhook
|
||||
webhookId: string
|
||||
}
|
||||
|
||||
export type ZapierStep = Omit<WebhookStep, 'type'> & {
|
||||
@@ -118,39 +118,12 @@ export type WebhookOptions = {
|
||||
responseVariableMapping: ResponseVariableMapping[]
|
||||
}
|
||||
|
||||
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 VariableForTest = {
|
||||
id: string
|
||||
variableId?: string
|
||||
value?: string
|
||||
}
|
||||
|
||||
export type Webhook = {
|
||||
id: string
|
||||
url?: string
|
||||
method: HttpMethod
|
||||
queryParams: KeyValue[]
|
||||
headers: KeyValue[]
|
||||
body?: string
|
||||
}
|
||||
|
||||
export type WebhookResponse = {
|
||||
statusCode: number
|
||||
data?: unknown
|
||||
}
|
||||
|
||||
export const defaultGoogleSheetsOptions: GoogleSheetsOptions = {}
|
||||
|
||||
export const defaultGoogleAnalyticsOptions: GoogleAnalyticsOptions = {}
|
||||
@@ -160,12 +133,6 @@ export const defaultWebhookOptions: Omit<WebhookOptions, 'webhookId'> = {
|
||||
variablesForTest: [],
|
||||
}
|
||||
|
||||
export const defaultWebhookAttributes: Omit<Webhook, 'id'> = {
|
||||
method: HttpMethod.GET,
|
||||
headers: [],
|
||||
queryParams: [],
|
||||
}
|
||||
|
||||
export const defaultSendEmailOptions: SendEmailOptions = {
|
||||
credentialsId: 'default',
|
||||
recipients: [],
|
||||
|
||||
38
packages/models/src/webhooks.ts
Normal file
38
packages/models/src/webhooks.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { Webhook as WebhookFromPrisma } from 'db'
|
||||
|
||||
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'
|
||||
> & {
|
||||
queryParams: KeyValue[]
|
||||
headers: KeyValue[]
|
||||
method: HttpMethod
|
||||
}
|
||||
|
||||
export type WebhookResponse = {
|
||||
statusCode: number
|
||||
data?: unknown
|
||||
}
|
||||
|
||||
export const defaultWebhookAttributes: Omit<
|
||||
Webhook,
|
||||
'id' | 'body' | 'url' | 'typebotId'
|
||||
> = {
|
||||
method: HttpMethod.GET,
|
||||
headers: [],
|
||||
queryParams: [],
|
||||
}
|
||||
@@ -10,6 +10,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"db": "*",
|
||||
"models": "*",
|
||||
"utils": "*",
|
||||
"ts-node": "^10.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
Block,
|
||||
} from 'models'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { byId, isDefined } from '.'
|
||||
import { byId, isDefined } from './utils'
|
||||
|
||||
export const methodNotAllowed = (res: NextApiResponse) =>
|
||||
res.status(405).json({ message: 'Method Not Allowed' })
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
WebhookStep,
|
||||
StepType,
|
||||
StepWithOptionsType,
|
||||
PublicStep,
|
||||
ImageBubbleStep,
|
||||
VideoBubbleStep,
|
||||
} from 'models'
|
||||
@@ -62,50 +61,42 @@ export const isNotDefined = <T>(
|
||||
value: T | undefined | null
|
||||
): value is undefined | null => value === undefined || value === null
|
||||
|
||||
export const isInputStep = (step: Step | PublicStep): step is InputStep =>
|
||||
export const isInputStep = (step: Step): step is InputStep =>
|
||||
(Object.values(InputStepType) as string[]).includes(step.type)
|
||||
|
||||
export const isBubbleStep = (step: Step | PublicStep): step is BubbleStep =>
|
||||
export const isBubbleStep = (step: Step): step is BubbleStep =>
|
||||
(Object.values(BubbleStepType) as string[]).includes(step.type)
|
||||
|
||||
export const isLogicStep = (step: Step | PublicStep): step is LogicStep =>
|
||||
export const isLogicStep = (step: Step): step is LogicStep =>
|
||||
(Object.values(LogicStepType) as string[]).includes(step.type)
|
||||
|
||||
export const isTextBubbleStep = (
|
||||
step: Step | PublicStep
|
||||
): step is TextBubbleStep => step.type === BubbleStepType.TEXT
|
||||
export const isTextBubbleStep = (step: Step): step is TextBubbleStep =>
|
||||
step.type === BubbleStepType.TEXT
|
||||
|
||||
export const isMediaBubbleStep = (
|
||||
step: Step | PublicStep
|
||||
step: Step
|
||||
): step is ImageBubbleStep | VideoBubbleStep =>
|
||||
step.type === BubbleStepType.IMAGE || step.type === BubbleStepType.VIDEO
|
||||
|
||||
export const isTextInputStep = (
|
||||
step: Step | PublicStep
|
||||
): step is TextInputStep => step.type === InputStepType.TEXT
|
||||
export const isTextInputStep = (step: Step): step is TextInputStep =>
|
||||
step.type === InputStepType.TEXT
|
||||
|
||||
export const isChoiceInput = (
|
||||
step: Step | PublicStep
|
||||
): step is ChoiceInputStep => step.type === InputStepType.CHOICE
|
||||
export const isChoiceInput = (step: Step): step is ChoiceInputStep =>
|
||||
step.type === InputStepType.CHOICE
|
||||
|
||||
export const isSingleChoiceInput = (
|
||||
step: Step | PublicStep
|
||||
): step is ChoiceInputStep =>
|
||||
export const isSingleChoiceInput = (step: Step): step is ChoiceInputStep =>
|
||||
step.type === InputStepType.CHOICE &&
|
||||
'options' in step &&
|
||||
!step.options.isMultipleChoice
|
||||
|
||||
export const isConditionStep = (
|
||||
step: Step | PublicStep
|
||||
): step is ConditionStep => step.type === LogicStepType.CONDITION
|
||||
export const isConditionStep = (step: Step): step is ConditionStep =>
|
||||
step.type === LogicStepType.CONDITION
|
||||
|
||||
export const isIntegrationStep = (
|
||||
step: Step | PublicStep
|
||||
): step is IntegrationStep =>
|
||||
export const isIntegrationStep = (step: Step): step is IntegrationStep =>
|
||||
(Object.values(IntegrationStepType) as string[]).includes(step.type)
|
||||
|
||||
export const isWebhookStep = (step: Step | PublicStep): step is WebhookStep =>
|
||||
'webhook' in step
|
||||
export const isWebhookStep = (step: Step): step is WebhookStep =>
|
||||
'webhookId' in step
|
||||
|
||||
export const isBubbleStepType = (type: StepType): type is BubbleStepType =>
|
||||
(Object.values(BubbleStepType) as string[]).includes(type)
|
||||
@@ -132,9 +123,29 @@ export const stepTypeHasItems = (
|
||||
type === LogicStepType.CONDITION || type === InputStepType.CHOICE
|
||||
|
||||
export const stepHasItems = (
|
||||
step: Step | PublicStep
|
||||
step: Step
|
||||
): step is ConditionStep | ChoiceInputStep => 'items' in step
|
||||
|
||||
export const byId = (id?: string) => (obj: { id: string }) => obj.id === id
|
||||
|
||||
export const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1)
|
||||
|
||||
interface Omit {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
<T extends object, K extends [...(keyof T)[]]>(obj: T, ...keys: K): {
|
||||
[K2 in Exclude<keyof T, K[number]>]: T[K2]
|
||||
}
|
||||
}
|
||||
|
||||
export const omit: Omit = (obj, ...keys) => {
|
||||
const ret = {} as {
|
||||
[K in keyof typeof obj]: typeof obj[K]
|
||||
}
|
||||
let key: keyof typeof obj
|
||||
for (key in obj) {
|
||||
if (!keys.includes(key)) {
|
||||
ret[key] = obj[key]
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user