♻️ (viewer) Change to features-centric folder structure
This commit is contained in:
committed by
Baptiste Arnaud
parent
643571fe7d
commit
a9d04798bc
3
.gitignore
vendored
3
.gitignore
vendored
@@ -9,7 +9,8 @@ authenticatedState.json
|
|||||||
playwright-report
|
playwright-report
|
||||||
dist
|
dist
|
||||||
test-results
|
test-results
|
||||||
test/results
|
**/src/test/results
|
||||||
|
**/src/test/reporters
|
||||||
test/report
|
test/report
|
||||||
**/api/scripts
|
**/api/scripts
|
||||||
.sentryclirc
|
.sentryclirc
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ const config: PlaywrightTestConfig = {
|
|||||||
baseURL: process.env.NEXTAUTH_URL,
|
baseURL: process.env.NEXTAUTH_URL,
|
||||||
storageState: path.join(__dirname, 'src/test/storageState.json'),
|
storageState: path.join(__dirname, 'src/test/storageState.json'),
|
||||||
},
|
},
|
||||||
outputDir: path.join(__dirname, 'src/test/results/'),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default config
|
export default config
|
||||||
|
|||||||
@@ -25,5 +25,19 @@ module.exports = {
|
|||||||
plugins: ['react', '@typescript-eslint'],
|
plugins: ['react', '@typescript-eslint'],
|
||||||
rules: {
|
rules: {
|
||||||
'react/no-unescaped-entities': [0],
|
'react/no-unescaped-entities': [0],
|
||||||
|
'react/display-name': [0],
|
||||||
|
'no-restricted-imports': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
patterns: [
|
||||||
|
'*/src/*',
|
||||||
|
'src/*',
|
||||||
|
'*/src',
|
||||||
|
'@/features/*/*',
|
||||||
|
'!@/features/blocks/*',
|
||||||
|
'!@/features/*/api',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { playwrightBaseConfig } from 'configs/playwright'
|
|||||||
|
|
||||||
const config: PlaywrightTestConfig = {
|
const config: PlaywrightTestConfig = {
|
||||||
...playwrightBaseConfig,
|
...playwrightBaseConfig,
|
||||||
testDir: path.join(__dirname, 'playwright/tests'),
|
testDir: path.join(__dirname, 'src'),
|
||||||
webServer: process.env.CI
|
webServer: process.env.CI
|
||||||
? {
|
? {
|
||||||
...(playwrightBaseConfig.webServer as { command: string }),
|
...(playwrightBaseConfig.webServer as { command: string }),
|
||||||
@@ -15,7 +15,6 @@ const config: PlaywrightTestConfig = {
|
|||||||
...playwrightBaseConfig.use,
|
...playwrightBaseConfig.use,
|
||||||
baseURL: process.env.NEXT_PUBLIC_VIEWER_URL,
|
baseURL: process.env.NEXT_PUBLIC_VIEWER_URL,
|
||||||
},
|
},
|
||||||
outputDir: path.join(__dirname, 'playwright/test-results/'),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default config
|
export default config
|
||||||
|
|||||||
@@ -1,137 +0,0 @@
|
|||||||
import test, { expect } from '@playwright/test'
|
|
||||||
import path from 'path'
|
|
||||||
import {
|
|
||||||
importTypebotInDatabase,
|
|
||||||
createWebhook,
|
|
||||||
injectFakeResults,
|
|
||||||
} from 'utils/playwright/databaseActions'
|
|
||||||
import { apiToken } from 'utils/playwright/databaseSetup'
|
|
||||||
|
|
||||||
const typebotId = 'webhook-flow'
|
|
||||||
test.beforeAll(async () => {
|
|
||||||
try {
|
|
||||||
await importTypebotInDatabase(
|
|
||||||
path.join(__dirname, '../fixtures/typebots/api.json'),
|
|
||||||
{ id: typebotId }
|
|
||||||
)
|
|
||||||
await createWebhook(typebotId)
|
|
||||||
await injectFakeResults({ typebotId, count: 20 })
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
test('can list typebots', async ({ request }) => {
|
|
||||||
expect((await request.get(`/api/typebots`)).status()).toBe(401)
|
|
||||||
const response = await request.get(`/api/typebots`, {
|
|
||||||
headers: { Authorization: `Bearer ${apiToken}` },
|
|
||||||
})
|
|
||||||
const { typebots } = (await response.json()) as { typebots: unknown[] }
|
|
||||||
expect(typebots.length).toBeGreaterThanOrEqual(1)
|
|
||||||
expect(typebots[0]).toMatchObject({
|
|
||||||
id: typebotId,
|
|
||||||
publishedTypebotId: null,
|
|
||||||
name: 'My typebot',
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('can get webhook blocks', async ({ request }) => {
|
|
||||||
expect(
|
|
||||||
(await request.get(`/api/typebots/${typebotId}/webhookBlocks`)).status()
|
|
||||||
).toBe(401)
|
|
||||||
const response = await request.get(
|
|
||||||
`/api/typebots/${typebotId}/webhookBlocks`,
|
|
||||||
{
|
|
||||||
headers: { Authorization: `Bearer ${apiToken}` },
|
|
||||||
}
|
|
||||||
)
|
|
||||||
const { blocks } = await response.json()
|
|
||||||
expect(blocks).toHaveLength(1)
|
|
||||||
expect(blocks[0]).toEqual({
|
|
||||||
blockId: 'webhookBlock',
|
|
||||||
name: 'Webhook > webhookBlock',
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('can subscribe webhook', async ({ request }) => {
|
|
||||||
expect(
|
|
||||||
(
|
|
||||||
await request.post(
|
|
||||||
`/api/typebots/${typebotId}/blocks/webhookBlock/subscribeWebhook`,
|
|
||||||
{ data: { url: 'https://test.com' } }
|
|
||||||
)
|
|
||||||
).status()
|
|
||||||
).toBe(401)
|
|
||||||
const response = await request.post(
|
|
||||||
`/api/typebots/${typebotId}/blocks/webhookBlock/subscribeWebhook`,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${apiToken}`,
|
|
||||||
},
|
|
||||||
data: { url: 'https://test.com' },
|
|
||||||
}
|
|
||||||
)
|
|
||||||
const body = await response.json()
|
|
||||||
expect(body).toEqual({
|
|
||||||
message: 'success',
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('can unsubscribe webhook', async ({ request }) => {
|
|
||||||
expect(
|
|
||||||
(
|
|
||||||
await request.post(
|
|
||||||
`/api/typebots/${typebotId}/blocks/webhookBlock/unsubscribeWebhook`
|
|
||||||
)
|
|
||||||
).status()
|
|
||||||
).toBe(401)
|
|
||||||
const response = await request.post(
|
|
||||||
`/api/typebots/${typebotId}/blocks/webhookBlock/unsubscribeWebhook`,
|
|
||||||
{
|
|
||||||
headers: { Authorization: `Bearer ${apiToken}` },
|
|
||||||
}
|
|
||||||
)
|
|
||||||
const body = await response.json()
|
|
||||||
expect(body).toEqual({
|
|
||||||
message: 'success',
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('can get a sample result', async ({ request }) => {
|
|
||||||
expect(
|
|
||||||
(
|
|
||||||
await request.get(
|
|
||||||
`/api/typebots/${typebotId}/blocks/webhookBlock/sampleResult`
|
|
||||||
)
|
|
||||||
).status()
|
|
||||||
).toBe(401)
|
|
||||||
const response = await request.get(
|
|
||||||
`/api/typebots/${typebotId}/blocks/webhookBlock/sampleResult`,
|
|
||||||
{
|
|
||||||
headers: { Authorization: `Bearer ${apiToken}` },
|
|
||||||
}
|
|
||||||
)
|
|
||||||
const data = await response.json()
|
|
||||||
expect(data).toMatchObject({
|
|
||||||
message: 'This is a sample result, it has been generated ⬇️',
|
|
||||||
Welcome: 'Hi!',
|
|
||||||
Email: 'test@email.com',
|
|
||||||
Name: 'answer value',
|
|
||||||
Services: 'Website dev, Content Marketing, Social Media, UI / UX Design',
|
|
||||||
'Additional information': 'answer value',
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('can list results', async ({ request }) => {
|
|
||||||
expect(
|
|
||||||
(await request.get(`/api/typebots/${typebotId}/results`)).status()
|
|
||||||
).toBe(401)
|
|
||||||
const response = await request.get(
|
|
||||||
`/api/typebots/${typebotId}/results?limit=10`,
|
|
||||||
{
|
|
||||||
headers: { Authorization: `Bearer ${apiToken}` },
|
|
||||||
}
|
|
||||||
)
|
|
||||||
const { results } = await response.json()
|
|
||||||
expect(results).toHaveLength(10)
|
|
||||||
})
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
import test, { expect } from '@playwright/test'
|
|
||||||
import {
|
|
||||||
defaultSettings,
|
|
||||||
defaultTextInputOptions,
|
|
||||||
InputBlockType,
|
|
||||||
Metadata,
|
|
||||||
} from 'models'
|
|
||||||
import cuid from 'cuid'
|
|
||||||
import { createTypebots } from 'utils/playwright/databaseActions'
|
|
||||||
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
|
|
||||||
import { typebotViewer } from 'utils/playwright/testHelpers'
|
|
||||||
|
|
||||||
test('Should correctly parse metadata', async ({ page }) => {
|
|
||||||
const typebotId = cuid()
|
|
||||||
const customMetadata: Metadata = {
|
|
||||||
description: 'My custom description',
|
|
||||||
title: 'Custom title',
|
|
||||||
favIconUrl: 'https://www.baptistearno.com/favicon.png',
|
|
||||||
imageUrl: 'https://www.baptistearno.com/images/site-preview.png',
|
|
||||||
customHeadCode: '<meta name="author" content="John Doe">',
|
|
||||||
}
|
|
||||||
await createTypebots([
|
|
||||||
{
|
|
||||||
id: typebotId,
|
|
||||||
settings: {
|
|
||||||
...defaultSettings,
|
|
||||||
metadata: customMetadata,
|
|
||||||
},
|
|
||||||
...parseDefaultGroupWithBlock({
|
|
||||||
type: InputBlockType.TEXT,
|
|
||||||
options: defaultTextInputOptions,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
])
|
|
||||||
await page.goto(`/${typebotId}-public`)
|
|
||||||
expect(
|
|
||||||
await page.evaluate(`document.querySelector('title').textContent`)
|
|
||||||
).toBe(customMetadata.title)
|
|
||||||
expect(
|
|
||||||
await page.evaluate(
|
|
||||||
() => (document.querySelector('meta[name="description"]') as any).content
|
|
||||||
)
|
|
||||||
).toBe(customMetadata.description)
|
|
||||||
expect(
|
|
||||||
await page.evaluate(
|
|
||||||
() => (document.querySelector('meta[property="og:image"]') as any).content
|
|
||||||
)
|
|
||||||
).toBe(customMetadata.imageUrl)
|
|
||||||
expect(
|
|
||||||
await page.evaluate(() =>
|
|
||||||
(document.querySelector('link[rel="icon"]') as any).getAttribute('href')
|
|
||||||
)
|
|
||||||
).toBe(customMetadata.favIconUrl)
|
|
||||||
expect(
|
|
||||||
await page.evaluate(
|
|
||||||
() => (document.querySelector('meta[name="author"]') as any).content
|
|
||||||
)
|
|
||||||
).toBe('John Doe')
|
|
||||||
await expect(
|
|
||||||
typebotViewer(page).locator(
|
|
||||||
`input[placeholder="${defaultTextInputOptions.labels.placeholder}"]`
|
|
||||||
)
|
|
||||||
).toBeVisible()
|
|
||||||
})
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
import test, { expect } from '@playwright/test'
|
|
||||||
import cuid from 'cuid'
|
|
||||||
import path from 'path'
|
|
||||||
import { HttpMethod } from 'models'
|
|
||||||
import {
|
|
||||||
createWebhook,
|
|
||||||
deleteTypebots,
|
|
||||||
deleteWebhooks,
|
|
||||||
importTypebotInDatabase,
|
|
||||||
} from 'utils/playwright/databaseActions'
|
|
||||||
import { typebotViewer } from 'utils/playwright/testHelpers'
|
|
||||||
|
|
||||||
const typebotId = cuid()
|
|
||||||
|
|
||||||
test.beforeEach(async () => {
|
|
||||||
await importTypebotInDatabase(
|
|
||||||
path.join(__dirname, '../fixtures/typebots/webhook.json'),
|
|
||||||
{ id: typebotId, publicId: `${typebotId}-public` }
|
|
||||||
)
|
|
||||||
|
|
||||||
await createWebhook(typebotId, {
|
|
||||||
id: 'failing-webhook',
|
|
||||||
url: 'http://localhost:3001/api/mock/fail',
|
|
||||||
method: HttpMethod.POST,
|
|
||||||
})
|
|
||||||
|
|
||||||
await createWebhook(typebotId, {
|
|
||||||
id: 'partial-body-webhook',
|
|
||||||
url: 'http://localhost:3000/api/mock/webhook-easy-config',
|
|
||||||
method: HttpMethod.POST,
|
|
||||||
body: `{
|
|
||||||
"name": "{{Name}}",
|
|
||||||
"age": {{Age}},
|
|
||||||
"gender": "{{Gender}}"
|
|
||||||
}`,
|
|
||||||
})
|
|
||||||
|
|
||||||
await createWebhook(typebotId, {
|
|
||||||
id: 'full-body-webhook',
|
|
||||||
url: 'http://localhost:3000/api/mock/webhook-easy-config',
|
|
||||||
method: HttpMethod.POST,
|
|
||||||
body: `{{Full body}}`,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test.afterEach(async () => {
|
|
||||||
await deleteTypebots([typebotId])
|
|
||||||
await deleteWebhooks([
|
|
||||||
'failing-webhook',
|
|
||||||
'partial-body-webhook',
|
|
||||||
'full-body-webhook',
|
|
||||||
])
|
|
||||||
})
|
|
||||||
|
|
||||||
test('should execute webhooks properly', async ({ page }) => {
|
|
||||||
await page.goto(`/${typebotId}-public`)
|
|
||||||
await typebotViewer(page).locator('text=Send failing webhook').click()
|
|
||||||
await typebotViewer(page)
|
|
||||||
.locator('[placeholder="Type a name..."]')
|
|
||||||
.fill('John')
|
|
||||||
await typebotViewer(page).locator('text="Send"').click()
|
|
||||||
await typebotViewer(page).locator('[placeholder="Type an age..."]').fill('30')
|
|
||||||
await typebotViewer(page).locator('text="Send"').click()
|
|
||||||
await typebotViewer(page).locator('text="Male"').click()
|
|
||||||
await expect(
|
|
||||||
typebotViewer(page).getByText('{"name":"John","age":25,"gender":"male"}')
|
|
||||||
).toBeVisible()
|
|
||||||
await expect(
|
|
||||||
typebotViewer(page).getByText('{"name":"John","age":30,"gender":"Male"}')
|
|
||||||
).toBeVisible()
|
|
||||||
await page.goto(`http://localhost:3000/typebots/${typebotId}/results`)
|
|
||||||
await page.click('text="See logs"')
|
|
||||||
await expect(
|
|
||||||
page.locator('text="Webhook successfuly executed." >> nth=1')
|
|
||||||
).toBeVisible()
|
|
||||||
await expect(page.locator('text="Webhook returned an error"')).toBeVisible()
|
|
||||||
})
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
import { User } from 'db'
|
|
||||||
import prisma from 'libs/prisma'
|
|
||||||
import {
|
|
||||||
LogicBlockType,
|
|
||||||
Typebot,
|
|
||||||
TypebotLinkBlock,
|
|
||||||
PublicTypebot,
|
|
||||||
} from 'models'
|
|
||||||
import { NextApiRequest } from 'next'
|
|
||||||
import { isDefined } from 'utils'
|
|
||||||
import { canReadTypebots } from './dbRules'
|
|
||||||
|
|
||||||
export const authenticateUser = async (
|
|
||||||
req: NextApiRequest
|
|
||||||
): Promise<User | undefined> => authenticateByToken(extractBearerToken(req))
|
|
||||||
|
|
||||||
const authenticateByToken = async (
|
|
||||||
apiToken?: string
|
|
||||||
): Promise<User | undefined> => {
|
|
||||||
if (!apiToken) return
|
|
||||||
return (await prisma.user.findFirst({
|
|
||||||
where: { apiTokens: { some: { token: apiToken } } },
|
|
||||||
})) as User
|
|
||||||
}
|
|
||||||
|
|
||||||
const extractBearerToken = (req: NextApiRequest) =>
|
|
||||||
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 || resultId === 'undefined') 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getLinkedTypebots = async (
|
|
||||||
typebot: Typebot | PublicTypebot,
|
|
||||||
user?: User
|
|
||||||
): Promise<(Typebot | PublicTypebot)[]> => {
|
|
||||||
const linkedTypebotIds = (
|
|
||||||
typebot.groups
|
|
||||||
.flatMap((g) => g.blocks)
|
|
||||||
.filter(
|
|
||||||
(s) =>
|
|
||||||
s.type === LogicBlockType.TYPEBOT_LINK &&
|
|
||||||
isDefined(s.options.typebotId)
|
|
||||||
) as TypebotLinkBlock[]
|
|
||||||
).map((s) => s.options.typebotId as string)
|
|
||||||
if (linkedTypebotIds.length === 0) return []
|
|
||||||
const typebots = (await ('typebotId' in typebot
|
|
||||||
? prisma.publicTypebot.findMany({
|
|
||||||
where: { id: { in: linkedTypebotIds } },
|
|
||||||
})
|
|
||||||
: prisma.typebot.findMany({
|
|
||||||
where: user
|
|
||||||
? {
|
|
||||||
AND: [
|
|
||||||
{ id: { in: linkedTypebotIds } },
|
|
||||||
canReadTypebots(linkedTypebotIds, user as User),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
: { id: { in: linkedTypebotIds } },
|
|
||||||
}))) as unknown as (Typebot | PublicTypebot)[]
|
|
||||||
return typebots
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import { Result } from 'db'
|
|
||||||
import { sendRequest } from 'utils'
|
|
||||||
|
|
||||||
export const createResult = async (typebotId: string) => {
|
|
||||||
return sendRequest<{ result: Result; hasReachedLimit: boolean }>({
|
|
||||||
url: `/api/typebots/${typebotId}/results`,
|
|
||||||
method: 'POST',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const updateResult = async (resultId: string, result: Partial<Result>) =>
|
|
||||||
sendRequest<Result>({
|
|
||||||
url: `/api/typebots/t/results/${resultId}`,
|
|
||||||
method: 'PATCH',
|
|
||||||
body: result,
|
|
||||||
})
|
|
||||||
@@ -2,11 +2,11 @@ import { TypebotViewer } from 'bot-engine'
|
|||||||
import { Answer, PublicTypebot, Typebot, VariableWithValue } from 'models'
|
import { Answer, PublicTypebot, Typebot, VariableWithValue } from 'models'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { upsertAnswer } from 'services/answer'
|
|
||||||
import { isDefined, isNotDefined } from 'utils'
|
import { isDefined, isNotDefined } from 'utils'
|
||||||
import { SEO } from '../components/Seo'
|
import { SEO } from './Seo'
|
||||||
import { createResult, updateResult } from '../services/result'
|
|
||||||
import { ErrorPage } from './ErrorPage'
|
import { ErrorPage } from './ErrorPage'
|
||||||
|
import { createResultQuery, updateResultQuery } from '@/features/results'
|
||||||
|
import { upsertAnswerQuery } from '@/features/answers'
|
||||||
|
|
||||||
export type TypebotPageProps = {
|
export type TypebotPageProps = {
|
||||||
publishedTypebot: Omit<PublicTypebot, 'createdAt' | 'updatedAt'> & {
|
publishedTypebot: Omit<PublicTypebot, 'createdAt' | 'updatedAt'> & {
|
||||||
@@ -67,7 +67,9 @@ export const TypebotPage = ({
|
|||||||
const resultIdFromSession = getExistingResultFromSession()
|
const resultIdFromSession = getExistingResultFromSession()
|
||||||
if (resultIdFromSession) setResultId(resultIdFromSession)
|
if (resultIdFromSession) setResultId(resultIdFromSession)
|
||||||
else {
|
else {
|
||||||
const { error, data } = await createResult(publishedTypebot.typebotId)
|
const { error, data } = await createResultQuery(
|
||||||
|
publishedTypebot.typebotId
|
||||||
|
)
|
||||||
if (error) return setError(error)
|
if (error) return setError(error)
|
||||||
if (data?.hasReachedLimit)
|
if (data?.hasReachedLimit)
|
||||||
return setError(new Error('This bot is now closed.'))
|
return setError(new Error('This bot is now closed.'))
|
||||||
@@ -97,7 +99,7 @@ export const TypebotPage = ({
|
|||||||
|
|
||||||
const sendNewVariables =
|
const sendNewVariables =
|
||||||
(resultId: string) => async (variables: VariableWithValue[]) => {
|
(resultId: string) => async (variables: VariableWithValue[]) => {
|
||||||
const { error } = await updateResult(resultId, { variables })
|
const { error } = await updateResultQuery(resultId, { variables })
|
||||||
if (error) setError(error)
|
if (error) setError(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,17 +107,17 @@ export const TypebotPage = ({
|
|||||||
answer: Answer & { uploadedFiles: boolean }
|
answer: Answer & { uploadedFiles: boolean }
|
||||||
) => {
|
) => {
|
||||||
if (!resultId) return setError(new Error('Error: result was not created'))
|
if (!resultId) return setError(new Error('Error: result was not created'))
|
||||||
const { error } = await upsertAnswer({ ...answer, resultId })
|
const { error } = await upsertAnswerQuery({ ...answer, resultId })
|
||||||
if (error) setError(error)
|
if (error) setError(error)
|
||||||
if (chatStarted) return
|
if (chatStarted) return
|
||||||
updateResult(resultId, {
|
updateResultQuery(resultId, {
|
||||||
hasStarted: true,
|
hasStarted: true,
|
||||||
}).then(({ error }) => (error ? setError(error) : setChatStarted(true)))
|
}).then(({ error }) => (error ? setError(error) : setChatStarted(true)))
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCompleted = async () => {
|
const handleCompleted = async () => {
|
||||||
if (!resultId) return setError(new Error('Error: result was not created'))
|
if (!resultId) return setError(new Error('Error: result was not created'))
|
||||||
const { error } = await updateResult(resultId, { isCompleted: true })
|
const { error } = await updateResultQuery(resultId, { isCompleted: true })
|
||||||
if (error) setError(error)
|
if (error) setError(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
1
apps/viewer/src/features/answers/index.ts
Normal file
1
apps/viewer/src/features/answers/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { upsertAnswerQuery } from './queries/upsertAnswerQuery'
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Answer } from 'models'
|
import { Answer } from 'models'
|
||||||
import { sendRequest } from 'utils'
|
import { sendRequest } from 'utils'
|
||||||
|
|
||||||
export const upsertAnswer = async (
|
export const upsertAnswerQuery = async (
|
||||||
answer: Answer & { resultId: string } & { uploadedFiles?: boolean }
|
answer: Answer & { resultId: string } & { uploadedFiles?: boolean }
|
||||||
) =>
|
) =>
|
||||||
sendRequest<Answer>({
|
sendRequest<Answer>({
|
||||||
19
apps/viewer/src/features/auth/api/authenticateUser.ts
Normal file
19
apps/viewer/src/features/auth/api/authenticateUser.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { User } from 'db'
|
||||||
|
import { NextApiRequest } from 'next'
|
||||||
|
import prisma from '@/lib/prisma'
|
||||||
|
|
||||||
|
export const authenticateUser = async (
|
||||||
|
req: NextApiRequest
|
||||||
|
): Promise<User | undefined> => authenticateByToken(extractBearerToken(req))
|
||||||
|
|
||||||
|
const authenticateByToken = async (
|
||||||
|
apiToken?: string
|
||||||
|
): Promise<User | undefined> => {
|
||||||
|
if (!apiToken) return
|
||||||
|
return (await prisma.user.findFirst({
|
||||||
|
where: { apiTokens: { some: { token: apiToken } } },
|
||||||
|
})) as User
|
||||||
|
}
|
||||||
|
|
||||||
|
const extractBearerToken = (req: NextApiRequest) =>
|
||||||
|
req.headers['authorization']?.slice(7)
|
||||||
1
apps/viewer/src/features/auth/api/index.ts
Normal file
1
apps/viewer/src/features/auth/api/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { authenticateUser } from './authenticateUser'
|
||||||
@@ -1,27 +1,27 @@
|
|||||||
import test, { expect } from '@playwright/test'
|
import test, { expect } from '@playwright/test'
|
||||||
import cuid from 'cuid'
|
import cuid from 'cuid'
|
||||||
import path from 'path'
|
|
||||||
import { parse } from 'papaparse'
|
import { parse } from 'papaparse'
|
||||||
import { readFileSync } from 'fs'
|
import { readFileSync } from 'fs'
|
||||||
import { isDefined } from 'utils'
|
import { isDefined } from 'utils'
|
||||||
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
|
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
|
||||||
import { typebotViewer } from 'utils/playwright/testHelpers'
|
import { typebotViewer } from 'utils/playwright/testHelpers'
|
||||||
|
import { getTestAsset } from '@/test/utils/playwright'
|
||||||
|
|
||||||
const THREE_GIGABYTES = 3 * 1024 * 1024 * 1024
|
// const THREE_GIGABYTES = 3 * 1024 * 1024 * 1024
|
||||||
|
|
||||||
test('should work as expected', async ({ page, browser }) => {
|
test('should work as expected', async ({ page, browser }) => {
|
||||||
const typebotId = cuid()
|
const typebotId = cuid()
|
||||||
await importTypebotInDatabase(
|
await importTypebotInDatabase(getTestAsset('typebots/fileUpload.json'), {
|
||||||
path.join(__dirname, '../fixtures/typebots/fileUpload.json'),
|
id: typebotId,
|
||||||
{ id: typebotId, publicId: `${typebotId}-public` }
|
publicId: `${typebotId}-public`,
|
||||||
)
|
})
|
||||||
await page.goto(`/${typebotId}-public`)
|
await page.goto(`/${typebotId}-public`)
|
||||||
await typebotViewer(page)
|
await typebotViewer(page)
|
||||||
.locator(`input[type="file"]`)
|
.locator(`input[type="file"]`)
|
||||||
.setInputFiles([
|
.setInputFiles([
|
||||||
path.join(__dirname, '../fixtures/typebots/api.json'),
|
getTestAsset('typebots/api.json'),
|
||||||
path.join(__dirname, '../fixtures/typebots/fileUpload.json'),
|
getTestAsset('typebots/fileUpload.json'),
|
||||||
path.join(__dirname, '../fixtures/typebots/hugeGroup.json'),
|
getTestAsset('typebots/hugeGroup.json'),
|
||||||
])
|
])
|
||||||
await expect(typebotViewer(page).locator(`text="3"`)).toBeVisible()
|
await expect(typebotViewer(page).locator(`text="3"`)).toBeVisible()
|
||||||
await typebotViewer(page).locator('text="Upload 3 files"').click()
|
await typebotViewer(page).locator('text="Upload 3 files"').click()
|
||||||
@@ -29,18 +29,16 @@ test('should work as expected', async ({ page, browser }) => {
|
|||||||
typebotViewer(page).locator(`text="3 files uploaded"`)
|
typebotViewer(page).locator(`text="3 files uploaded"`)
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
await page.goto(`${process.env.NEXTAUTH_URL}/typebots/${typebotId}/results`)
|
await page.goto(`${process.env.NEXTAUTH_URL}/typebots/${typebotId}/results`)
|
||||||
await expect(page.locator('text="api.json"')).toHaveAttribute(
|
await expect(page.getByRole('link', { name: 'api.json' })).toHaveAttribute(
|
||||||
'href',
|
'href',
|
||||||
/.+\/api\.json/
|
/.+\/api\.json/
|
||||||
)
|
)
|
||||||
await expect(page.locator('text="fileUpload.json"')).toHaveAttribute(
|
await expect(
|
||||||
'href',
|
page.getByRole('link', { name: 'fileUpload.json' })
|
||||||
/.+\/fileUpload\.json/
|
).toHaveAttribute('href', /.+\/fileUpload\.json/)
|
||||||
)
|
await expect(
|
||||||
await expect(page.locator('text="hugeGroup.json"')).toHaveAttribute(
|
page.getByRole('link', { name: 'hugeGroup.json' })
|
||||||
'href',
|
).toHaveAttribute('href', /.+\/hugeGroup\.json/)
|
||||||
/.+\/hugeGroup\.json/
|
|
||||||
)
|
|
||||||
|
|
||||||
await page.click('[data-testid="checkbox"] >> nth=0')
|
await page.click('[data-testid="checkbox"] >> nth=0')
|
||||||
const [download] = await Promise.all([
|
const [download] = await Promise.all([
|
||||||
@@ -57,9 +55,9 @@ test('should work as expected', async ({ page, browser }) => {
|
|||||||
const urls = (
|
const urls = (
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
[
|
[
|
||||||
page.locator('text="api.json"'),
|
page.getByRole('link', { name: 'api.json' }),
|
||||||
page.locator('text="fileUpload.json"'),
|
page.getByRole('link', { name: 'fileUpload.json' }),
|
||||||
page.locator('text="hugeGroup.json"'),
|
page.getByRole('link', { name: 'hugeGroup.json' }),
|
||||||
].map((elem) => elem.getAttribute('href'))
|
].map((elem) => elem.getAttribute('href'))
|
||||||
)
|
)
|
||||||
).filter(isDefined)
|
).filter(isDefined)
|
||||||
2
apps/viewer/src/features/logs/api/index.ts
Normal file
2
apps/viewer/src/features/logs/api/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './saveErrorLog'
|
||||||
|
export * from './saveSuccessLog'
|
||||||
7
apps/viewer/src/features/logs/api/saveErrorLog.ts
Normal file
7
apps/viewer/src/features/logs/api/saveErrorLog.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { saveLog } from './utils'
|
||||||
|
|
||||||
|
export const saveErrorLog = (
|
||||||
|
resultId: string | undefined,
|
||||||
|
message: string,
|
||||||
|
details?: unknown
|
||||||
|
) => saveLog('error', resultId, message, details)
|
||||||
7
apps/viewer/src/features/logs/api/saveSuccessLog.ts
Normal file
7
apps/viewer/src/features/logs/api/saveSuccessLog.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { saveLog } from './utils'
|
||||||
|
|
||||||
|
export const saveSuccessLog = (
|
||||||
|
resultId: string | undefined,
|
||||||
|
message: string,
|
||||||
|
details?: unknown
|
||||||
|
) => saveLog('success', resultId, message, details)
|
||||||
26
apps/viewer/src/features/logs/api/utils.ts
Normal file
26
apps/viewer/src/features/logs/api/utils.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import prisma from '@/lib/prisma'
|
||||||
|
|
||||||
|
export const saveLog = (
|
||||||
|
status: 'error' | 'success',
|
||||||
|
resultId: string | undefined,
|
||||||
|
message: string,
|
||||||
|
details?: unknown
|
||||||
|
) => {
|
||||||
|
if (!resultId || resultId === 'undefined') return
|
||||||
|
return prisma.log.create({
|
||||||
|
data: {
|
||||||
|
resultId,
|
||||||
|
status,
|
||||||
|
description: message,
|
||||||
|
details: formatDetails(details) as string,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatDetails = (details: unknown) => {
|
||||||
|
try {
|
||||||
|
return JSON.stringify(details, null, 2).substring(0, 1000)
|
||||||
|
} catch {
|
||||||
|
return details
|
||||||
|
}
|
||||||
|
}
|
||||||
1
apps/viewer/src/features/results/index.ts
Normal file
1
apps/viewer/src/features/results/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { createResultQuery, updateResultQuery } from './queries'
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { Result } from 'models'
|
||||||
|
import { sendRequest } from 'utils'
|
||||||
|
|
||||||
|
export const createResultQuery = async (typebotId: string) => {
|
||||||
|
return sendRequest<{ result: Result; hasReachedLimit: boolean }>({
|
||||||
|
url: `/api/typebots/${typebotId}/results`,
|
||||||
|
method: 'POST',
|
||||||
|
})
|
||||||
|
}
|
||||||
2
apps/viewer/src/features/results/queries/index.ts
Normal file
2
apps/viewer/src/features/results/queries/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './createResultQuery'
|
||||||
|
export * from './updateResultQuery'
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { Result } from 'models'
|
||||||
|
import { sendRequest } from 'utils'
|
||||||
|
|
||||||
|
export const updateResultQuery = async (
|
||||||
|
resultId: string,
|
||||||
|
result: Partial<Result>
|
||||||
|
) =>
|
||||||
|
sendRequest<Result>({
|
||||||
|
url: `/api/typebots/t/results/${resultId}`,
|
||||||
|
method: 'PATCH',
|
||||||
|
body: result,
|
||||||
|
})
|
||||||
@@ -1,15 +1,19 @@
|
|||||||
|
import { getTestAsset } from '@/test/utils/playwright'
|
||||||
import test, { expect } from '@playwright/test'
|
import test, { expect } from '@playwright/test'
|
||||||
import path from 'path'
|
|
||||||
import cuid from 'cuid'
|
import cuid from 'cuid'
|
||||||
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
|
import {
|
||||||
|
importTypebotInDatabase,
|
||||||
|
injectFakeResults,
|
||||||
|
} from 'utils/playwright/databaseActions'
|
||||||
|
import { apiToken } from 'utils/playwright/databaseSetup'
|
||||||
import { typebotViewer } from 'utils/playwright/testHelpers'
|
import { typebotViewer } from 'utils/playwright/testHelpers'
|
||||||
|
|
||||||
test('should work as expected', async ({ page }) => {
|
test('Big groups should work as expected', async ({ page }) => {
|
||||||
const typebotId = cuid()
|
const typebotId = cuid()
|
||||||
await importTypebotInDatabase(
|
await importTypebotInDatabase(getTestAsset('typebots/hugeGroup.json'), {
|
||||||
path.join(__dirname, '../fixtures/typebots/hugeGroup.json'),
|
id: typebotId,
|
||||||
{ id: typebotId, publicId: `${typebotId}-public` }
|
publicId: `${typebotId}-public`,
|
||||||
)
|
})
|
||||||
await page.goto(`/${typebotId}-public`)
|
await page.goto(`/${typebotId}-public`)
|
||||||
await typebotViewer(page).locator('input').fill('Baptiste')
|
await typebotViewer(page).locator('input').fill('Baptiste')
|
||||||
await typebotViewer(page).locator('input').press('Enter')
|
await typebotViewer(page).locator('input').press('Enter')
|
||||||
@@ -26,3 +30,22 @@ test('should work as expected', async ({ page }) => {
|
|||||||
await expect(page.locator('text="26" >> nth=1')).toBeVisible()
|
await expect(page.locator('text="26" >> nth=1')).toBeVisible()
|
||||||
await expect(page.locator('text="Yes" >> nth=1')).toBeVisible()
|
await expect(page.locator('text="Yes" >> nth=1')).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('can list results with API', async ({ request }) => {
|
||||||
|
const typebotId = cuid()
|
||||||
|
await importTypebotInDatabase(getTestAsset('typebots/api.json'), {
|
||||||
|
id: typebotId,
|
||||||
|
})
|
||||||
|
await injectFakeResults({ typebotId, count: 20 })
|
||||||
|
expect(
|
||||||
|
(await request.get(`/api/typebots/${typebotId}/results`)).status()
|
||||||
|
).toBe(401)
|
||||||
|
const response = await request.get(
|
||||||
|
`/api/typebots/${typebotId}/results?limit=10`,
|
||||||
|
{
|
||||||
|
headers: { Authorization: `Bearer ${apiToken}` },
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const { results } = await response.json()
|
||||||
|
expect(results).toHaveLength(10)
|
||||||
|
})
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import test, { expect } from '@playwright/test'
|
import test, { expect } from '@playwright/test'
|
||||||
import { createSmtpCredentials } from '../services/databaseActions'
|
import { createSmtpCredentials } from '../../test/utils/databaseActions'
|
||||||
import cuid from 'cuid'
|
import cuid from 'cuid'
|
||||||
import path from 'path'
|
|
||||||
import { SmtpCredentialsData } from 'models'
|
import { SmtpCredentialsData } from 'models'
|
||||||
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
|
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
|
||||||
import { typebotViewer } from 'utils/playwright/testHelpers'
|
import { typebotViewer } from 'utils/playwright/testHelpers'
|
||||||
|
import { getTestAsset } from '@/test/utils/playwright'
|
||||||
|
|
||||||
const mockSmtpCredentials: SmtpCredentialsData = {
|
const mockSmtpCredentials: SmtpCredentialsData = {
|
||||||
from: {
|
from: {
|
||||||
@@ -28,10 +28,10 @@ test.beforeAll(async () => {
|
|||||||
|
|
||||||
test('should send an email', async ({ page }) => {
|
test('should send an email', async ({ page }) => {
|
||||||
const typebotId = cuid()
|
const typebotId = cuid()
|
||||||
await importTypebotInDatabase(
|
await importTypebotInDatabase(getTestAsset('typebots/sendEmail.json'), {
|
||||||
path.join(__dirname, '../fixtures/typebots/sendEmail.json'),
|
id: typebotId,
|
||||||
{ id: typebotId, publicId: `${typebotId}-public` }
|
publicId: `${typebotId}-public`,
|
||||||
)
|
})
|
||||||
await page.goto(`/${typebotId}-public`)
|
await page.goto(`/${typebotId}-public`)
|
||||||
const [response] = await Promise.all([
|
const [response] = await Promise.all([
|
||||||
page.waitForResponse((resp) =>
|
page.waitForResponse((resp) =>
|
||||||
@@ -4,9 +4,11 @@ import {
|
|||||||
defaultSettings,
|
defaultSettings,
|
||||||
defaultTextInputOptions,
|
defaultTextInputOptions,
|
||||||
InputBlockType,
|
InputBlockType,
|
||||||
|
Metadata,
|
||||||
} from 'models'
|
} from 'models'
|
||||||
import { createTypebots, updateTypebot } from 'utils/playwright/databaseActions'
|
import { createTypebots, updateTypebot } from 'utils/playwright/databaseActions'
|
||||||
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
|
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
|
||||||
|
import { typebotViewer } from 'utils/playwright/testHelpers'
|
||||||
|
|
||||||
test('Result should be in storage by default', async ({ page }) => {
|
test('Result should be in storage by default', async ({ page }) => {
|
||||||
const typebotId = cuid()
|
const typebotId = cuid()
|
||||||
@@ -120,3 +122,56 @@ test('Show close message', async ({ page }) => {
|
|||||||
await page.goto(`/${typebotId}-public`)
|
await page.goto(`/${typebotId}-public`)
|
||||||
await expect(page.locator('text=This bot is now closed')).toBeVisible()
|
await expect(page.locator('text=This bot is now closed')).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('Should correctly parse metadata', async ({ page }) => {
|
||||||
|
const typebotId = cuid()
|
||||||
|
const customMetadata: Metadata = {
|
||||||
|
description: 'My custom description',
|
||||||
|
title: 'Custom title',
|
||||||
|
favIconUrl: 'https://www.baptistearno.com/favicon.png',
|
||||||
|
imageUrl: 'https://www.baptistearno.com/images/site-preview.png',
|
||||||
|
customHeadCode: '<meta name="author" content="John Doe">',
|
||||||
|
}
|
||||||
|
await createTypebots([
|
||||||
|
{
|
||||||
|
id: typebotId,
|
||||||
|
settings: {
|
||||||
|
...defaultSettings,
|
||||||
|
metadata: customMetadata,
|
||||||
|
},
|
||||||
|
...parseDefaultGroupWithBlock({
|
||||||
|
type: InputBlockType.TEXT,
|
||||||
|
options: defaultTextInputOptions,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
await page.goto(`/${typebotId}-public`)
|
||||||
|
expect(
|
||||||
|
await page.evaluate(`document.querySelector('title').textContent`)
|
||||||
|
).toBe(customMetadata.title)
|
||||||
|
expect(
|
||||||
|
await page.evaluate(
|
||||||
|
() => (document.querySelector('meta[name="description"]') as any).content
|
||||||
|
)
|
||||||
|
).toBe(customMetadata.description)
|
||||||
|
expect(
|
||||||
|
await page.evaluate(
|
||||||
|
() => (document.querySelector('meta[property="og:image"]') as any).content
|
||||||
|
)
|
||||||
|
).toBe(customMetadata.imageUrl)
|
||||||
|
expect(
|
||||||
|
await page.evaluate(() =>
|
||||||
|
(document.querySelector('link[rel="icon"]') as any).getAttribute('href')
|
||||||
|
)
|
||||||
|
).toBe(customMetadata.favIconUrl)
|
||||||
|
expect(
|
||||||
|
await page.evaluate(
|
||||||
|
() => (document.querySelector('meta[name="author"]') as any).content
|
||||||
|
)
|
||||||
|
).toBe('John Doe')
|
||||||
|
await expect(
|
||||||
|
typebotViewer(page).locator(
|
||||||
|
`input[placeholder="${defaultTextInputOptions.labels.placeholder}"]`
|
||||||
|
)
|
||||||
|
).toBeVisible()
|
||||||
|
})
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import prisma from '@/lib/prisma'
|
||||||
|
import { canReadTypebots } from '@/utils/api/dbRules'
|
||||||
|
import { User } from 'db'
|
||||||
|
import {
|
||||||
|
LogicBlockType,
|
||||||
|
PublicTypebot,
|
||||||
|
Typebot,
|
||||||
|
TypebotLinkBlock,
|
||||||
|
} from 'models'
|
||||||
|
import { isDefined } from 'utils'
|
||||||
|
|
||||||
|
export const getLinkedTypebots = async (
|
||||||
|
typebot: Typebot | PublicTypebot,
|
||||||
|
user?: User
|
||||||
|
): Promise<(Typebot | PublicTypebot)[]> => {
|
||||||
|
const linkedTypebotIds = (
|
||||||
|
typebot.groups
|
||||||
|
.flatMap((g) => g.blocks)
|
||||||
|
.filter(
|
||||||
|
(s) =>
|
||||||
|
s.type === LogicBlockType.TYPEBOT_LINK &&
|
||||||
|
isDefined(s.options.typebotId)
|
||||||
|
) as TypebotLinkBlock[]
|
||||||
|
).map((s) => s.options.typebotId as string)
|
||||||
|
if (linkedTypebotIds.length === 0) return []
|
||||||
|
const typebots = (await ('typebotId' in typebot
|
||||||
|
? prisma.publicTypebot.findMany({
|
||||||
|
where: { id: { in: linkedTypebotIds } },
|
||||||
|
})
|
||||||
|
: prisma.typebot.findMany({
|
||||||
|
where: user
|
||||||
|
? {
|
||||||
|
AND: [
|
||||||
|
{ id: { in: linkedTypebotIds } },
|
||||||
|
canReadTypebots(linkedTypebotIds, user as User),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
: { id: { in: linkedTypebotIds } },
|
||||||
|
}))) as unknown as (Typebot | PublicTypebot)[]
|
||||||
|
return typebots
|
||||||
|
}
|
||||||
1
apps/viewer/src/features/typebotLink/api/index.ts
Normal file
1
apps/viewer/src/features/typebotLink/api/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './getLinkedTypebots'
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import { getTestAsset } from '@/test/utils/playwright'
|
||||||
import test, { expect } from '@playwright/test'
|
import test, { expect } from '@playwright/test'
|
||||||
import path from 'path'
|
|
||||||
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
|
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
|
||||||
import { typebotViewer } from 'utils/playwright/testHelpers'
|
import { typebotViewer } from 'utils/playwright/testHelpers'
|
||||||
|
|
||||||
@@ -9,11 +9,11 @@ const linkedTypebotId = 'cl0ibhv8d0130n21aw8doxhj5'
|
|||||||
test.beforeAll(async () => {
|
test.beforeAll(async () => {
|
||||||
try {
|
try {
|
||||||
await importTypebotInDatabase(
|
await importTypebotInDatabase(
|
||||||
path.join(__dirname, '../fixtures/typebots/linkTypebots/1.json'),
|
getTestAsset('typebots/linkTypebots/1.json'),
|
||||||
{ id: typebotId, publicId: `${typebotId}-public` }
|
{ id: typebotId, publicId: `${typebotId}-public` }
|
||||||
)
|
)
|
||||||
await importTypebotInDatabase(
|
await importTypebotInDatabase(
|
||||||
path.join(__dirname, '../fixtures/typebots/linkTypebots/2.json'),
|
getTestAsset('typebots/linkTypebots/2.json'),
|
||||||
{ id: linkedTypebotId, publicId: `${linkedTypebotId}-public` }
|
{ id: linkedTypebotId, publicId: `${linkedTypebotId}-public` }
|
||||||
)
|
)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import test, { expect } from '@playwright/test'
|
import test, { expect } from '@playwright/test'
|
||||||
import cuid from 'cuid'
|
import cuid from 'cuid'
|
||||||
import path from 'path'
|
|
||||||
|
|
||||||
// TODO: uncomment on 1st of November
|
// TODO: uncomment on 1st of November
|
||||||
|
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
|
import { getTestAsset } from '@/test/utils/playwright'
|
||||||
import test, { expect } from '@playwright/test'
|
import test, { expect } from '@playwright/test'
|
||||||
import cuid from 'cuid'
|
import cuid from 'cuid'
|
||||||
import path from 'path'
|
|
||||||
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
|
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
|
||||||
import { typebotViewer } from 'utils/playwright/testHelpers'
|
import { typebotViewer } from 'utils/playwright/testHelpers'
|
||||||
|
|
||||||
test('should correctly be injected', async ({ page }) => {
|
test('should correctly be injected', async ({ page }) => {
|
||||||
const typebotId = cuid()
|
const typebotId = cuid()
|
||||||
await importTypebotInDatabase(
|
await importTypebotInDatabase(
|
||||||
path.join(__dirname, '../fixtures/typebots/predefinedVariables.json'),
|
getTestAsset('typebots/predefinedVariables.json'),
|
||||||
{ id: typebotId, publicId: `${typebotId}-public` }
|
{ id: typebotId, publicId: `${typebotId}-public` }
|
||||||
)
|
)
|
||||||
await page.goto(`/${typebotId}-public`)
|
await page.goto(`/${typebotId}-public`)
|
||||||
1
apps/viewer/src/features/webhook/api/index.ts
Normal file
1
apps/viewer/src/features/webhook/api/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './parseSampleResult'
|
||||||
198
apps/viewer/src/features/webhook/webhook.spec.ts
Normal file
198
apps/viewer/src/features/webhook/webhook.spec.ts
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
import test, { expect } from '@playwright/test'
|
||||||
|
import cuid from 'cuid'
|
||||||
|
import { HttpMethod } from 'models'
|
||||||
|
import {
|
||||||
|
createWebhook,
|
||||||
|
deleteTypebots,
|
||||||
|
deleteWebhooks,
|
||||||
|
importTypebotInDatabase,
|
||||||
|
} from 'utils/playwright/databaseActions'
|
||||||
|
import { typebotViewer } from 'utils/playwright/testHelpers'
|
||||||
|
import { apiToken } from 'utils/playwright/databaseSetup'
|
||||||
|
import { getTestAsset } from '@/test/utils/playwright'
|
||||||
|
|
||||||
|
test.describe('Bot', () => {
|
||||||
|
const typebotId = cuid()
|
||||||
|
|
||||||
|
test.beforeEach(async () => {
|
||||||
|
await importTypebotInDatabase(getTestAsset('typebots/webhook.json'), {
|
||||||
|
id: typebotId,
|
||||||
|
publicId: `${typebotId}-public`,
|
||||||
|
})
|
||||||
|
|
||||||
|
await createWebhook(typebotId, {
|
||||||
|
id: 'failing-webhook',
|
||||||
|
url: 'http://localhost:3001/api/mock/fail',
|
||||||
|
method: HttpMethod.POST,
|
||||||
|
})
|
||||||
|
|
||||||
|
await createWebhook(typebotId, {
|
||||||
|
id: 'partial-body-webhook',
|
||||||
|
url: 'http://localhost:3000/api/mock/webhook-easy-config',
|
||||||
|
method: HttpMethod.POST,
|
||||||
|
body: `{
|
||||||
|
"name": "{{Name}}",
|
||||||
|
"age": {{Age}},
|
||||||
|
"gender": "{{Gender}}"
|
||||||
|
}`,
|
||||||
|
})
|
||||||
|
|
||||||
|
await createWebhook(typebotId, {
|
||||||
|
id: 'full-body-webhook',
|
||||||
|
url: 'http://localhost:3000/api/mock/webhook-easy-config',
|
||||||
|
method: HttpMethod.POST,
|
||||||
|
body: `{{Full body}}`,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test.afterEach(async () => {
|
||||||
|
await deleteTypebots([typebotId])
|
||||||
|
await deleteWebhooks([
|
||||||
|
'failing-webhook',
|
||||||
|
'partial-body-webhook',
|
||||||
|
'full-body-webhook',
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should execute webhooks properly', async ({ page }) => {
|
||||||
|
await page.goto(`/${typebotId}-public`)
|
||||||
|
await typebotViewer(page).locator('text=Send failing webhook').click()
|
||||||
|
await typebotViewer(page)
|
||||||
|
.locator('[placeholder="Type a name..."]')
|
||||||
|
.fill('John')
|
||||||
|
await typebotViewer(page).locator('text="Send"').click()
|
||||||
|
await typebotViewer(page)
|
||||||
|
.locator('[placeholder="Type an age..."]')
|
||||||
|
.fill('30')
|
||||||
|
await typebotViewer(page).locator('text="Send"').click()
|
||||||
|
await typebotViewer(page).locator('text="Male"').click()
|
||||||
|
await expect(
|
||||||
|
typebotViewer(page).getByText('{"name":"John","age":25,"gender":"male"}')
|
||||||
|
).toBeVisible()
|
||||||
|
await expect(
|
||||||
|
typebotViewer(page).getByText('{"name":"John","age":30,"gender":"Male"}')
|
||||||
|
).toBeVisible()
|
||||||
|
await page.goto(`http://localhost:3000/typebots/${typebotId}/results`)
|
||||||
|
await page.click('text="See logs"')
|
||||||
|
await expect(
|
||||||
|
page.locator('text="Webhook successfuly executed." >> nth=1')
|
||||||
|
).toBeVisible()
|
||||||
|
await expect(page.locator('text="Webhook returned an error"')).toBeVisible()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test.describe('API', () => {
|
||||||
|
const typebotId = 'webhook-flow'
|
||||||
|
|
||||||
|
test.beforeAll(async () => {
|
||||||
|
try {
|
||||||
|
await importTypebotInDatabase(getTestAsset('typebots/api.json'), {
|
||||||
|
id: typebotId,
|
||||||
|
})
|
||||||
|
await createWebhook(typebotId)
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test('can list typebots', async ({ request }) => {
|
||||||
|
expect((await request.get(`/api/typebots`)).status()).toBe(401)
|
||||||
|
const response = await request.get(`/api/typebots`, {
|
||||||
|
headers: { Authorization: `Bearer ${apiToken}` },
|
||||||
|
})
|
||||||
|
const { typebots } = (await response.json()) as { typebots: unknown[] }
|
||||||
|
expect(typebots.length).toBeGreaterThanOrEqual(1)
|
||||||
|
expect(typebots[0]).toMatchObject({
|
||||||
|
id: typebotId,
|
||||||
|
publishedTypebotId: null,
|
||||||
|
name: 'My typebot',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('can get webhook blocks', async ({ request }) => {
|
||||||
|
expect(
|
||||||
|
(await request.get(`/api/typebots/${typebotId}/webhookBlocks`)).status()
|
||||||
|
).toBe(401)
|
||||||
|
const response = await request.get(
|
||||||
|
`/api/typebots/${typebotId}/webhookBlocks`,
|
||||||
|
{
|
||||||
|
headers: { Authorization: `Bearer ${apiToken}` },
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const { blocks } = await response.json()
|
||||||
|
expect(blocks).toHaveLength(1)
|
||||||
|
expect(blocks[0]).toEqual({
|
||||||
|
blockId: 'webhookBlock',
|
||||||
|
name: 'Webhook > webhookBlock',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('can subscribe webhook', async ({ request }) => {
|
||||||
|
expect(
|
||||||
|
(
|
||||||
|
await request.post(
|
||||||
|
`/api/typebots/${typebotId}/blocks/webhookBlock/subscribeWebhook`,
|
||||||
|
{ data: { url: 'https://test.com' } }
|
||||||
|
)
|
||||||
|
).status()
|
||||||
|
).toBe(401)
|
||||||
|
const response = await request.post(
|
||||||
|
`/api/typebots/${typebotId}/blocks/webhookBlock/subscribeWebhook`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${apiToken}`,
|
||||||
|
},
|
||||||
|
data: { url: 'https://test.com' },
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const body = await response.json()
|
||||||
|
expect(body).toEqual({
|
||||||
|
message: 'success',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('can unsubscribe webhook', async ({ request }) => {
|
||||||
|
expect(
|
||||||
|
(
|
||||||
|
await request.post(
|
||||||
|
`/api/typebots/${typebotId}/blocks/webhookBlock/unsubscribeWebhook`
|
||||||
|
)
|
||||||
|
).status()
|
||||||
|
).toBe(401)
|
||||||
|
const response = await request.post(
|
||||||
|
`/api/typebots/${typebotId}/blocks/webhookBlock/unsubscribeWebhook`,
|
||||||
|
{
|
||||||
|
headers: { Authorization: `Bearer ${apiToken}` },
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const body = await response.json()
|
||||||
|
expect(body).toEqual({
|
||||||
|
message: 'success',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('can get a sample result', async ({ request }) => {
|
||||||
|
expect(
|
||||||
|
(
|
||||||
|
await request.get(
|
||||||
|
`/api/typebots/${typebotId}/blocks/webhookBlock/sampleResult`
|
||||||
|
)
|
||||||
|
).status()
|
||||||
|
).toBe(401)
|
||||||
|
const response = await request.get(
|
||||||
|
`/api/typebots/${typebotId}/blocks/webhookBlock/sampleResult`,
|
||||||
|
{
|
||||||
|
headers: { Authorization: `Bearer ${apiToken}` },
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const data = await response.json()
|
||||||
|
expect(data).toMatchObject({
|
||||||
|
message: 'This is a sample result, it has been generated ⬇️',
|
||||||
|
Welcome: 'Hi!',
|
||||||
|
Email: 'test@email.com',
|
||||||
|
Name: 'answer value',
|
||||||
|
Services: 'Website dev, Content Marketing, Social Media, UI / UX Design',
|
||||||
|
'Additional information': 'answer value',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import { IncomingMessage } from 'http'
|
import { IncomingMessage } from 'http'
|
||||||
import { ErrorPage } from 'layouts/ErrorPage'
|
import { ErrorPage } from '@/components/ErrorPage'
|
||||||
import { NotFoundPage } from 'layouts/NotFoundPage'
|
import { NotFoundPage } from '@/components/NotFoundPage'
|
||||||
import { GetServerSideProps, GetServerSidePropsContext } from 'next'
|
import { GetServerSideProps, GetServerSidePropsContext } from 'next'
|
||||||
import sanitizeHtml from 'sanitize-html'
|
import sanitizeHtml from 'sanitize-html'
|
||||||
import { env, getViewerUrl, isDefined, isNotDefined, omit } from 'utils'
|
import { env, getViewerUrl, isDefined, isNotDefined, omit } from 'utils'
|
||||||
import { TypebotPage, TypebotPageProps } from '../layouts/TypebotPage'
|
import { TypebotPage, TypebotPageProps } from '../components/TypebotPage'
|
||||||
import prisma from '../libs/prisma'
|
import prisma from '../lib/prisma'
|
||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps = async (
|
export const getServerSideProps: GetServerSideProps = async (
|
||||||
context: GetServerSidePropsContext
|
context: GetServerSidePropsContext
|
||||||
@@ -2,11 +2,11 @@ import { NextApiRequest, NextApiResponse } from 'next'
|
|||||||
import { badRequest, initMiddleware, methodNotAllowed } from 'utils/api'
|
import { badRequest, initMiddleware, methodNotAllowed } from 'utils/api'
|
||||||
import { hasValue } from 'utils'
|
import { hasValue } from 'utils'
|
||||||
import { GoogleSpreadsheet } from 'google-spreadsheet'
|
import { GoogleSpreadsheet } from 'google-spreadsheet'
|
||||||
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'
|
import { getAuthenticatedGoogleClient } from '@/lib/google-sheets'
|
||||||
|
import { saveErrorLog, saveSuccessLog } from '@/features/logs/api'
|
||||||
|
|
||||||
const cors = initMiddleware(Cors())
|
const cors = initMiddleware(Cors())
|
||||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
@@ -11,7 +11,7 @@ import Stripe from 'stripe'
|
|||||||
import Cors from 'cors'
|
import Cors from 'cors'
|
||||||
import { withSentry } from '@sentry/nextjs'
|
import { withSentry } from '@sentry/nextjs'
|
||||||
import { PaymentInputOptions, StripeCredentialsData, Variable } from 'models'
|
import { PaymentInputOptions, StripeCredentialsData, Variable } from 'models'
|
||||||
import prisma from 'libs/prisma'
|
import prisma from '@/lib/prisma'
|
||||||
import { parseVariables } from 'bot-engine'
|
import { parseVariables } from 'bot-engine'
|
||||||
|
|
||||||
const cors = initMiddleware(Cors())
|
const cors = initMiddleware(Cors())
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { withSentry } from '@sentry/nextjs'
|
import { withSentry } from '@sentry/nextjs'
|
||||||
import prisma from 'libs/prisma'
|
import prisma from '@/lib/prisma'
|
||||||
import { NextApiRequest, NextApiResponse } from 'next'
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
import Cors from 'cors'
|
import Cors from 'cors'
|
||||||
import { initMiddleware, methodNotAllowed, notFound } from 'utils/api'
|
import { initMiddleware, methodNotAllowed, notFound } from 'utils/api'
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
|
import { authenticateUser } from '@/features/auth/api'
|
||||||
|
import prisma from '@/lib/prisma'
|
||||||
import { withSentry } from '@sentry/nextjs'
|
import { withSentry } from '@sentry/nextjs'
|
||||||
import prisma from 'libs/prisma'
|
|
||||||
import { NextApiRequest, NextApiResponse } from 'next'
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
import { authenticateUser } from 'services/api/utils'
|
|
||||||
import { methodNotAllowed } from 'utils/api'
|
import { methodNotAllowed } from 'utils/api'
|
||||||
|
|
||||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import prisma from 'libs/prisma'
|
|
||||||
import {
|
import {
|
||||||
defaultWebhookAttributes,
|
defaultWebhookAttributes,
|
||||||
KeyValue,
|
KeyValue,
|
||||||
@@ -20,12 +19,10 @@ import { initMiddleware, methodNotAllowed, notFound } from 'utils/api'
|
|||||||
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 prisma from '@/lib/prisma'
|
||||||
import {
|
import { getLinkedTypebots } from '@/features/typebotLink/api'
|
||||||
getLinkedTypebots,
|
import { saveErrorLog, saveSuccessLog } from '@/features/logs/api'
|
||||||
saveErrorLog,
|
import { parseSampleResult } from '@/features/webhook/api'
|
||||||
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) => {
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
import prisma from 'libs/prisma'
|
import { authenticateUser } from '@/features/auth/api'
|
||||||
|
import { getLinkedTypebots } from '@/features/typebotLink/api'
|
||||||
|
import { parseSampleResult } from '@/features/webhook/api'
|
||||||
|
import prisma from '@/lib/prisma'
|
||||||
import { Typebot } from 'models'
|
import { Typebot } from 'models'
|
||||||
import { NextApiRequest, NextApiResponse } from 'next'
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
import { authenticateUser, getLinkedTypebots } from 'services/api/utils'
|
|
||||||
import { parseSampleResult } from 'services/api/webhooks'
|
|
||||||
import { methodNotAllowed } from 'utils/api'
|
import { methodNotAllowed } from 'utils/api'
|
||||||
|
|
||||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import prisma from 'libs/prisma'
|
import prisma from '@/lib/prisma'
|
||||||
import {
|
import {
|
||||||
defaultWebhookAttributes,
|
defaultWebhookAttributes,
|
||||||
ResultValues,
|
ResultValues,
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
import prisma from 'libs/prisma'
|
import { authenticateUser } from '@/features/auth/api'
|
||||||
|
import { getLinkedTypebots } from '@/features/typebotLink/api'
|
||||||
|
import prisma from '@/lib/prisma'
|
||||||
import { Typebot } from 'models'
|
import { Typebot } from 'models'
|
||||||
import { NextApiRequest, NextApiResponse } from 'next'
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
import { authenticateUser, getLinkedTypebots } from 'services/api/utils'
|
|
||||||
import { parseSampleResult } from 'services/api/webhooks'
|
|
||||||
import { methodNotAllowed } from 'utils/api'
|
import { methodNotAllowed } from 'utils/api'
|
||||||
|
import { parseSampleResult } from '@/features/webhook/api'
|
||||||
|
|
||||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
const user = await authenticateUser(req)
|
const user = await authenticateUser(req)
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import { withSentry } from '@sentry/nextjs'
|
import { withSentry } from '@sentry/nextjs'
|
||||||
import prisma from 'libs/prisma'
|
|
||||||
import { Typebot, WebhookBlock } from 'models'
|
import { Typebot, WebhookBlock } from 'models'
|
||||||
import { NextApiRequest, NextApiResponse } from 'next'
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
import { authenticateUser } from 'services/api/utils'
|
|
||||||
import { methodNotAllowed } from 'utils/api'
|
import { methodNotAllowed } from 'utils/api'
|
||||||
import { byId } from 'utils'
|
import { byId } from 'utils'
|
||||||
|
import { authenticateUser } from '@/features/auth/api'
|
||||||
|
import prisma from '@/lib/prisma'
|
||||||
|
|
||||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
const user = await authenticateUser(req)
|
const user = await authenticateUser(req)
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import { withSentry } from '@sentry/nextjs'
|
import { withSentry } from '@sentry/nextjs'
|
||||||
import prisma from 'libs/prisma'
|
|
||||||
import { Typebot, WebhookBlock } from 'models'
|
import { Typebot, WebhookBlock } from 'models'
|
||||||
import { NextApiRequest, NextApiResponse } from 'next'
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
import { authenticateUser } from 'services/api/utils'
|
|
||||||
import { methodNotAllowed } from 'utils/api'
|
import { methodNotAllowed } from 'utils/api'
|
||||||
import { byId } from 'utils'
|
import { byId } from 'utils'
|
||||||
|
import { authenticateUser } from '@/features/auth/api'
|
||||||
|
import prisma from '@/lib/prisma'
|
||||||
|
|
||||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
const user = await authenticateUser(req)
|
const user = await authenticateUser(req)
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { withSentry } from '@sentry/nextjs'
|
import { withSentry } from '@sentry/nextjs'
|
||||||
import { WorkspaceRole } from 'db'
|
import { WorkspaceRole } from 'db'
|
||||||
import prisma from 'libs/prisma'
|
import prisma from '@/lib/prisma'
|
||||||
import { InputBlockType, PublicTypebot } from 'models'
|
import { InputBlockType, PublicTypebot } from 'models'
|
||||||
import { NextApiRequest, NextApiResponse } from 'next'
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
import { badRequest, generatePresignedUrl, methodNotAllowed } from 'utils/api'
|
import { badRequest, generatePresignedUrl, methodNotAllowed } from 'utils/api'
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
|
import { authenticateUser } from '@/features/auth/api'
|
||||||
|
import prisma from '@/lib/prisma'
|
||||||
import { withSentry } from '@sentry/nextjs'
|
import { withSentry } from '@sentry/nextjs'
|
||||||
import prisma from 'libs/prisma'
|
|
||||||
import { Typebot, WebhookBlock } from 'models'
|
import { Typebot, WebhookBlock } from 'models'
|
||||||
import { NextApiRequest, NextApiResponse } from 'next'
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
import { authenticateUser } from 'services/api/utils'
|
|
||||||
import { byId } from 'utils'
|
import { byId } from 'utils'
|
||||||
import { methodNotAllowed } from 'utils/api'
|
import { methodNotAllowed } from 'utils/api'
|
||||||
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
|
import { authenticateUser } from '@/features/auth/api'
|
||||||
|
import prisma from '@/lib/prisma'
|
||||||
import { withSentry } from '@sentry/nextjs'
|
import { withSentry } from '@sentry/nextjs'
|
||||||
import prisma from 'libs/prisma'
|
|
||||||
import { Typebot, WebhookBlock } from 'models'
|
import { Typebot, WebhookBlock } from 'models'
|
||||||
import { NextApiRequest, NextApiResponse } from 'next'
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
import { authenticateUser } from 'services/api/utils'
|
|
||||||
import { byId } from 'utils'
|
import { byId } from 'utils'
|
||||||
import { methodNotAllowed } from 'utils/api'
|
import { methodNotAllowed } from 'utils/api'
|
||||||
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import prisma from 'libs/prisma'
|
|
||||||
import {
|
import {
|
||||||
PublicTypebot,
|
PublicTypebot,
|
||||||
ResultValues,
|
ResultValues,
|
||||||
@@ -9,16 +8,14 @@ import { NextApiRequest, NextApiResponse } from 'next'
|
|||||||
import { createTransport, getTestMessageUrl } from 'nodemailer'
|
import { createTransport, getTestMessageUrl } from 'nodemailer'
|
||||||
import { isEmpty, isNotDefined, omit, parseAnswers } from 'utils'
|
import { isEmpty, isNotDefined, omit, parseAnswers } from 'utils'
|
||||||
import { methodNotAllowed, initMiddleware, decrypt } from 'utils/api'
|
import { methodNotAllowed, initMiddleware, decrypt } from 'utils/api'
|
||||||
|
import { saveErrorLog, saveSuccessLog } from '@/features/logs/api'
|
||||||
|
|
||||||
import Cors from 'cors'
|
import Cors from 'cors'
|
||||||
import { withSentry } from '@sentry/nextjs'
|
import { withSentry } from '@sentry/nextjs'
|
||||||
import {
|
|
||||||
getLinkedTypebots,
|
|
||||||
saveErrorLog,
|
|
||||||
saveSuccessLog,
|
|
||||||
} from 'services/api/utils'
|
|
||||||
import Mail from 'nodemailer/lib/mailer'
|
import Mail from 'nodemailer/lib/mailer'
|
||||||
import { DefaultBotNotificationEmail, render } from 'emails'
|
import { DefaultBotNotificationEmail, render } from 'emails'
|
||||||
|
import prisma from '@/lib/prisma'
|
||||||
|
import { getLinkedTypebots } from '@/features/typebotLink/api'
|
||||||
|
|
||||||
const cors = initMiddleware(Cors())
|
const cors = initMiddleware(Cors())
|
||||||
|
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
|
import { authenticateUser } from '@/features/auth/api'
|
||||||
|
import prisma from '@/lib/prisma'
|
||||||
import { Workspace, WorkspaceRole } from 'db'
|
import { Workspace, WorkspaceRole } from 'db'
|
||||||
import {
|
import {
|
||||||
sendAlmostReachedChatsLimitEmail,
|
sendAlmostReachedChatsLimitEmail,
|
||||||
sendReachedChatsLimitEmail,
|
sendReachedChatsLimitEmail,
|
||||||
} from 'emails'
|
} from 'emails'
|
||||||
import prisma from 'libs/prisma'
|
|
||||||
import { ResultWithAnswers } from 'models'
|
import { ResultWithAnswers } from 'models'
|
||||||
import { NextApiRequest, NextApiResponse } from 'next'
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
import { authenticateUser } from 'services/api/utils'
|
|
||||||
import { env, getChatsLimit, isDefined } from 'utils'
|
import { env, getChatsLimit, isDefined } from 'utils'
|
||||||
import { methodNotAllowed } from 'utils/api'
|
import { methodNotAllowed } from 'utils/api'
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { withSentry } from '@sentry/nextjs'
|
import { withSentry } from '@sentry/nextjs'
|
||||||
import prisma from 'libs/prisma'
|
import prisma from '@/lib/prisma'
|
||||||
import { Result } from 'models'
|
import { Result } from 'models'
|
||||||
import { NextApiRequest, NextApiResponse } from 'next'
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
import { methodNotAllowed } from 'utils/api'
|
import { methodNotAllowed } from 'utils/api'
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
|
import prisma from '@/lib/prisma'
|
||||||
import { withSentry } from '@sentry/nextjs'
|
import { withSentry } from '@sentry/nextjs'
|
||||||
import { Answer } from 'db'
|
import { Answer } from 'db'
|
||||||
import { got } from 'got'
|
import { got } from 'got'
|
||||||
import prisma from 'libs/prisma'
|
|
||||||
import { NextApiRequest, NextApiResponse } from 'next'
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
import { isNotDefined } from 'utils'
|
import { isNotDefined } from 'utils'
|
||||||
import { methodNotAllowed } from 'utils/api'
|
import { methodNotAllowed } from 'utils/api'
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
|
import { authenticateUser } from '@/features/auth/api'
|
||||||
|
import prisma from '@/lib/prisma'
|
||||||
import { withSentry } from '@sentry/nextjs'
|
import { withSentry } from '@sentry/nextjs'
|
||||||
import prisma from 'libs/prisma'
|
|
||||||
import { Group, WebhookBlock } from 'models'
|
import { Group, WebhookBlock } from 'models'
|
||||||
import { NextApiRequest, NextApiResponse } from 'next'
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
import { authenticateUser } from 'services/api/utils'
|
|
||||||
import { byId, isWebhookBlock } from 'utils'
|
import { byId, isWebhookBlock } from 'utils'
|
||||||
import { methodNotAllowed } from 'utils/api'
|
import { methodNotAllowed } from 'utils/api'
|
||||||
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
|
import { authenticateUser } from '@/features/auth/api'
|
||||||
|
import prisma from '@/lib/prisma'
|
||||||
import { withSentry } from '@sentry/nextjs'
|
import { withSentry } from '@sentry/nextjs'
|
||||||
import prisma from 'libs/prisma'
|
|
||||||
import { Group, WebhookBlock } from 'models'
|
import { Group, WebhookBlock } from 'models'
|
||||||
import { NextApiRequest, NextApiResponse } from 'next'
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
import { authenticateUser } from 'services/api/utils'
|
|
||||||
import { byId, isNotDefined, isWebhookBlock } from 'utils'
|
import { byId, isNotDefined, isWebhookBlock } from 'utils'
|
||||||
import { methodNotAllowed } from 'utils/api'
|
import { methodNotAllowed } from 'utils/api'
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import { authenticateUser } from '@/features/auth/api'
|
||||||
import { withSentry } from '@sentry/nextjs'
|
import { withSentry } from '@sentry/nextjs'
|
||||||
import { NextApiRequest, NextApiResponse } from 'next'
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
import { authenticateUser } from 'services/api/utils'
|
|
||||||
import { isNotDefined } from 'utils'
|
import { isNotDefined } from 'utils'
|
||||||
import { methodNotAllowed } from 'utils/api'
|
import { methodNotAllowed } from 'utils/api'
|
||||||
|
|
||||||
@@ -15,6 +15,9 @@
|
|||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["src/*"]
|
||||||
|
},
|
||||||
"composite": true,
|
"composite": true,
|
||||||
"downlevelIteration": true
|
"downlevelIteration": true
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -28,7 +28,10 @@ export const playwrightBaseConfig: PlaywrightTestConfig = {
|
|||||||
},
|
},
|
||||||
retries: process.env.NO_RETRIES ? 0 : 1,
|
retries: process.env.NO_RETRIES ? 0 : 1,
|
||||||
workers: process.env.CI ? 2 : 3,
|
workers: process.env.CI ? 2 : 3,
|
||||||
reporter: [[process.env.CI ? 'github' : 'list'], ['html']],
|
reporter: [
|
||||||
|
[process.env.CI ? 'github' : 'list'],
|
||||||
|
['html', { outputFolder: 'src/test/reporters' }],
|
||||||
|
],
|
||||||
maxFailures: process.env.CI ? 10 : undefined,
|
maxFailures: process.env.CI ? 10 : undefined,
|
||||||
webServer: process.env.CI
|
webServer: process.env.CI
|
||||||
? {
|
? {
|
||||||
@@ -37,6 +40,7 @@ export const playwrightBaseConfig: PlaywrightTestConfig = {
|
|||||||
reuseExistingServer: true,
|
reuseExistingServer: true,
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
|
outputDir: './src/test/results',
|
||||||
use: {
|
use: {
|
||||||
trace: 'on-first-retry',
|
trace: 'on-first-retry',
|
||||||
video: 'retain-on-failure',
|
video: 'retain-on-failure',
|
||||||
|
|||||||
Reference in New Issue
Block a user