♻️ (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
|
||||
dist
|
||||
test-results
|
||||
test/results
|
||||
**/src/test/results
|
||||
**/src/test/reporters
|
||||
test/report
|
||||
**/api/scripts
|
||||
.sentryclirc
|
||||
|
@ -15,7 +15,6 @@ const config: PlaywrightTestConfig = {
|
||||
baseURL: process.env.NEXTAUTH_URL,
|
||||
storageState: path.join(__dirname, 'src/test/storageState.json'),
|
||||
},
|
||||
outputDir: path.join(__dirname, 'src/test/results/'),
|
||||
}
|
||||
|
||||
export default config
|
||||
|
@ -25,5 +25,19 @@ module.exports = {
|
||||
plugins: ['react', '@typescript-eslint'],
|
||||
rules: {
|
||||
'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 = {
|
||||
...playwrightBaseConfig,
|
||||
testDir: path.join(__dirname, 'playwright/tests'),
|
||||
testDir: path.join(__dirname, 'src'),
|
||||
webServer: process.env.CI
|
||||
? {
|
||||
...(playwrightBaseConfig.webServer as { command: string }),
|
||||
@ -15,7 +15,6 @@ const config: PlaywrightTestConfig = {
|
||||
...playwrightBaseConfig.use,
|
||||
baseURL: process.env.NEXT_PUBLIC_VIEWER_URL,
|
||||
},
|
||||
outputDir: path.join(__dirname, 'playwright/test-results/'),
|
||||
}
|
||||
|
||||
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 { useRouter } from 'next/router'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { upsertAnswer } from 'services/answer'
|
||||
import { isDefined, isNotDefined } from 'utils'
|
||||
import { SEO } from '../components/Seo'
|
||||
import { createResult, updateResult } from '../services/result'
|
||||
import { SEO } from './Seo'
|
||||
import { ErrorPage } from './ErrorPage'
|
||||
import { createResultQuery, updateResultQuery } from '@/features/results'
|
||||
import { upsertAnswerQuery } from '@/features/answers'
|
||||
|
||||
export type TypebotPageProps = {
|
||||
publishedTypebot: Omit<PublicTypebot, 'createdAt' | 'updatedAt'> & {
|
||||
@ -67,7 +67,9 @@ export const TypebotPage = ({
|
||||
const resultIdFromSession = getExistingResultFromSession()
|
||||
if (resultIdFromSession) setResultId(resultIdFromSession)
|
||||
else {
|
||||
const { error, data } = await createResult(publishedTypebot.typebotId)
|
||||
const { error, data } = await createResultQuery(
|
||||
publishedTypebot.typebotId
|
||||
)
|
||||
if (error) return setError(error)
|
||||
if (data?.hasReachedLimit)
|
||||
return setError(new Error('This bot is now closed.'))
|
||||
@ -97,7 +99,7 @@ export const TypebotPage = ({
|
||||
|
||||
const sendNewVariables =
|
||||
(resultId: string) => async (variables: VariableWithValue[]) => {
|
||||
const { error } = await updateResult(resultId, { variables })
|
||||
const { error } = await updateResultQuery(resultId, { variables })
|
||||
if (error) setError(error)
|
||||
}
|
||||
|
||||
@ -105,17 +107,17 @@ export const TypebotPage = ({
|
||||
answer: Answer & { uploadedFiles: boolean }
|
||||
) => {
|
||||
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 (chatStarted) return
|
||||
updateResult(resultId, {
|
||||
updateResultQuery(resultId, {
|
||||
hasStarted: true,
|
||||
}).then(({ error }) => (error ? setError(error) : setChatStarted(true)))
|
||||
}
|
||||
|
||||
const handleCompleted = async () => {
|
||||
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)
|
||||
}
|
||||
|
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 { sendRequest } from 'utils'
|
||||
|
||||
export const upsertAnswer = async (
|
||||
export const upsertAnswerQuery = async (
|
||||
answer: Answer & { resultId: string } & { uploadedFiles?: boolean }
|
||||
) =>
|
||||
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 cuid from 'cuid'
|
||||
import path from 'path'
|
||||
import { parse } from 'papaparse'
|
||||
import { readFileSync } from 'fs'
|
||||
import { isDefined } from 'utils'
|
||||
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
|
||||
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 }) => {
|
||||
const typebotId = cuid()
|
||||
await importTypebotInDatabase(
|
||||
path.join(__dirname, '../fixtures/typebots/fileUpload.json'),
|
||||
{ id: typebotId, publicId: `${typebotId}-public` }
|
||||
)
|
||||
await importTypebotInDatabase(getTestAsset('typebots/fileUpload.json'), {
|
||||
id: typebotId,
|
||||
publicId: `${typebotId}-public`,
|
||||
})
|
||||
await page.goto(`/${typebotId}-public`)
|
||||
await typebotViewer(page)
|
||||
.locator(`input[type="file"]`)
|
||||
.setInputFiles([
|
||||
path.join(__dirname, '../fixtures/typebots/api.json'),
|
||||
path.join(__dirname, '../fixtures/typebots/fileUpload.json'),
|
||||
path.join(__dirname, '../fixtures/typebots/hugeGroup.json'),
|
||||
getTestAsset('typebots/api.json'),
|
||||
getTestAsset('typebots/fileUpload.json'),
|
||||
getTestAsset('typebots/hugeGroup.json'),
|
||||
])
|
||||
await expect(typebotViewer(page).locator(`text="3"`)).toBeVisible()
|
||||
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"`)
|
||||
).toBeVisible()
|
||||
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',
|
||||
/.+\/api\.json/
|
||||
)
|
||||
await expect(page.locator('text="fileUpload.json"')).toHaveAttribute(
|
||||
'href',
|
||||
/.+\/fileUpload\.json/
|
||||
)
|
||||
await expect(page.locator('text="hugeGroup.json"')).toHaveAttribute(
|
||||
'href',
|
||||
/.+\/hugeGroup\.json/
|
||||
)
|
||||
await expect(
|
||||
page.getByRole('link', { name: 'fileUpload.json' })
|
||||
).toHaveAttribute('href', /.+\/fileUpload\.json/)
|
||||
await expect(
|
||||
page.getByRole('link', { name: 'hugeGroup.json' })
|
||||
).toHaveAttribute('href', /.+\/hugeGroup\.json/)
|
||||
|
||||
await page.click('[data-testid="checkbox"] >> nth=0')
|
||||
const [download] = await Promise.all([
|
||||
@ -57,9 +55,9 @@ test('should work as expected', async ({ page, browser }) => {
|
||||
const urls = (
|
||||
await Promise.all(
|
||||
[
|
||||
page.locator('text="api.json"'),
|
||||
page.locator('text="fileUpload.json"'),
|
||||
page.locator('text="hugeGroup.json"'),
|
||||
page.getByRole('link', { name: 'api.json' }),
|
||||
page.getByRole('link', { name: 'fileUpload.json' }),
|
||||
page.getByRole('link', { name: 'hugeGroup.json' }),
|
||||
].map((elem) => elem.getAttribute('href'))
|
||||
)
|
||||
).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 path from 'path'
|
||||
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'
|
||||
|
||||
test('should work as expected', async ({ page }) => {
|
||||
test('Big groups should work as expected', async ({ page }) => {
|
||||
const typebotId = cuid()
|
||||
await importTypebotInDatabase(
|
||||
path.join(__dirname, '../fixtures/typebots/hugeGroup.json'),
|
||||
{ id: typebotId, publicId: `${typebotId}-public` }
|
||||
)
|
||||
await importTypebotInDatabase(getTestAsset('typebots/hugeGroup.json'), {
|
||||
id: typebotId,
|
||||
publicId: `${typebotId}-public`,
|
||||
})
|
||||
await page.goto(`/${typebotId}-public`)
|
||||
await typebotViewer(page).locator('input').fill('Baptiste')
|
||||
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="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 { createSmtpCredentials } from '../services/databaseActions'
|
||||
import { createSmtpCredentials } from '../../test/utils/databaseActions'
|
||||
import cuid from 'cuid'
|
||||
import path from 'path'
|
||||
import { SmtpCredentialsData } from 'models'
|
||||
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
|
||||
import { typebotViewer } from 'utils/playwright/testHelpers'
|
||||
import { getTestAsset } from '@/test/utils/playwright'
|
||||
|
||||
const mockSmtpCredentials: SmtpCredentialsData = {
|
||||
from: {
|
||||
@ -28,10 +28,10 @@ test.beforeAll(async () => {
|
||||
|
||||
test('should send an email', async ({ page }) => {
|
||||
const typebotId = cuid()
|
||||
await importTypebotInDatabase(
|
||||
path.join(__dirname, '../fixtures/typebots/sendEmail.json'),
|
||||
{ id: typebotId, publicId: `${typebotId}-public` }
|
||||
)
|
||||
await importTypebotInDatabase(getTestAsset('typebots/sendEmail.json'), {
|
||||
id: typebotId,
|
||||
publicId: `${typebotId}-public`,
|
||||
})
|
||||
await page.goto(`/${typebotId}-public`)
|
||||
const [response] = await Promise.all([
|
||||
page.waitForResponse((resp) =>
|
@ -4,9 +4,11 @@ import {
|
||||
defaultSettings,
|
||||
defaultTextInputOptions,
|
||||
InputBlockType,
|
||||
Metadata,
|
||||
} from 'models'
|
||||
import { createTypebots, updateTypebot } from 'utils/playwright/databaseActions'
|
||||
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
|
||||
import { typebotViewer } from 'utils/playwright/testHelpers'
|
||||
|
||||
test('Result should be in storage by default', async ({ page }) => {
|
||||
const typebotId = cuid()
|
||||
@ -120,3 +122,56 @@ test('Show close message', async ({ page }) => {
|
||||
await page.goto(`/${typebotId}-public`)
|
||||
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 path from 'path'
|
||||
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
|
||||
import { typebotViewer } from 'utils/playwright/testHelpers'
|
||||
|
||||
@ -9,11 +9,11 @@ const linkedTypebotId = 'cl0ibhv8d0130n21aw8doxhj5'
|
||||
test.beforeAll(async () => {
|
||||
try {
|
||||
await importTypebotInDatabase(
|
||||
path.join(__dirname, '../fixtures/typebots/linkTypebots/1.json'),
|
||||
getTestAsset('typebots/linkTypebots/1.json'),
|
||||
{ id: typebotId, publicId: `${typebotId}-public` }
|
||||
)
|
||||
await importTypebotInDatabase(
|
||||
path.join(__dirname, '../fixtures/typebots/linkTypebots/2.json'),
|
||||
getTestAsset('typebots/linkTypebots/2.json'),
|
||||
{ id: linkedTypebotId, publicId: `${linkedTypebotId}-public` }
|
||||
)
|
||||
} catch (err) {
|
@ -1,6 +1,5 @@
|
||||
import test, { expect } from '@playwright/test'
|
||||
import cuid from 'cuid'
|
||||
import path from 'path'
|
||||
|
||||
// TODO: uncomment on 1st of November
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { getTestAsset } from '@/test/utils/playwright'
|
||||
import test, { expect } from '@playwright/test'
|
||||
import cuid from 'cuid'
|
||||
import path from 'path'
|
||||
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
|
||||
import { typebotViewer } from 'utils/playwright/testHelpers'
|
||||
|
||||
test('should correctly be injected', async ({ page }) => {
|
||||
const typebotId = cuid()
|
||||
await importTypebotInDatabase(
|
||||
path.join(__dirname, '../fixtures/typebots/predefinedVariables.json'),
|
||||
getTestAsset('typebots/predefinedVariables.json'),
|
||||
{ id: typebotId, publicId: `${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 { ErrorPage } from 'layouts/ErrorPage'
|
||||
import { NotFoundPage } from 'layouts/NotFoundPage'
|
||||
import { ErrorPage } from '@/components/ErrorPage'
|
||||
import { NotFoundPage } from '@/components/NotFoundPage'
|
||||
import { GetServerSideProps, GetServerSidePropsContext } from 'next'
|
||||
import sanitizeHtml from 'sanitize-html'
|
||||
import { env, getViewerUrl, isDefined, isNotDefined, omit } from 'utils'
|
||||
import { TypebotPage, TypebotPageProps } from '../layouts/TypebotPage'
|
||||
import prisma from '../libs/prisma'
|
||||
import { TypebotPage, TypebotPageProps } from '../components/TypebotPage'
|
||||
import prisma from '../lib/prisma'
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async (
|
||||
context: GetServerSidePropsContext
|
@ -2,11 +2,11 @@ import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { badRequest, initMiddleware, methodNotAllowed } from 'utils/api'
|
||||
import { hasValue } from 'utils'
|
||||
import { GoogleSpreadsheet } from 'google-spreadsheet'
|
||||
import { getAuthenticatedGoogleClient } from 'libs/google-sheets'
|
||||
import { Cell } from 'models'
|
||||
import Cors from 'cors'
|
||||
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 handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
@ -11,7 +11,7 @@ import Stripe from 'stripe'
|
||||
import Cors from 'cors'
|
||||
import { withSentry } from '@sentry/nextjs'
|
||||
import { PaymentInputOptions, StripeCredentialsData, Variable } from 'models'
|
||||
import prisma from 'libs/prisma'
|
||||
import prisma from '@/lib/prisma'
|
||||
import { parseVariables } from 'bot-engine'
|
||||
|
||||
const cors = initMiddleware(Cors())
|
@ -1,5 +1,5 @@
|
||||
import { withSentry } from '@sentry/nextjs'
|
||||
import prisma from 'libs/prisma'
|
||||
import prisma from '@/lib/prisma'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import Cors from 'cors'
|
||||
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 prisma from 'libs/prisma'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { authenticateUser } from 'services/api/utils'
|
||||
import { methodNotAllowed } from 'utils/api'
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
@ -1,4 +1,3 @@
|
||||
import prisma from 'libs/prisma'
|
||||
import {
|
||||
defaultWebhookAttributes,
|
||||
KeyValue,
|
||||
@ -20,12 +19,10 @@ import { initMiddleware, methodNotAllowed, notFound } from 'utils/api'
|
||||
import { stringify } from 'qs'
|
||||
import { withSentry } from '@sentry/nextjs'
|
||||
import Cors from 'cors'
|
||||
import { parseSampleResult } from 'services/api/webhooks'
|
||||
import {
|
||||
getLinkedTypebots,
|
||||
saveErrorLog,
|
||||
saveSuccessLog,
|
||||
} from 'services/api/utils'
|
||||
import prisma from '@/lib/prisma'
|
||||
import { getLinkedTypebots } from '@/features/typebotLink/api'
|
||||
import { saveErrorLog, saveSuccessLog } from '@/features/logs/api'
|
||||
import { parseSampleResult } from '@/features/webhook/api'
|
||||
|
||||
const cors = initMiddleware(Cors())
|
||||
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 { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { authenticateUser, getLinkedTypebots } from 'services/api/utils'
|
||||
import { parseSampleResult } from 'services/api/webhooks'
|
||||
import { methodNotAllowed } from 'utils/api'
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
@ -1,4 +1,4 @@
|
||||
import prisma from 'libs/prisma'
|
||||
import prisma from '@/lib/prisma'
|
||||
import {
|
||||
defaultWebhookAttributes,
|
||||
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 { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { authenticateUser, getLinkedTypebots } from 'services/api/utils'
|
||||
import { parseSampleResult } from 'services/api/webhooks'
|
||||
import { methodNotAllowed } from 'utils/api'
|
||||
import { parseSampleResult } from '@/features/webhook/api'
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const user = await authenticateUser(req)
|
@ -1,10 +1,10 @@
|
||||
import { withSentry } from '@sentry/nextjs'
|
||||
import prisma from 'libs/prisma'
|
||||
import { Typebot, WebhookBlock } from 'models'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { authenticateUser } from 'services/api/utils'
|
||||
import { methodNotAllowed } from 'utils/api'
|
||||
import { byId } from 'utils'
|
||||
import { authenticateUser } from '@/features/auth/api'
|
||||
import prisma from '@/lib/prisma'
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const user = await authenticateUser(req)
|
@ -1,10 +1,10 @@
|
||||
import { withSentry } from '@sentry/nextjs'
|
||||
import prisma from 'libs/prisma'
|
||||
import { Typebot, WebhookBlock } from 'models'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { authenticateUser } from 'services/api/utils'
|
||||
import { methodNotAllowed } from 'utils/api'
|
||||
import { byId } from 'utils'
|
||||
import { authenticateUser } from '@/features/auth/api'
|
||||
import prisma from '@/lib/prisma'
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const user = await authenticateUser(req)
|
@ -1,6 +1,6 @@
|
||||
import { withSentry } from '@sentry/nextjs'
|
||||
import { WorkspaceRole } from 'db'
|
||||
import prisma from 'libs/prisma'
|
||||
import prisma from '@/lib/prisma'
|
||||
import { InputBlockType, PublicTypebot } from 'models'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
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 prisma from 'libs/prisma'
|
||||
import { Typebot, WebhookBlock } from 'models'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { authenticateUser } from 'services/api/utils'
|
||||
import { byId } from 'utils'
|
||||
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 prisma from 'libs/prisma'
|
||||
import { Typebot, WebhookBlock } from 'models'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { authenticateUser } from 'services/api/utils'
|
||||
import { byId } from 'utils'
|
||||
import { methodNotAllowed } from 'utils/api'
|
||||
|
@ -1,4 +1,3 @@
|
||||
import prisma from 'libs/prisma'
|
||||
import {
|
||||
PublicTypebot,
|
||||
ResultValues,
|
||||
@ -9,16 +8,14 @@ import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { createTransport, getTestMessageUrl } from 'nodemailer'
|
||||
import { isEmpty, isNotDefined, omit, parseAnswers } from 'utils'
|
||||
import { methodNotAllowed, initMiddleware, decrypt } from 'utils/api'
|
||||
import { saveErrorLog, saveSuccessLog } from '@/features/logs/api'
|
||||
|
||||
import Cors from 'cors'
|
||||
import { withSentry } from '@sentry/nextjs'
|
||||
import {
|
||||
getLinkedTypebots,
|
||||
saveErrorLog,
|
||||
saveSuccessLog,
|
||||
} from 'services/api/utils'
|
||||
import Mail from 'nodemailer/lib/mailer'
|
||||
import { DefaultBotNotificationEmail, render } from 'emails'
|
||||
import prisma from '@/lib/prisma'
|
||||
import { getLinkedTypebots } from '@/features/typebotLink/api'
|
||||
|
||||
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 {
|
||||
sendAlmostReachedChatsLimitEmail,
|
||||
sendReachedChatsLimitEmail,
|
||||
} from 'emails'
|
||||
import prisma from 'libs/prisma'
|
||||
import { ResultWithAnswers } from 'models'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { authenticateUser } from 'services/api/utils'
|
||||
import { env, getChatsLimit, isDefined } from 'utils'
|
||||
import { methodNotAllowed } from 'utils/api'
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { withSentry } from '@sentry/nextjs'
|
||||
import prisma from 'libs/prisma'
|
||||
import prisma from '@/lib/prisma'
|
||||
import { Result } from 'models'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { methodNotAllowed } from 'utils/api'
|
@ -1,7 +1,7 @@
|
||||
import prisma from '@/lib/prisma'
|
||||
import { withSentry } from '@sentry/nextjs'
|
||||
import { Answer } from 'db'
|
||||
import { got } from 'got'
|
||||
import prisma from 'libs/prisma'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { isNotDefined } from 'utils'
|
||||
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 prisma from 'libs/prisma'
|
||||
import { Group, WebhookBlock } from 'models'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { authenticateUser } from 'services/api/utils'
|
||||
import { byId, isWebhookBlock } from 'utils'
|
||||
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 prisma from 'libs/prisma'
|
||||
import { Group, WebhookBlock } from 'models'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { authenticateUser } from 'services/api/utils'
|
||||
import { byId, isNotDefined, isWebhookBlock } from 'utils'
|
||||
import { methodNotAllowed } from 'utils/api'
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { authenticateUser } from '@/features/auth/api'
|
||||
import { withSentry } from '@sentry/nextjs'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { authenticateUser } from 'services/api/utils'
|
||||
import { isNotDefined } from 'utils'
|
||||
import { methodNotAllowed } from 'utils/api'
|
||||
|
@ -15,6 +15,9 @@
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
},
|
||||
"composite": true,
|
||||
"downlevelIteration": true
|
||||
},
|
||||
|
@ -28,7 +28,10 @@ export const playwrightBaseConfig: PlaywrightTestConfig = {
|
||||
},
|
||||
retries: process.env.NO_RETRIES ? 0 : 1,
|
||||
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,
|
||||
webServer: process.env.CI
|
||||
? {
|
||||
@ -37,6 +40,7 @@ export const playwrightBaseConfig: PlaywrightTestConfig = {
|
||||
reuseExistingServer: true,
|
||||
}
|
||||
: undefined,
|
||||
outputDir: './src/test/results',
|
||||
use: {
|
||||
trace: 'on-first-retry',
|
||||
video: 'retain-on-failure',
|
||||
|
Reference in New Issue
Block a user