-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(viewer): 🐛 Custom metadata in viewer
- Loading branch information
1 parent
feb966d
commit 6ec8d06
Showing
8 changed files
with
312 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { devices, PlaywrightTestConfig } from '@playwright/test' | ||
import path from 'path' | ||
|
||
const config: PlaywrightTestConfig = { | ||
globalSetup: require.resolve(path.join(__dirname, 'playwright/global-setup')), | ||
testDir: path.join(__dirname, 'playwright/tests'), | ||
timeout: 10 * 2000, | ||
expect: { | ||
timeout: 5000, | ||
}, | ||
retries: process.env.NO_RETRIES ? 0 : 2, | ||
workers: process.env.CI ? 1 : 3, | ||
reporter: 'html', | ||
maxFailures: process.env.CI ? 10 : undefined, | ||
use: { | ||
actionTimeout: 0, | ||
baseURL: process.env.NEXT_PUBLIC_VIEWER_HOST, | ||
trace: 'on-first-retry', | ||
video: 'retain-on-failure', | ||
locale: 'en-US', | ||
}, | ||
outputDir: path.join(__dirname, 'playwright/test-results/'), | ||
projects: [ | ||
{ | ||
name: 'Chrome', | ||
use: { | ||
...devices['Desktop Chrome'], | ||
viewport: { width: 1400, height: 1000 }, | ||
}, | ||
}, | ||
], | ||
} | ||
export default config |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { FullConfig } from '@playwright/test' | ||
import { setupDatabase, teardownDatabase } from './services/database' | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-var-requires | ||
require('dotenv').config({ path: '.env' }) | ||
|
||
async function globalSetup(config: FullConfig) { | ||
const { baseURL } = config.projects[0].use | ||
if (!baseURL) throw new Error('baseURL is missing') | ||
await teardownDatabase() | ||
await setupDatabase() | ||
} | ||
|
||
export default globalSetup |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
import { | ||
Block, | ||
CredentialsType, | ||
defaultSettings, | ||
defaultTheme, | ||
PublicBlock, | ||
PublicTypebot, | ||
Step, | ||
Typebot, | ||
} from 'models' | ||
import { PrismaClient, User } from 'db' | ||
import { readFileSync } from 'fs' | ||
import { encrypt } from 'utils' | ||
|
||
const prisma = new PrismaClient() | ||
|
||
export const teardownDatabase = async () => { | ||
const ownerFilter = { | ||
where: { ownerId: { in: ['freeUser', 'proUser'] } }, | ||
} | ||
await prisma.user.deleteMany({ | ||
where: { id: { in: ['freeUser', 'proUser'] } }, | ||
}) | ||
await prisma.credentials.deleteMany(ownerFilter) | ||
return prisma.typebot.deleteMany(ownerFilter) | ||
} | ||
|
||
export const setupDatabase = async () => { | ||
await createUsers() | ||
return createCredentials() | ||
} | ||
|
||
export const createUsers = () => | ||
prisma.user.createMany({ | ||
data: [ | ||
{ id: 'freeUser', email: 'free-user@email.com', name: 'Free user' }, | ||
{ id: 'proUser', email: 'pro-user@email.com', name: 'Pro user' }, | ||
], | ||
}) | ||
|
||
export const getSignedInUser = (email: string) => | ||
prisma.user.findFirst({ where: { email } }) | ||
|
||
export const createTypebots = async (partialTypebots: Partial<Typebot>[]) => { | ||
await prisma.typebot.createMany({ | ||
data: partialTypebots.map(parseTestTypebot) as any[], | ||
}) | ||
return prisma.publicTypebot.createMany({ | ||
data: partialTypebots.map((t) => | ||
parseTypebotToPublicTypebot(t.id + '-published', parseTestTypebot(t)) | ||
) as any[], | ||
}) | ||
} | ||
|
||
const createCredentials = () => { | ||
const { encryptedData, iv } = encrypt({ | ||
expiry_date: 1642441058842, | ||
access_token: | ||
'ya29.A0ARrdaM--PV_87ebjywDJpXKb77NBFJl16meVUapYdfNv6W6ZzqqC47fNaPaRjbDbOIIcp6f49cMaX5ndK9TAFnKwlVqz3nrK9nLKqgyDIhYsIq47smcAIZkK56SWPx3X3DwAFqRu2UPojpd2upWwo-3uJrod', | ||
// This token is linked to a test Google account (typebot.test.user@gmail.com) | ||
refresh_token: | ||
'1//039xWRt8YaYa3CgYIARAAGAMSNwF-L9Iru9FyuTrDSa7lkSceggPho83kJt2J29G69iEhT1C6XV1vmo6bQS9puL_R2t8FIwR3gek', | ||
}) | ||
return prisma.credentials.createMany({ | ||
data: [ | ||
{ | ||
name: 'test2@gmail.com', | ||
ownerId: 'proUser', | ||
type: CredentialsType.GOOGLE_SHEETS, | ||
data: encryptedData, | ||
iv, | ||
}, | ||
], | ||
}) | ||
} | ||
|
||
export const updateUser = (data: Partial<User>) => | ||
prisma.user.update({ | ||
data, | ||
where: { | ||
id: 'proUser', | ||
}, | ||
}) | ||
|
||
const parseTypebotToPublicTypebot = ( | ||
id: string, | ||
typebot: Typebot | ||
): PublicTypebot => ({ | ||
id, | ||
name: typebot.name, | ||
blocks: parseBlocksToPublicBlocks(typebot.blocks), | ||
typebotId: typebot.id, | ||
theme: typebot.theme, | ||
settings: typebot.settings, | ||
publicId: typebot.publicId, | ||
variables: typebot.variables, | ||
edges: typebot.edges, | ||
customDomain: null, | ||
}) | ||
|
||
const parseBlocksToPublicBlocks = (blocks: Block[]): PublicBlock[] => | ||
blocks.map((b) => ({ | ||
...b, | ||
steps: b.steps.map((s) => | ||
'webhook' in s ? { ...s, webhook: s.webhook.id } : s | ||
), | ||
})) | ||
|
||
const parseTestTypebot = (partialTypebot: Partial<Typebot>): Typebot => ({ | ||
id: partialTypebot.id ?? 'typebot', | ||
folderId: null, | ||
name: 'My typebot', | ||
ownerId: 'proUser', | ||
theme: defaultTheme, | ||
settings: defaultSettings, | ||
createdAt: new Date(), | ||
publicId: partialTypebot.id + '-public', | ||
publishedTypebotId: null, | ||
updatedAt: new Date(), | ||
customDomain: null, | ||
variables: [{ id: 'var1', name: 'var1' }], | ||
...partialTypebot, | ||
edges: [ | ||
{ | ||
id: 'edge1', | ||
from: { blockId: 'block0', stepId: 'step0' }, | ||
to: { blockId: 'block1' }, | ||
}, | ||
], | ||
blocks: [ | ||
{ | ||
id: 'block0', | ||
title: 'Block #0', | ||
steps: [ | ||
{ | ||
id: 'step0', | ||
type: 'start', | ||
blockId: 'block0', | ||
label: 'Start', | ||
outgoingEdgeId: 'edge1', | ||
}, | ||
], | ||
graphCoordinates: { x: 0, y: 0 }, | ||
}, | ||
...(partialTypebot.blocks ?? []), | ||
], | ||
}) | ||
|
||
export const parseDefaultBlockWithStep = ( | ||
step: Partial<Step> | ||
): Pick<Typebot, 'blocks'> => ({ | ||
blocks: [ | ||
{ | ||
graphCoordinates: { x: 200, y: 200 }, | ||
id: 'block1', | ||
steps: [ | ||
{ | ||
id: 'step1', | ||
blockId: 'block1', | ||
...step, | ||
} as Step, | ||
], | ||
title: 'Block #1', | ||
}, | ||
], | ||
}) | ||
|
||
export const importTypebotInDatabase = async ( | ||
path: string, | ||
updates?: Partial<Typebot> | ||
) => { | ||
const typebot: any = { | ||
...JSON.parse(readFileSync(path).toString()), | ||
...updates, | ||
ownerId: 'proUser', | ||
} | ||
await prisma.typebot.create({ | ||
data: typebot, | ||
}) | ||
return prisma.publicTypebot.create({ | ||
data: parseTypebotToPublicTypebot( | ||
updates?.id ? `${updates?.id}-public` : 'publicBot', | ||
typebot | ||
), | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
import { Page } from '@playwright/test' | ||
|
||
export const typebotViewer = (page: Page) => | ||
page.frameLocator('#typebot-iframe') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import test, { expect } from '@playwright/test' | ||
import { createTypebots, parseDefaultBlockWithStep } from '../services/database' | ||
import { generate } from 'short-uuid' | ||
import { | ||
defaultSettings, | ||
defaultTextInputOptions, | ||
InputStepType, | ||
Metadata, | ||
} from 'models' | ||
|
||
test('Should correctly parse metadata', async ({ page }) => { | ||
const typebotId = generate() | ||
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', | ||
} | ||
await createTypebots([ | ||
{ | ||
id: typebotId, | ||
settings: { | ||
...defaultSettings, | ||
metadata: customMetadata, | ||
}, | ||
...parseDefaultBlockWithStep({ | ||
type: InputStepType.TEXT, | ||
options: defaultTextInputOptions, | ||
}), | ||
}, | ||
]) | ||
await page.goto(`/${typebotId}-public`) | ||
await expect( | ||
await page.evaluate(`document.querySelector('title').textContent`) | ||
).toBe(customMetadata.title) | ||
await expect( | ||
await page.evaluate( | ||
() => (document.querySelector('meta[name="description"]') as any).content | ||
) | ||
).toBe(customMetadata.description) | ||
await expect( | ||
await page.evaluate( | ||
() => (document.querySelector('meta[property="og:image"]') as any).content | ||
) | ||
).toBe(customMetadata.imageUrl) | ||
await expect( | ||
await page.evaluate(() => | ||
(document.querySelector('link[rel="icon"]') as any).getAttribute('href') | ||
) | ||
).toBe(customMetadata.favIconUrl) | ||
}) |
6ec8d06
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
viewer-v2 – ./apps/viewer
demo.wemakebots.xyz
viewer-v2-typebot-io.vercel.app
viewer.typebot.io
criar.somaperuzzo.com
typebot-viewer.vercel.app
bot.theiofundation.org
viewer-v2-git-main-typebot-io.vercel.app
bot.adventureconsulting.hu
chat.hayuri.id
bot.pinpointinteractive.com
6ec8d06
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
builder-v2 – ./apps/builder
builder-v2-typebot-io.vercel.app
app.typebot.io
builder-v2-git-main-typebot-io.vercel.app