2
0

🔧 Enable usage limits on viewer

This commit is contained in:
Baptiste Arnaud
2022-12-05 13:53:20 +01:00
parent 4b2f42b40d
commit cfcecaaa17
4 changed files with 334 additions and 263 deletions

View File

@ -3,11 +3,16 @@ import cuid from 'cuid'
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 {
createWorkspaces,
importTypebotInDatabase,
injectFakeResults,
} from 'utils/playwright/databaseActions'
import { typebotViewer } from 'utils/playwright/testHelpers' import { typebotViewer } from 'utils/playwright/testHelpers'
import { getTestAsset } from '@/test/utils/playwright' import { getTestAsset } from '@/test/utils/playwright'
import { Plan } from 'db'
// 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()
@ -73,47 +78,45 @@ test('should work as expected', async ({ page, browser }) => {
await expect(page2.locator('pre')).toBeHidden() await expect(page2.locator('pre')).toBeHidden()
}) })
// TODO: uncomment on 1st of November test.describe('Storage limit is reached', () => {
const typebotId = cuid()
const workspaceId = cuid()
// test.describe('Storage limit is reached', () => { test.beforeAll(async () => {
// const typebotId = cuid() await createWorkspaces([{ id: workspaceId, plan: Plan.STARTER }])
await importTypebotInDatabase(getTestAsset('typebots/fileUpload.json'), {
id: typebotId,
publicId: `${typebotId}-public`,
workspaceId,
})
await injectFakeResults({
typebotId,
count: 20,
fakeStorage: THREE_GIGABYTES,
})
})
// test.beforeAll(async () => { test("shouldn't upload anything if limit has been reached", async ({
// await importTypebotInDatabase( page,
// path.join(__dirname, '../fixtures/typebots/fileUpload.json'), }) => {
// { await page.goto(`/${typebotId}-public`)
// id: typebotId, await typebotViewer(page)
// publicId: `${typebotId}-public`, .locator(`input[type="file"]`)
// } .setInputFiles([
// ) getTestAsset('typebots/api.json'),
// await createResults({ getTestAsset('typebots/fileUpload.json'),
// typebotId, getTestAsset('typebots/hugeGroup.json'),
// count: 20, ])
// fakeStorage: THREE_GIGABYTES, await expect(typebotViewer(page).locator(`text="3"`)).toBeVisible()
// }) await typebotViewer(page).locator('text="Upload 3 files"').click()
// }) await expect(
typebotViewer(page).locator(`text="3 files uploaded"`)
// test("shouldn't upload anything if limit has been reached", async ({ ).toBeVisible()
// page, await page.evaluate(() =>
// }) => { window.localStorage.setItem('workspaceId', 'starterWorkspace')
// await page.goto(`/${typebotId}-public`) )
// await typebotViewer(page) await page.goto(`${process.env.NEXTAUTH_URL}/typebots/${typebotId}/results`)
// .locator(`input[type="file"]`) await expect(page.locator('text="150%"')).toBeVisible()
// .setInputFiles([ await expect(page.locator('text="api.json"')).toBeHidden()
// path.join(__dirname, '../fixtures/typebots/api.json'), })
// path.join(__dirname, '../fixtures/typebots/fileUpload.json'), })
// path.join(__dirname, '../fixtures/typebots/hugeGroup.json'),
// ])
// await expect(typebotViewer(page).locator(`text="3"`)).toBeVisible()
// await typebotViewer(page).locator('text="Upload 3 files"').click()
// await expect(
// typebotViewer(page).locator(`text="3 files uploaded"`)
// ).toBeVisible()
// await page.evaluate(() =>
// window.localStorage.setItem('workspaceId', 'starterWorkspace')
// )
// await page.goto(`${process.env.NEXTAUTH_URL}/typebots/${typebotId}/results`)
// await expect(page.locator('text="150%"')).toBeVisible()
// await expect(page.locator('text="api.json"')).toBeHidden()
// })
// })

View File

@ -1,19 +1,75 @@
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 { Plan } from 'db'
import { defaultSettings } from 'models'
import {
createWorkspaces,
importTypebotInDatabase,
injectFakeResults,
} from 'utils/playwright/databaseActions'
import { typebotViewer } from 'utils/playwright/testHelpers'
// TODO: uncomment on 1st of November test('should not start if chat limit is reached', async ({ page, context }) => {
await test.step('Free plan', async () => {
const workspaceId = cuid()
const typebotId = cuid()
await createWorkspaces([{ id: workspaceId, plan: Plan.FREE }])
await importTypebotInDatabase(getTestAsset('typebots/fileUpload.json'), {
id: typebotId,
publicId: `${typebotId}-public`,
workspaceId,
})
await injectFakeResults({ typebotId, count: 400 })
await page.goto(`/${typebotId}-public`)
await expect(page.locator('text="This bot is now closed."')).toBeVisible()
await page.goto(`${process.env.NEXTAUTH_URL}/typebots/${typebotId}/results`)
await expect(page.locator('text="133%"')).toBeVisible()
})
// test('should not start if chat limit is reached', async ({ page }) => { await test.step('Lifetime plan', async () => {
// const typebotId = cuid() const workspaceId = cuid()
// await importTypebotInDatabase( const typebotId = cuid()
// path.join(__dirname, '../fixtures/typebots/fileUpload.json'), await createWorkspaces([{ id: workspaceId, plan: Plan.LIFETIME }])
// { await importTypebotInDatabase(getTestAsset('typebots/fileUpload.json'), {
// id: typebotId, id: typebotId,
// publicId: `${typebotId}-public`, publicId: `${typebotId}-public`,
// workspaceId: limitTestWorkspaceId, workspaceId,
// } })
// ) await injectFakeResults({ typebotId, count: 3000 })
// await createResults({ typebotId, count: 320 }) await page.goto(`/${typebotId}-public`)
// await page.goto(`/${typebotId}-public`) await expect(
// await expect(page.locator('text="This bot is now closed."')).toBeVisible() typebotViewer(page).locator('text="Hey there, upload please"')
// }) ).toBeVisible()
})
await test.step('Custom plan', async () => {
const workspaceId = cuid()
const typebotId = cuid()
await createWorkspaces([
{ id: workspaceId, plan: Plan.CUSTOM, customChatsLimit: 1000 },
])
await importTypebotInDatabase(getTestAsset('typebots/fileUpload.json'), {
id: typebotId,
publicId: `${typebotId}-public`,
workspaceId,
settings: {
...defaultSettings,
general: {
...defaultSettings.general,
isNewResultOnRefreshEnabled: true,
},
},
})
const page = await context.newPage()
await page.goto(`/${typebotId}-public`)
await expect(
typebotViewer(page).locator('text="Hey there, upload please"')
).toBeVisible()
await injectFakeResults({ typebotId, count: 2000 })
await page.goto(`/${typebotId}-public`)
await expect(page.locator('text="This bot is now closed."')).toBeVisible()
await page.goto(`${process.env.NEXTAUTH_URL}/typebots/${typebotId}/results`)
await expect(page.locator('text="200%"')).toBeVisible()
})
})

View File

@ -3,9 +3,14 @@ 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'
import { byId } from 'utils' import { byId, env, getStorageLimit, isDefined } from 'utils'
import {
sendAlmostReachedStorageLimitEmail,
sendReachedStorageLimitEmail,
} from 'emails'
import { WorkspaceRole } from 'db'
// const LIMIT_EMAIL_TRIGGER_PERCENT = 0.8 const LIMIT_EMAIL_TRIGGER_PERCENT = 0.8
const handler = async ( const handler = async (
req: NextApiRequest, req: NextApiRequest,
@ -27,7 +32,7 @@ const handler = async (
const typebotId = req.query.typebotId as string const typebotId = req.query.typebotId as string
const blockId = req.query.blockId as string const blockId = req.query.blockId as string
if (!filePath) return badRequest(res, 'Missing filePath or fileType') if (!filePath) return badRequest(res, 'Missing filePath or fileType')
// const hasReachedStorageLimit = await checkStorageLimit(typebotId) const hasReachedStorageLimit = await checkStorageLimit(typebotId)
const typebot = (await prisma.publicTypebot.findFirst({ const typebot = (await prisma.publicTypebot.findFirst({
where: { typebotId }, where: { typebotId },
})) as unknown as PublicTypebot })) as unknown as PublicTypebot
@ -46,117 +51,119 @@ const handler = async (
sizeLimit: sizeLimit * 1024 * 1024, sizeLimit: sizeLimit * 1024 * 1024,
}) })
// TODO: enable storage limit on 1st of November 2022 return res.status(200).send({
return res.status(200).send({ presignedUrl, hasReachedStorageLimit: false }) presignedUrl,
hasReachedStorageLimit,
})
} }
return methodNotAllowed(res) return methodNotAllowed(res)
} }
// const checkStorageLimit = async (typebotId: string) => { const checkStorageLimit = async (typebotId: string): Promise<boolean> => {
// const typebot = await prisma.typebot.findFirst({ const typebot = await prisma.typebot.findFirst({
// where: { id: typebotId }, where: { id: typebotId },
// include: { include: {
// workspace: { workspace: {
// select: { select: {
// id: true, id: true,
// additionalStorageIndex: true, additionalStorageIndex: true,
// plan: true, plan: true,
// storageLimitFirstEmailSentAt: true, storageLimitFirstEmailSentAt: true,
// storageLimitSecondEmailSentAt: true, storageLimitSecondEmailSentAt: true,
// customStorageLimit: true, customStorageLimit: true,
// }, },
// }, },
// }, },
// }) })
// if (!typebot?.workspace) throw new Error('Workspace not found') if (!typebot?.workspace) throw new Error('Workspace not found')
// const { workspace } = typebot const { workspace } = typebot
// const { const {
// _sum: { storageUsed: totalStorageUsed }, _sum: { storageUsed: totalStorageUsed },
// } = await prisma.answer.aggregate({ } = await prisma.answer.aggregate({
// where: { where: {
// storageUsed: { gt: 0 }, storageUsed: { gt: 0 },
// result: { result: {
// typebot: { typebot: {
// workspace: { workspace: {
// id: typebot?.workspaceId, id: typebot?.workspaceId,
// }, },
// }, },
// }, },
// }, },
// _sum: { storageUsed: true }, _sum: { storageUsed: true },
// }) })
// if (!totalStorageUsed) return false if (!totalStorageUsed) return false
// const hasSentFirstEmail = workspace.storageLimitFirstEmailSentAt !== null const hasSentFirstEmail = workspace.storageLimitFirstEmailSentAt !== null
// const hasSentSecondEmail = workspace.storageLimitSecondEmailSentAt !== null const hasSentSecondEmail = workspace.storageLimitSecondEmailSentAt !== null
// const storageLimit = getStorageLimit(typebot.workspace) const storageLimit = getStorageLimit(typebot.workspace)
// const storageLimitBytes = storageLimit * 1024 * 1024 * 1024 const storageLimitBytes = storageLimit * 1024 * 1024 * 1024
// if ( if (
// totalStorageUsed >= storageLimitBytes * LIMIT_EMAIL_TRIGGER_PERCENT && totalStorageUsed >= storageLimitBytes * LIMIT_EMAIL_TRIGGER_PERCENT &&
// !hasSentFirstEmail && !hasSentFirstEmail &&
// env('E2E_TEST') !== 'true' env('E2E_TEST') !== 'true'
// ) )
// await sendAlmostReachStorageLimitNotification({ await sendAlmostReachStorageLimitNotification({
// workspaceId: workspace.id, workspaceId: workspace.id,
// storageLimit, storageLimit,
// }) })
// if ( if (
// totalStorageUsed >= storageLimitBytes && totalStorageUsed >= storageLimitBytes &&
// !hasSentSecondEmail && !hasSentSecondEmail &&
// env('E2E_TEST') !== 'true' env('E2E_TEST') !== 'true'
// ) )
// await sendReachStorageLimitNotification({ await sendReachStorageLimitNotification({
// workspaceId: workspace.id, workspaceId: workspace.id,
// storageLimit, storageLimit,
// }) })
// return totalStorageUsed >= storageLimitBytes return totalStorageUsed >= storageLimitBytes
// } }
// const sendAlmostReachStorageLimitNotification = async ({ const sendAlmostReachStorageLimitNotification = async ({
// workspaceId, workspaceId,
// storageLimit, storageLimit,
// }: { }: {
// workspaceId: string workspaceId: string
// storageLimit: number storageLimit: number
// }) => { }) => {
// const members = await prisma.memberInWorkspace.findMany({ const members = await prisma.memberInWorkspace.findMany({
// where: { role: WorkspaceRole.ADMIN, workspaceId }, where: { role: WorkspaceRole.ADMIN, workspaceId },
// include: { user: { select: { email: true } } }, include: { user: { select: { email: true } } },
// }) })
// await sendAlmostReachedStorageLimitEmail({ await sendAlmostReachedStorageLimitEmail({
// to: members.map((member) => member.user.email).filter(isDefined), to: members.map((member) => member.user.email).filter(isDefined),
// storageLimit, storageLimit,
// url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`, url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`,
// }) })
// await prisma.workspace.update({ await prisma.workspace.update({
// where: { id: workspaceId }, where: { id: workspaceId },
// data: { storageLimitFirstEmailSentAt: new Date() }, data: { storageLimitFirstEmailSentAt: new Date() },
// }) })
// } }
// const sendReachStorageLimitNotification = async ({ const sendReachStorageLimitNotification = async ({
// workspaceId, workspaceId,
// storageLimit, storageLimit,
// }: { }: {
// workspaceId: string workspaceId: string
// storageLimit: number storageLimit: number
// }) => { }) => {
// const members = await prisma.memberInWorkspace.findMany({ const members = await prisma.memberInWorkspace.findMany({
// where: { role: WorkspaceRole.ADMIN, workspaceId }, where: { role: WorkspaceRole.ADMIN, workspaceId },
// include: { user: { select: { email: true } } }, include: { user: { select: { email: true } } },
// }) })
// await sendReachedStorageLimitEmail({ await sendReachedStorageLimitEmail({
// to: members.map((member) => member.user.email).filter(isDefined), to: members.map((member) => member.user.email).filter(isDefined),
// storageLimit, storageLimit,
// url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`, url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`,
// }) })
// await prisma.workspace.update({ await prisma.workspace.update({
// where: { id: workspaceId }, where: { id: workspaceId },
// data: { storageLimitSecondEmailSentAt: new Date() }, data: { storageLimitSecondEmailSentAt: new Date() },
// }) })
// } }
export default withSentry(handler) export default withSentry(handler)

View File

@ -1,10 +1,16 @@
import { authenticateUser } from '@/features/auth/api' import { authenticateUser } from '@/features/auth/api'
import prisma from '@/lib/prisma' import prisma from '@/lib/prisma'
import { ResultWithAnswers } from 'models' import { WorkspaceRole } from 'db'
import {
sendAlmostReachedChatsLimitEmail,
sendReachedChatsLimitEmail,
} from 'emails'
import { ResultWithAnswers, Workspace } from 'models'
import { NextApiRequest, NextApiResponse } from 'next' import { NextApiRequest, NextApiResponse } from 'next'
import { env, getChatsLimit, isDefined } from 'utils'
import { methodNotAllowed } from 'utils/api' import { methodNotAllowed } from 'utils/api'
// const LIMIT_EMAIL_TRIGGER_PERCENT = 0.8 const LIMIT_EMAIL_TRIGGER_PERCENT = 0.8
const handler = async (req: NextApiRequest, res: NextApiResponse) => { const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === 'GET') { if (req.method === 'GET') {
@ -49,113 +55,112 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
}, },
}, },
}) })
// TODO: enable storage limit on 1st of November 2022 const hasReachedLimit = await checkChatsUsage(result.typebot.workspace)
// const hasReachedLimit = await checkChatsUsage(result.typebot.workspace) res.send({ result, hasReachedLimit })
res.send({ result, hasReachedLimit: false })
return return
} }
methodNotAllowed(res) methodNotAllowed(res)
} }
// const checkChatsUsage = async ( const checkChatsUsage = async (
// workspace: Pick< workspace: Pick<
// Workspace, Workspace,
// | 'id' | 'id'
// | 'plan' | 'plan'
// | 'additionalChatsIndex' | 'additionalChatsIndex'
// | 'chatsLimitFirstEmailSentAt' | 'chatsLimitFirstEmailSentAt'
// | 'chatsLimitSecondEmailSentAt' | 'chatsLimitSecondEmailSentAt'
// | 'customChatsLimit' | 'customChatsLimit'
// > >
// ) => { ) => {
// const chatsLimit = getChatsLimit(workspace) const chatsLimit = getChatsLimit(workspace)
// if (chatsLimit === -1) return if (chatsLimit === -1) return
// const now = new Date() const now = new Date()
// const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1) const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1)
// const lastDayOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0) const lastDayOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0)
// const firstDayOfNextMonth = new Date(now.getFullYear(), now.getMonth() + 1, 1) const firstDayOfNextMonth = new Date(now.getFullYear(), now.getMonth() + 1, 1)
// const chatsCount = await prisma.result.count({ const chatsCount = await prisma.result.count({
// where: { where: {
// typebot: { workspaceId: workspace.id }, typebot: { workspaceId: workspace.id },
// hasStarted: true, hasStarted: true,
// createdAt: { gte: firstDayOfMonth, lte: lastDayOfMonth }, createdAt: { gte: firstDayOfMonth, lte: lastDayOfMonth },
// }, },
// }) })
// const hasSentFirstEmail = const hasSentFirstEmail =
// workspace.chatsLimitFirstEmailSentAt !== null && workspace.chatsLimitFirstEmailSentAt !== null &&
// workspace.chatsLimitFirstEmailSentAt < firstDayOfNextMonth && workspace.chatsLimitFirstEmailSentAt < firstDayOfNextMonth &&
// workspace.chatsLimitFirstEmailSentAt > firstDayOfMonth workspace.chatsLimitFirstEmailSentAt > firstDayOfMonth
// const hasSentSecondEmail = const hasSentSecondEmail =
// workspace.chatsLimitSecondEmailSentAt !== null && workspace.chatsLimitSecondEmailSentAt !== null &&
// workspace.chatsLimitSecondEmailSentAt < firstDayOfNextMonth && workspace.chatsLimitSecondEmailSentAt < firstDayOfNextMonth &&
// workspace.chatsLimitSecondEmailSentAt > firstDayOfMonth workspace.chatsLimitSecondEmailSentAt > firstDayOfMonth
// if ( if (
// chatsCount >= chatsLimit * LIMIT_EMAIL_TRIGGER_PERCENT && chatsCount >= chatsLimit * LIMIT_EMAIL_TRIGGER_PERCENT &&
// !hasSentFirstEmail && !hasSentFirstEmail &&
// env('E2E_TEST') !== 'true' env('E2E_TEST') !== 'true'
// ) )
// await sendAlmostReachChatsLimitNotification({ await sendAlmostReachChatsLimitNotification({
// workspaceId: workspace.id, workspaceId: workspace.id,
// chatsLimit, chatsLimit,
// }) })
// if ( if (
// chatsCount >= chatsLimit && chatsCount >= chatsLimit &&
// !hasSentSecondEmail && !hasSentSecondEmail &&
// env('E2E_TEST') !== 'true' env('E2E_TEST') !== 'true'
// ) )
// await sendReachedAlertNotification({ await sendReachedAlertNotification({
// workspaceId: workspace.id, workspaceId: workspace.id,
// chatsLimit, chatsLimit,
// }) })
// return chatsCount >= chatsLimit return chatsCount >= chatsLimit
// } }
// const sendAlmostReachChatsLimitNotification = async ({ const sendAlmostReachChatsLimitNotification = async ({
// workspaceId, workspaceId,
// chatsLimit, chatsLimit,
// }: { }: {
// workspaceId: string workspaceId: string
// chatsLimit: number chatsLimit: number
// }) => { }) => {
// const members = await prisma.memberInWorkspace.findMany({ const members = await prisma.memberInWorkspace.findMany({
// where: { role: WorkspaceRole.ADMIN, workspaceId }, where: { role: WorkspaceRole.ADMIN, workspaceId },
// include: { user: { select: { email: true } } }, include: { user: { select: { email: true } } },
// }) })
// await sendAlmostReachedChatsLimitEmail({ await sendAlmostReachedChatsLimitEmail({
// to: members.map((member) => member.user.email).filter(isDefined), to: members.map((member) => member.user.email).filter(isDefined),
// chatsLimit, chatsLimit,
// url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`, url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`,
// }) })
// await prisma.workspace.update({ await prisma.workspace.update({
// where: { id: workspaceId }, where: { id: workspaceId },
// data: { chatsLimitFirstEmailSentAt: new Date() }, data: { chatsLimitFirstEmailSentAt: new Date() },
// }) })
// } }
// const sendReachedAlertNotification = async ({ const sendReachedAlertNotification = async ({
// workspaceId, workspaceId,
// chatsLimit, chatsLimit,
// }: { }: {
// workspaceId: string workspaceId: string
// chatsLimit: number chatsLimit: number
// }) => { }) => {
// const members = await prisma.memberInWorkspace.findMany({ const members = await prisma.memberInWorkspace.findMany({
// where: { role: WorkspaceRole.ADMIN, workspaceId }, where: { role: WorkspaceRole.ADMIN, workspaceId },
// include: { user: { select: { email: true } } }, include: { user: { select: { email: true } } },
// }) })
// await sendReachedChatsLimitEmail({ await sendReachedChatsLimitEmail({
// to: members.map((member) => member.user.email).filter(isDefined), to: members.map((member) => member.user.email).filter(isDefined),
// chatsLimit, chatsLimit,
// url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`, url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspaceId}`,
// }) })
// await prisma.workspace.update({ await prisma.workspace.update({
// where: { id: workspaceId }, where: { id: workspaceId },
// data: { chatsLimitSecondEmailSentAt: new Date() }, data: { chatsLimitSecondEmailSentAt: new Date() },
// }) })
// } }
export default handler export default handler