🛂 Reset isQuarantined on the first of month
This commit is contained in:
@@ -13,6 +13,7 @@ import { SendMailOptions } from 'nodemailer'
|
|||||||
import { sendEmail } from '../sendEmail'
|
import { sendEmail } from '../sendEmail'
|
||||||
|
|
||||||
type AlmostReachedChatsLimitEmailProps = {
|
type AlmostReachedChatsLimitEmailProps = {
|
||||||
|
usagePercent: number
|
||||||
chatsLimit: number
|
chatsLimit: number
|
||||||
url: string
|
url: string
|
||||||
}
|
}
|
||||||
@@ -26,6 +27,7 @@ const readableResetDate = firstDayOfNextMonth
|
|||||||
.join(' ')
|
.join(' ')
|
||||||
|
|
||||||
export const AlmostReachedChatsLimitEmail = ({
|
export const AlmostReachedChatsLimitEmail = ({
|
||||||
|
usagePercent,
|
||||||
chatsLimit,
|
chatsLimit,
|
||||||
url,
|
url,
|
||||||
}: AlmostReachedChatsLimitEmailProps) => {
|
}: AlmostReachedChatsLimitEmailProps) => {
|
||||||
@@ -45,7 +47,8 @@ export const AlmostReachedChatsLimitEmail = ({
|
|||||||
<Text>Your bots are chatting a lot. That's amazing. 💙</Text>
|
<Text>Your bots are chatting a lot. That's amazing. 💙</Text>
|
||||||
<Text>
|
<Text>
|
||||||
This means you've almost reached your monthly chats limit.
|
This means you've almost reached your monthly chats limit.
|
||||||
You currently reached 80% of {readableChatsLimit} chats.
|
You currently reached {usagePercent}% of {readableChatsLimit}{' '}
|
||||||
|
chats.
|
||||||
</Text>
|
</Text>
|
||||||
<Text>This limit will be reset on {readableResetDate}.</Text>
|
<Text>This limit will be reset on {readableResetDate}.</Text>
|
||||||
<Text fontWeight="800">
|
<Text fontWeight="800">
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
import React, { ComponentProps } from 'react'
|
|
||||||
import {
|
|
||||||
Mjml,
|
|
||||||
MjmlBody,
|
|
||||||
MjmlSection,
|
|
||||||
MjmlColumn,
|
|
||||||
MjmlSpacer,
|
|
||||||
} from '@faire/mjml-react'
|
|
||||||
import { render } from '@faire/mjml-react/utils/render'
|
|
||||||
import { Button, Head, HeroImage, Text } from '../components'
|
|
||||||
import { SendMailOptions } from 'nodemailer'
|
|
||||||
import { sendEmail } from '../sendEmail'
|
|
||||||
|
|
||||||
type AlmostReachedStorageLimitEmailProps = {
|
|
||||||
storageLimit: number
|
|
||||||
url: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const AlmostReachedStorageLimitEmail = ({
|
|
||||||
storageLimit,
|
|
||||||
url,
|
|
||||||
}: AlmostReachedStorageLimitEmailProps) => {
|
|
||||||
const readableStorageLimit = `${storageLimit} GB`
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Mjml>
|
|
||||||
<Head />
|
|
||||||
<MjmlBody width={600}>
|
|
||||||
<MjmlSection padding="0">
|
|
||||||
<MjmlColumn>
|
|
||||||
<HeroImage src="https://typebot.s3.fr-par.scw.cloud/public/assets/yourBotIsFlyingEmailBanner.png" />
|
|
||||||
</MjmlColumn>
|
|
||||||
</MjmlSection>
|
|
||||||
<MjmlSection padding="0 24px" cssClass="smooth">
|
|
||||||
<MjmlColumn>
|
|
||||||
<Text>Your bots are working a lot. That's amazing. 🤖</Text>
|
|
||||||
<Text>
|
|
||||||
This means you've almost reached your storage limit. You
|
|
||||||
currently reached 80% of your {readableStorageLimit} storage
|
|
||||||
limit.
|
|
||||||
</Text>
|
|
||||||
<Text fontWeight="800">
|
|
||||||
Upon this limit your bots will still continue to collect new
|
|
||||||
files, but we ask you kindly to upgrade your storage limit or
|
|
||||||
delete existing results to free up space.
|
|
||||||
</Text>
|
|
||||||
<MjmlSpacer height="24px" />
|
|
||||||
<Button link={url}>Upgrade workspace</Button>
|
|
||||||
</MjmlColumn>
|
|
||||||
</MjmlSection>
|
|
||||||
</MjmlBody>
|
|
||||||
</Mjml>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const sendAlmostReachedStorageLimitEmail = ({
|
|
||||||
to,
|
|
||||||
...props
|
|
||||||
}: Pick<SendMailOptions, 'to'> &
|
|
||||||
ComponentProps<typeof AlmostReachedStorageLimitEmail>) =>
|
|
||||||
sendEmail({
|
|
||||||
to,
|
|
||||||
subject: "You're close to your storage limit",
|
|
||||||
html: render(<AlmostReachedStorageLimitEmail {...props} />).html,
|
|
||||||
})
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
import { ComponentProps } from 'react'
|
|
||||||
import {
|
|
||||||
Mjml,
|
|
||||||
MjmlBody,
|
|
||||||
MjmlSection,
|
|
||||||
MjmlColumn,
|
|
||||||
MjmlSpacer,
|
|
||||||
} from '@faire/mjml-react'
|
|
||||||
import { render } from '@faire/mjml-react/utils/render'
|
|
||||||
import { Button, Head, HeroImage, Text } from '../components'
|
|
||||||
import { parseNumberWithCommas } from '@typebot.io/lib'
|
|
||||||
import { SendMailOptions } from 'nodemailer'
|
|
||||||
import { sendEmail } from '../sendEmail'
|
|
||||||
|
|
||||||
type ReachedChatsLimitEmailProps = {
|
|
||||||
chatsLimit: number
|
|
||||||
url: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ReachedChatsLimitEmail = ({
|
|
||||||
chatsLimit,
|
|
||||||
url,
|
|
||||||
}: ReachedChatsLimitEmailProps) => {
|
|
||||||
const readableChatsLimit = parseNumberWithCommas(chatsLimit)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Mjml>
|
|
||||||
<Head />
|
|
||||||
<MjmlBody width={600}>
|
|
||||||
<MjmlSection padding="0">
|
|
||||||
<MjmlColumn>
|
|
||||||
<HeroImage src="https://typebot.s3.fr-par.scw.cloud/public/assets/actionRequiredEmailBanner.png" />
|
|
||||||
</MjmlColumn>
|
|
||||||
</MjmlSection>
|
|
||||||
<MjmlSection padding="0 24px" cssClass="smooth">
|
|
||||||
<MjmlColumn>
|
|
||||||
<Text>
|
|
||||||
It just happened, you've reached your monthly{' '}
|
|
||||||
{readableChatsLimit} chats limit 😮
|
|
||||||
</Text>
|
|
||||||
<Text>
|
|
||||||
If you'd like your bots to continue chatting with your users
|
|
||||||
this month, then you need to upgrade your plan. 🚀
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<MjmlSpacer height="24px" />
|
|
||||||
<Button link={url}>Upgrade workspace</Button>
|
|
||||||
</MjmlColumn>
|
|
||||||
</MjmlSection>
|
|
||||||
</MjmlBody>
|
|
||||||
</Mjml>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const sendReachedChatsLimitEmail = ({
|
|
||||||
to,
|
|
||||||
...props
|
|
||||||
}: Pick<SendMailOptions, 'to'> &
|
|
||||||
ComponentProps<typeof ReachedChatsLimitEmail>) =>
|
|
||||||
sendEmail({
|
|
||||||
to,
|
|
||||||
subject: "You've reached your chats limit",
|
|
||||||
html: render(<ReachedChatsLimitEmail {...props} />).html,
|
|
||||||
})
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
import React, { ComponentProps } from 'react'
|
|
||||||
import {
|
|
||||||
Mjml,
|
|
||||||
MjmlBody,
|
|
||||||
MjmlSection,
|
|
||||||
MjmlColumn,
|
|
||||||
MjmlSpacer,
|
|
||||||
} from '@faire/mjml-react'
|
|
||||||
import { render } from '@faire/mjml-react/utils/render'
|
|
||||||
import { Button, Head, HeroImage, Text } from '../components'
|
|
||||||
import { SendMailOptions } from 'nodemailer'
|
|
||||||
import { sendEmail } from '../sendEmail'
|
|
||||||
|
|
||||||
type ReachedStorageLimitEmailProps = {
|
|
||||||
storageLimit: number
|
|
||||||
url: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ReachedStorageLimitEmail = ({
|
|
||||||
storageLimit,
|
|
||||||
url,
|
|
||||||
}: ReachedStorageLimitEmailProps) => {
|
|
||||||
const readableStorageLimit = `${storageLimit} GB`
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Mjml>
|
|
||||||
<Head />
|
|
||||||
<MjmlBody width={600}>
|
|
||||||
<MjmlSection padding="0">
|
|
||||||
<MjmlColumn>
|
|
||||||
<HeroImage src="https://typebot.s3.fr-par.scw.cloud/public/assets/actionRequiredEmailBanner.png" />
|
|
||||||
</MjmlColumn>
|
|
||||||
</MjmlSection>
|
|
||||||
<MjmlSection padding="0 24px" cssClass="smooth">
|
|
||||||
<MjmlColumn>
|
|
||||||
<Text>
|
|
||||||
It just happened, you've reached your {readableStorageLimit}{' '}
|
|
||||||
storage limit 😮
|
|
||||||
</Text>
|
|
||||||
<Text>
|
|
||||||
If you'd like to continue collecting files, then you need to
|
|
||||||
upgrade your plan or remove existing results to free up space. 🚀
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<MjmlSpacer height="24px" />
|
|
||||||
<Button link={url}>Upgrade workspace</Button>
|
|
||||||
</MjmlColumn>
|
|
||||||
</MjmlSection>
|
|
||||||
</MjmlBody>
|
|
||||||
</Mjml>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const sendReachedStorageLimitEmail = ({
|
|
||||||
to,
|
|
||||||
...props
|
|
||||||
}: Pick<SendMailOptions, 'to'> &
|
|
||||||
ComponentProps<typeof ReachedStorageLimitEmail>) =>
|
|
||||||
sendEmail({
|
|
||||||
to,
|
|
||||||
subject: "You've reached your storage limit",
|
|
||||||
html: render(<ReachedStorageLimitEmail {...props} />).html,
|
|
||||||
})
|
|
||||||
@@ -1,8 +1,5 @@
|
|||||||
export * from './AlmostReachedChatsLimitEmail'
|
export * from './AlmostReachedChatsLimitEmail'
|
||||||
export * from './AlmostReachedStorageLimitEmail'
|
|
||||||
export * from './DefaultBotNotificationEmail'
|
export * from './DefaultBotNotificationEmail'
|
||||||
export * from './GuestInvitationEmail'
|
export * from './GuestInvitationEmail'
|
||||||
export * from './ReachedChatsLimitEmail'
|
|
||||||
export * from './ReachedStorageLimitEmail'
|
|
||||||
export * from './WorkspaceMemberInvitationEmail'
|
export * from './WorkspaceMemberInvitationEmail'
|
||||||
export * from './MagicLinkEmail'
|
export * from './MagicLinkEmail'
|
||||||
|
|||||||
@@ -3,11 +3,8 @@ import fs from 'fs'
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import {
|
import {
|
||||||
AlmostReachedChatsLimitEmail,
|
AlmostReachedChatsLimitEmail,
|
||||||
AlmostReachedStorageLimitEmail,
|
|
||||||
DefaultBotNotificationEmail,
|
DefaultBotNotificationEmail,
|
||||||
GuestInvitationEmail,
|
GuestInvitationEmail,
|
||||||
ReachedChatsLimitEmail,
|
|
||||||
ReachedStorageLimitEmail,
|
|
||||||
WorkspaceMemberInvitation,
|
WorkspaceMemberInvitation,
|
||||||
} from './emails'
|
} from './emails'
|
||||||
import { MagicLinkEmail } from './emails/MagicLinkEmail'
|
import { MagicLinkEmail } from './emails/MagicLinkEmail'
|
||||||
@@ -47,38 +44,12 @@ const createHtmlFile = () => {
|
|||||||
path.resolve(__dirname, 'dist', 'almostReachedChatsLimit.html'),
|
path.resolve(__dirname, 'dist', 'almostReachedChatsLimit.html'),
|
||||||
render(
|
render(
|
||||||
<AlmostReachedChatsLimitEmail
|
<AlmostReachedChatsLimitEmail
|
||||||
|
usagePercent={86}
|
||||||
url={'https://app.typebot.io'}
|
url={'https://app.typebot.io'}
|
||||||
chatsLimit={2000}
|
chatsLimit={2000}
|
||||||
/>
|
/>
|
||||||
).html
|
).html
|
||||||
)
|
)
|
||||||
fs.writeFileSync(
|
|
||||||
path.resolve(__dirname, 'dist', 'almostReachedStorageLimit.html'),
|
|
||||||
render(
|
|
||||||
<AlmostReachedStorageLimitEmail
|
|
||||||
url={'https://app.typebot.io'}
|
|
||||||
storageLimit={4}
|
|
||||||
/>
|
|
||||||
).html
|
|
||||||
)
|
|
||||||
fs.writeFileSync(
|
|
||||||
path.resolve(__dirname, 'dist', 'reachedChatsLimit.html'),
|
|
||||||
render(
|
|
||||||
<ReachedChatsLimitEmail
|
|
||||||
url={'https://app.typebot.io'}
|
|
||||||
chatsLimit={10000}
|
|
||||||
/>
|
|
||||||
).html
|
|
||||||
)
|
|
||||||
fs.writeFileSync(
|
|
||||||
path.resolve(__dirname, 'dist', 'reachedStorageLimit.html'),
|
|
||||||
render(
|
|
||||||
<ReachedStorageLimitEmail
|
|
||||||
url={'https://app.typebot.io'}
|
|
||||||
storageLimit={8}
|
|
||||||
/>
|
|
||||||
).html
|
|
||||||
)
|
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
path.resolve(__dirname, 'dist', 'defaultBotNotification.html'),
|
path.resolve(__dirname, 'dist', 'defaultBotNotification.html'),
|
||||||
render(
|
render(
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export const cleanDatabase = async () => {
|
|||||||
if (isFirstOfMonth) {
|
if (isFirstOfMonth) {
|
||||||
await deleteArchivedResults()
|
await deleteArchivedResults()
|
||||||
await deleteArchivedTypebots()
|
await deleteArchivedTypebots()
|
||||||
|
await resetQuarantinedWorkspaces()
|
||||||
}
|
}
|
||||||
console.log('Done!')
|
console.log('Done!')
|
||||||
}
|
}
|
||||||
@@ -118,4 +119,14 @@ const deleteExpiredVerificationTokens = async () => {
|
|||||||
console.log(`Deleted ${count} expired verifiations tokens.`)
|
console.log(`Deleted ${count} expired verifiations tokens.`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resetQuarantinedWorkspaces = async () =>
|
||||||
|
prisma.workspace.updateMany({
|
||||||
|
where: {
|
||||||
|
isQuarantined: true,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
isQuarantined: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
cleanDatabase().then()
|
cleanDatabase().then()
|
||||||
|
|||||||
@@ -11,6 +11,22 @@ import { sendTelemetryEvents } from '@typebot.io/lib/telemetry/sendTelemetryEven
|
|||||||
import { Workspace } from '@typebot.io/schemas'
|
import { Workspace } from '@typebot.io/schemas'
|
||||||
|
|
||||||
const prisma = new PrismaClient()
|
const prisma = new PrismaClient()
|
||||||
|
const LIMIT_EMAIL_TRIGGER_PERCENT = 0.8
|
||||||
|
|
||||||
|
type WorkspaceForDigest = Pick<
|
||||||
|
Workspace,
|
||||||
|
| 'id'
|
||||||
|
| 'plan'
|
||||||
|
| 'customChatsLimit'
|
||||||
|
| 'customStorageLimit'
|
||||||
|
| 'additionalChatsIndex'
|
||||||
|
| 'additionalStorageIndex'
|
||||||
|
| 'isQuarantined'
|
||||||
|
> & {
|
||||||
|
members: (Pick<MemberInWorkspace, 'role'> & {
|
||||||
|
user: { id: string; email: string | null }
|
||||||
|
})[]
|
||||||
|
}
|
||||||
|
|
||||||
export const sendTotalResultsDigest = async () => {
|
export const sendTotalResultsDigest = async () => {
|
||||||
await promptAndSetEnvironment('production')
|
await promptAndSetEnvironment('production')
|
||||||
@@ -53,12 +69,15 @@ export const sendTotalResultsDigest = async () => {
|
|||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
typebots: { select: { id: true } },
|
typebots: { select: { id: true } },
|
||||||
members: { select: { userId: true, role: true } },
|
members: {
|
||||||
|
select: { user: { select: { id: true, email: true } }, role: true },
|
||||||
|
},
|
||||||
additionalChatsIndex: true,
|
additionalChatsIndex: true,
|
||||||
additionalStorageIndex: true,
|
additionalStorageIndex: true,
|
||||||
customChatsLimit: true,
|
customChatsLimit: true,
|
||||||
customStorageLimit: true,
|
customStorageLimit: true,
|
||||||
plan: true,
|
plan: true,
|
||||||
|
isQuarantined: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -71,7 +90,7 @@ export const sendTotalResultsDigest = async () => {
|
|||||||
return workspace.members
|
return workspace.members
|
||||||
.filter((member) => member.role !== WorkspaceRole.GUEST)
|
.filter((member) => member.role !== WorkspaceRole.GUEST)
|
||||||
.map((member, memberIndex) => ({
|
.map((member, memberIndex) => ({
|
||||||
userId: member.userId,
|
userId: member.user.id,
|
||||||
workspace: workspace,
|
workspace: workspace,
|
||||||
typebotId: result.typebotId,
|
typebotId: result.typebotId,
|
||||||
totalResultsYesterday: result._count._all,
|
totalResultsYesterday: result._count._all,
|
||||||
@@ -115,20 +134,13 @@ export const sendTotalResultsDigest = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sendAlertIfLimitReached = async (
|
const sendAlertIfLimitReached = async (
|
||||||
workspaces: (Pick<
|
workspaces: WorkspaceForDigest[]
|
||||||
Workspace,
|
|
||||||
| 'id'
|
|
||||||
| 'plan'
|
|
||||||
| 'customChatsLimit'
|
|
||||||
| 'customStorageLimit'
|
|
||||||
| 'additionalChatsIndex'
|
|
||||||
| 'additionalStorageIndex'
|
|
||||||
> & { members: Pick<MemberInWorkspace, 'userId' | 'role'>[] })[]
|
|
||||||
): Promise<TelemetryEvent[]> => {
|
): Promise<TelemetryEvent[]> => {
|
||||||
const events: TelemetryEvent[] = []
|
const events: TelemetryEvent[] = []
|
||||||
const taggedWorkspaces: string[] = []
|
const taggedWorkspaces: string[] = []
|
||||||
for (const workspace of workspaces) {
|
for (const workspace of workspaces) {
|
||||||
if (taggedWorkspaces.includes(workspace.id)) continue
|
if (taggedWorkspaces.includes(workspace.id) || workspace.isQuarantined)
|
||||||
|
continue
|
||||||
taggedWorkspaces.push(workspace.id)
|
taggedWorkspaces.push(workspace.id)
|
||||||
const { totalChatsUsed, totalStorageUsed } = await getUsage(workspace.id)
|
const { totalChatsUsed, totalStorageUsed } = await getUsage(workspace.id)
|
||||||
const totalStorageUsedInGb = totalStorageUsed / 1024 / 1024 / 1024
|
const totalStorageUsedInGb = totalStorageUsed / 1024 / 1024 / 1024
|
||||||
@@ -145,7 +157,7 @@ const sendAlertIfLimitReached = async (
|
|||||||
(member) =>
|
(member) =>
|
||||||
({
|
({
|
||||||
name: 'Workspace limit reached',
|
name: 'Workspace limit reached',
|
||||||
userId: member.userId,
|
userId: member.user.id,
|
||||||
workspaceId: workspace.id,
|
workspaceId: workspace.id,
|
||||||
data: {
|
data: {
|
||||||
totalChatsUsed,
|
totalChatsUsed,
|
||||||
@@ -156,7 +168,20 @@ const sendAlertIfLimitReached = async (
|
|||||||
} satisfies TelemetryEvent)
|
} satisfies TelemetryEvent)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
// if (
|
||||||
|
// chatsLimit > 0 &&
|
||||||
|
// totalChatsUsed >= chatsLimit * LIMIT_EMAIL_TRIGGER_PERCENT
|
||||||
|
// )
|
||||||
|
// await sendAlmostReachedChatsLimitEmail({
|
||||||
|
// to: workspace.members
|
||||||
|
// .map((member) => member.user.email)
|
||||||
|
// .filter(isDefined),
|
||||||
|
// usagePercent: Math.round((totalChatsUsed / chatsLimit) * 100),
|
||||||
|
// chatsLimit,
|
||||||
|
// url: `https://app.typebot.io/typebots?workspaceId=${workspace.id}`,
|
||||||
|
// })
|
||||||
}
|
}
|
||||||
return events
|
return events
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user