Skip to content

Commit

Permalink
📈 Add telemetry webhook
Browse files Browse the repository at this point in the history
Closes #357
  • Loading branch information
baptisteArno committed Mar 14, 2023
1 parent e713211 commit 9ca17e4
Show file tree
Hide file tree
Showing 22 changed files with 523 additions and 34 deletions.
21 changes: 21 additions & 0 deletions .github/workflows/send-total-results-digest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Send total results daily digest

on:
schedule:
- cron: '0 5 * * *'

jobs:
send:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./packages/scripts
env:
DATABASE_URL: '${{ secrets.DATABASE_URL }}'
TELEMETRY_WEBHOOK_URL: '${{ secrets.TELEMETRY_WEBHOOK_URL }}'
TELEMETRY_WEBHOOK_BEARER_TOKEN: '${{ secrets.TELEMETRY_WEBHOOK_BEARER_TOKEN }}'
steps:
- uses: actions/checkout@v2
- uses: pnpm/action-setup@v2.2.2
- run: pnpm i --frozen-lockfile
- run: pnpm turbo run telemetry:sendTotalResultsDigest
1 change: 1 addition & 0 deletions apps/builder/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"nodemailer": "6.9.1",
"nprogress": "0.2.0",
"papaparse": "5.3.2",
"posthog-node": "^2.5.4",
"prettier": "2.8.4",
"qs": "6.11.0",
"react": "18.2.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,13 @@ export const createCheckoutSession = authenticatedProcedure
name: 'never',
},
mode: 'subscription',
metadata: { workspaceId, plan, additionalChats, additionalStorage },
metadata: {
workspaceId,
plan,
additionalChats,
additionalStorage,
userId: user.id,
},
currency,
billing_address_collection: 'required',
automatic_tax: { enabled: true },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { sendTelemetryEvents } from 'utils/telemetry/sendTelemetryEvent'
import prisma from '@/lib/prisma'
import { authenticatedProcedure } from '@/utils/server/trpc'
import { TRPCError } from '@trpc/server'
Expand Down Expand Up @@ -141,6 +142,19 @@ export const updateSubscription = authenticatedProcedure
},
})

await sendTelemetryEvents([
{
name: 'Subscription updated',
workspaceId,
userId: user.id,
data: {
plan,
additionalChatsIndex: additionalChats,
additionalStorageIndex: additionalStorage,
},
},
])

return { workspace: updatedWorkspace }
}
)
93 changes: 93 additions & 0 deletions apps/builder/src/features/telemetry/api/processTelemetryEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { eventSchema } from 'models/features/telemetry'
import { z } from 'zod'
import { PostHog } from 'posthog-node'
import { TRPCError } from '@trpc/server'
import got from 'got'
import { authenticatedProcedure } from '@/utils/server/trpc'

// Only used for the cloud version of Typebot. It's the way it processes telemetry events and inject it to thrid-party services.
export const processTelemetryEvent = authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/t/process',
description:
"Only used for the cloud version of Typebot. It's the way it processes telemetry events and inject it to thrid-party services.",
},
})
.input(
z.object({
events: z.array(eventSchema),
})
)
.output(
z.object({
message: z.literal('Events injected'),
})
)
.query(async ({ input: { events }, ctx: { user } }) => {
if (user.email !== process.env.ADMIN_EMAIL)
throw new TRPCError({
code: 'BAD_REQUEST',
message: 'Only app admin can process telemetry events',
})
if (!process.env.POSTHOG_API_KEY)
throw new TRPCError({
code: 'BAD_REQUEST',
message: 'Server does not have POSTHOG_API_KEY configured',
})
const client = new PostHog(process.env.POSTHOG_API_KEY, {
host: 'https://eu.posthog.com',
})

events.forEach(async (event) => {
if (event.name === 'User created') {
client.identify({
distinctId: event.userId,
properties: event.data,
})
}
if (
event.name === 'Workspace created' ||
event.name === 'Subscription updated'
)
client.groupIdentify({
groupType: 'workspace',
groupKey: event.workspaceId,
properties: event.data,
})
if (
event.name === 'Typebot created' ||
event.name === 'Typebot published'
)
client.groupIdentify({
groupType: 'typebot',
groupKey: event.typebotId,
properties: { name: event.data.name },
})
if (
event.name === 'User created' &&
process.env.USER_CREATED_WEBHOOK_URL
) {
await got.post(process.env.USER_CREATED_WEBHOOK_URL, {
json: {
email: event.data.email,
name: event.data.name ? event.data.name.split(' ')[0] : undefined,
},
})
}
const groups: { workspace?: string; typebot?: string } = {}
if ('workspaceId' in event) groups['workspace'] = event.workspaceId
if ('typebotId' in event) groups['typebot'] = event.typebotId
client.capture({
distinctId: event.userId,
event: event.name,
properties: event.data,
groups,
})
})

await client.shutdownAsync()

return { message: 'Events injected' }
})
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { sendTelemetryEvents } from 'utils/telemetry/sendTelemetryEvent'
import prisma from '@/lib/prisma'
import { authenticatedProcedure } from '@/utils/server/trpc'
import { TRPCError } from '@trpc/server'
Expand Down Expand Up @@ -49,6 +50,18 @@ export const createWorkspaceProcedure = authenticatedProcedure
},
})) as Workspace

await sendTelemetryEvents([
{
name: 'Workspace created',
workspaceId: newWorkspace.id,
userId: user.id,
data: {
name,
plan,
},
},
])

return {
workspace: newWorkspace,
}
Expand Down
42 changes: 29 additions & 13 deletions apps/builder/src/pages/api/auth/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
import { PrismaClient, Prisma, WorkspaceRole, Session } from 'db'
import type { Adapter, AdapterUser } from 'next-auth/adapters'
import { createId } from '@paralleldrive/cuid2'
import { got } from 'got'
import { generateId } from 'utils'
import { parseWorkspaceDefaultPlan } from '@/features/workspace'
import {
getNewUserInvitations,
convertInvitationsToCollaborations,
joinWorkspaces,
} from '@/features/auth/api'
import { sendTelemetryEvents } from 'utils/telemetry/sendTelemetryEvent'
import { TelemetryEvent } from 'models/features/telemetry'

export function CustomAdapter(p: PrismaClient): Adapter {
return {
Expand All @@ -28,6 +29,11 @@ export function CustomAdapter(p: PrismaClient): Adapter {
workspaceInvitations.length === 0
)
throw Error('New users are forbidden')

const newWorkspaceData = {
name: data.name ? `${data.name}'s workspace` : `My workspace`,
plan: parseWorkspaceDefaultPlan(data.email),
}
const createdUser = await p.user.create({
data: {
...data,
Expand All @@ -42,25 +48,35 @@ export function CustomAdapter(p: PrismaClient): Adapter {
create: {
role: WorkspaceRole.ADMIN,
workspace: {
create: {
name: data.name
? `${data.name}'s workspace`
: `My workspace`,
plan: parseWorkspaceDefaultPlan(data.email),
},
create: newWorkspaceData,
},
},
},
onboardingCategories: [],
},
include: {
workspaces: { select: { workspaceId: true } },
},
})
if (process.env.USER_CREATED_WEBHOOK_URL)
await got.post(process.env.USER_CREATED_WEBHOOK_URL, {
json: {
email: data.email,
name: data.name ? (data.name as string).split(' ')[0] : undefined,
},
const newWorkspaceId = createdUser.workspaces.pop()?.workspaceId
const events: TelemetryEvent[] = []
if (newWorkspaceId) {
events.push({
name: 'Workspace created',
workspaceId: newWorkspaceId,
userId: createdUser.id,
data: newWorkspaceData,
})
}
events.push({
name: 'User created',
userId: createdUser.id,
data: {
email: data.email,
name: data.name ? (data.name as string).split(' ')[0] : undefined,
},
})
await sendTelemetryEvents(events)
if (invitations.length > 0)
await convertInvitationsToCollaborations(p, user, invitations)
if (workspaceInvitations.length > 0)
Expand Down
20 changes: 18 additions & 2 deletions apps/builder/src/pages/api/publicTypebots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { NextApiRequest, NextApiResponse } from 'next'
import { canPublishFileInput } from '@/utils/api/dbRules'
import { badRequest, methodNotAllowed, notAuthenticated } from 'utils/api'
import { getAuthenticatedUser } from '@/features/auth/api'
import { sendTelemetryEvents } from 'utils/telemetry/sendTelemetryEvent'

const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const user = await getAuthenticatedUser(req)
Expand All @@ -23,10 +24,25 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
!(await canPublishFileInput({ userId: user.id, workspaceId, res }))
)
return
const typebot = await prisma.publicTypebot.create({
const publicTypebot = await prisma.publicTypebot.create({
data: { ...data },
include: {
typebot: { select: { name: true } },
},
})
return res.send(typebot)
await sendTelemetryEvents([
{
name: 'Typebot published',
userId: user.id,
workspaceId,
typebotId: publicTypebot.typebotId,
data: {
isFirstPublish: true,
name: publicTypebot.typebot.name,
},
},
])
return res.send(publicTypebot)
}
return methodNotAllowed(res)
} catch (err) {
Expand Down
19 changes: 17 additions & 2 deletions apps/builder/src/pages/api/publicTypebots/[id].ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { NextApiRequest, NextApiResponse } from 'next'
import { canPublishFileInput, canWriteTypebots } from '@/utils/api/dbRules'
import { getAuthenticatedUser } from '@/features/auth/api'
import { badRequest, methodNotAllowed, notAuthenticated } from 'utils/api'
import { sendTelemetryEvents } from 'utils/telemetry/sendTelemetryEvent'

const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const user = await getAuthenticatedUser(req)
Expand All @@ -25,11 +26,25 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
!(await canPublishFileInput({ userId: user.id, workspaceId, res }))
)
return
const typebots = await prisma.publicTypebot.update({
const publicTypebot = await prisma.publicTypebot.update({
where: { id },
data,
include: {
typebot: { select: { name: true } },
},
})
return res.send({ typebots })
await sendTelemetryEvents([
{
name: 'Typebot published',
userId: user.id,
workspaceId,
typebotId: publicTypebot.typebotId,
data: {
name: publicTypebot.typebot.name,
},
},
])
return res.send({ typebot: publicTypebot })
}
if (req.method === 'DELETE') {
const publishedTypebotId = req.query.id as string
Expand Down
1 change: 1 addition & 0 deletions apps/builder/src/pages/api/stripe/custom-plan-checkout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const createCheckoutSession = async (userId: string) => {
mode: 'subscription',
metadata: {
claimableCustomPlanId: claimableCustomPlan.id,
userId,
},
currency: claimableCustomPlan.currency,
automatic_tax: { enabled: true },
Expand Down
Loading

4 comments on commit 9ca17e4

@vercel
Copy link

@vercel vercel bot commented on 9ca17e4 Mar 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on 9ca17e4 Mar 14, 2023

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

ns8.vn
1stop.au
yobot.me
klujo.com
me.cr8.ai
chat.marius.digital
chatbot.matthesv.de
chatbot.repplai.com
demo.botscientis.us
demo.wemakebots.xyz
forms.webisharp.com
hrbot.robomotion.io
kbsub.wpwakanda.com
live.botscientis.us
mentoria.omelhor.vc
nutrisamirbayde.com
order.maitempah.com
quest.wpwakanda.com
support.wawplus.com
survey1.digienge.io
surveys.essiell.com
test.botscientis.us
test.reventepro.com
typebot.stillio.com
wordsandimagery.com
88584434.therpm.club
92109660.therpm.club
abbonamento.bwell.it
assistent.m-vogel.de
bium.gratirabbit.com
bot.ansuraniphone.my
bot.barrettamario.it
bot.cotemeuplano.com
bot.leadbooster.help
bot.mycompay.reviews
chat.hayurihijab.com
chatbee.agfunnel.com
click.sevenoways.com
connect.growthguy.in
forms.bonanza.design
hello.advergreen.com
kuiz.sistemniaga.com
offer.botscientis.us
sellmycarglasgow.com
talkbot.agfunnel.com
tenorioadvogados.com
uppity.wpwakanda.com
abutton.wpwakanda.com
acelera.maxbot.com.br
aidigitalmarketing.kr
bbutton.wpwakanda.com
bot.coachayongzul.com
bot.digitalpointer.id
bot.eikju.photography
bot.incusservices.com
tarian.theiofoundation.org
ted.meujalecobrasil.com.br
type.dericsoncalari.com.br
bot.pinpointinteractive.com
bot.polychromes-project.com
bot.seidinembroseanchetu.it
chat.semanalimpanome.com.br
chatbot.berbelanjabiz.trade
designguide.techyscouts.com
liveconvert2.kandalearn.com
presente.empresarias.com.mx
sell.sellthemotorhome.co.uk
anamnese.odontopavani.com.br
austin.channelautomation.com
bot.marketingplusmindset.com
bot.seidibergamoseanchetu.it
desabafe.sergiolimajr.com.br
download.venturemarketing.in
jc-app.virtuesocialmedia.com
piazzatorre.barrettamario.it
type.cookieacademyonline.com
upload.atlasoutfittersk9.com
bot.brigadeirosemdrama.com.br
forms.escoladeautomacao.com.br
onboarding.libertydreamcare.ie
type.talitasouzamarques.com.br
agendamento.sergiolimajr.com.br
anamnese.clinicamegasjdr.com.br
bookings.littlepartymonkeys.com
bot.comercializadoraomicron.com
elevateyourmind.groovepages.com
viewer-v2-typebot-io.vercel.app
yourfeedback.comebackreward.com
gerador.verificadordehospedes.com
personal-trainer.barrettamario.it
preagendamento.sergiolimajr.com.br
studiotecnicoimmobiliaremerelli.it
download.thailandmicespecialist.com
register.thailandmicespecialist.com
bot.studiotecnicoimmobiliaremerelli.it
pesquisa.escolamodacomproposito.com.br
anamnese.clinicaramosodontologia.com.br
chrome-os-inquiry-system.itschromeos.com
viewer-v2-git-main-typebot-io.vercel.app
main-menu-for-itschromeos.itschromeos.com

@vercel
Copy link

@vercel vercel bot commented on 9ca17e4 Mar 14, 2023

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-git-main-typebot-io.vercel.app
builder-v2-typebot-io.vercel.app
app.typebot.io

@vercel
Copy link

@vercel vercel bot commented on 9ca17e4 Mar 14, 2023

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:

docs – ./apps/docs

docs-git-main-typebot-io.vercel.app
docs-typebot-io.vercel.app
docs.typebot.io

Please sign in to comment.