2
0

♻️ Re-organize telemetry package

This commit is contained in:
Baptiste Arnaud
2024-02-01 14:19:24 +01:00
parent 3fcb0081e5
commit 92a1d672fd
26 changed files with 102 additions and 206 deletions

View File

@ -78,8 +78,6 @@
"nprogress": "0.2.0",
"openai": "4.24.1",
"papaparse": "5.4.1",
"posthog-js": "1.77.1",
"posthog-node": "3.1.1",
"prettier": "2.8.8",
"qs": "6.11.2",
"react": "18.2.0",
@ -95,7 +93,8 @@
"tinycolor2": "1.6.0",
"unsplash-js": "7.0.18",
"use-debounce": "9.0.4",
"zustand": "4.5.0"
"zustand": "4.5.0",
"ky": "1.1.3"
},
"devDependencies": {
"@chakra-ui/styled-system": "2.9.1",

View File

@ -8,7 +8,6 @@ import { useToast } from '@/hooks/useToast'
import { updateUserQuery } from './queries/updateUserQuery'
import { useDebouncedCallback } from 'use-debounce'
import { env } from '@typebot.io/env'
import { identifyUser } from '../telemetry/posthog'
import { useColorMode } from '@chakra-ui/react'
export const userContext = createContext<{
@ -62,7 +61,6 @@ export const UserProvider = ({ children }: { children: ReactNode }) => {
if (parsedUser?.id) {
setSentryUser({ id: parsedUser.id })
identifyUser(parsedUser.id)
}
}, [session, user])

View File

@ -8,13 +8,13 @@ import {
import type { Adapter, AdapterUser } from 'next-auth/adapters'
import { createId } from '@paralleldrive/cuid2'
import { generateId } from '@typebot.io/lib'
import { sendTelemetryEvents } from '@typebot.io/lib/telemetry/sendTelemetryEvent'
import { TelemetryEvent } from '@typebot.io/schemas/features/telemetry'
import { convertInvitationsToCollaborations } from '@/features/auth/helpers/convertInvitationsToCollaborations'
import { getNewUserInvitations } from '@/features/auth/helpers/getNewUserInvitations'
import { joinWorkspaces } from '@/features/auth/helpers/joinWorkspaces'
import { parseWorkspaceDefaultPlan } from '@/features/workspace/helpers/parseWorkspaceDefaultPlan'
import { env } from '@typebot.io/env'
import { trackEvents } from '@typebot.io/lib/telemetry/trackEvents'
export function customAdapter(p: PrismaClient): Adapter {
return {
@ -28,7 +28,7 @@ export function customAdapter(p: PrismaClient): Adapter {
)
if (
env.DISABLE_SIGNUP &&
env.ADMIN_EMAIL !== user.email &&
env.ADMIN_EMAIL?.every((email) => email !== user.email) &&
invitations.length === 0 &&
workspaceInvitations.length === 0
)
@ -80,7 +80,7 @@ export function customAdapter(p: PrismaClient): Adapter {
name: data.name ? (data.name as string).split(' ')[0] : undefined,
},
})
await sendTelemetryEvents(events)
await trackEvents(events)
if (invitations.length > 0)
await convertInvitationsToCollaborations(p, user, invitations)
if (workspaceInvitations.length > 0)

View File

@ -1,4 +1,3 @@
import { sendTelemetryEvents } from '@typebot.io/lib/telemetry/sendTelemetryEvent'
import prisma from '@typebot.io/lib/prisma'
import { authenticatedProcedure } from '@/helpers/server/trpc'
import { TRPCError } from '@trpc/server'
@ -9,6 +8,7 @@ import { z } from 'zod'
import { createCheckoutSessionUrl } from './createCheckoutSession'
import { isAdminWriteWorkspaceForbidden } from '@/features/workspace/helpers/isAdminWriteWorkspaceForbidden'
import { env } from '@typebot.io/env'
import { trackEvents } from '@typebot.io/lib/telemetry/trackEvents'
export const updateSubscription = authenticatedProcedure
.meta({
@ -157,7 +157,7 @@ export const updateSubscription = authenticatedProcedure
},
})
await sendTelemetryEvents([
await trackEvents([
{
name: 'Subscription updated',
workspaceId,

View File

@ -1,91 +0,0 @@
import { eventSchema } from '@typebot.io/schemas/features/telemetry'
import { z } from 'zod'
import { PostHog } from 'posthog-node'
import { TRPCError } from '@trpc/server'
import got from 'got'
import { authenticatedProcedure } from '@/helpers/server/trpc'
import { env } from '@typebot.io/env'
export const processTelemetryEvent = authenticatedProcedure
.meta({
openapi: {
method: 'POST',
path: '/v1/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.",
tags: ['Telemetry'],
},
})
.input(
z.object({
events: z.array(eventSchema),
})
)
.output(
z.object({
message: z.literal('Events injected'),
})
)
.mutation(async ({ input: { events }, ctx: { user } }) => {
if (user.email !== env.ADMIN_EMAIL)
throw new TRPCError({
code: 'BAD_REQUEST',
message: 'Only app admin can process telemetry events',
})
if (!env.NEXT_PUBLIC_POSTHOG_KEY)
throw new TRPCError({
code: 'BAD_REQUEST',
message: 'Server does not have POSTHOG_API_KEY configured',
})
const client = new PostHog(env.NEXT_PUBLIC_POSTHOG_KEY, {
host: env.NEXT_PUBLIC_POSTHOG_HOST,
})
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' && env.USER_CREATED_WEBHOOK_URL) {
await got.post(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: 'data' in event ? event.data : undefined,
groups,
})
})
await client.shutdownAsync()
return { message: 'Events injected' }
})

View File

@ -1,27 +0,0 @@
import { env } from '@typebot.io/env'
import posthog from 'posthog-js'
export const initPostHogIfEnabled = () => {
if (typeof window === 'undefined') return
const posthogKey = env.NEXT_PUBLIC_POSTHOG_KEY
if (!posthogKey) return
posthog.init(posthogKey, {
api_host: env.NEXT_PUBLIC_POSTHOG_HOST,
loaded: (posthog) => {
if (process.env.NODE_ENV === 'development') posthog.debug()
},
capture_pageview: false,
capture_pageleave: false,
autocapture: false,
})
}
export const identifyUser = (userId: string) => {
if (!posthog.__loaded) return
posthog.identify(userId)
}
export { posthog }

View File

@ -11,9 +11,9 @@ import {
sanitizeGroups,
sanitizeSettings,
} from '../helpers/sanitizers'
import { sendTelemetryEvents } from '@typebot.io/lib/telemetry/sendTelemetryEvent'
import { createId } from '@paralleldrive/cuid2'
import { EventType } from '@typebot.io/schemas/features/events/constants'
import { trackEvents } from '@typebot.io/lib/telemetry/trackEvents'
const typebotCreateSchemaPick = {
name: true,
@ -116,7 +116,7 @@ export const createTypebot = authenticatedProcedure
const parsedNewTypebot = typebotV6Schema.parse(newTypebot)
await sendTelemetryEvents([
await trackEvents([
{
name: 'Typebot created',
workspaceId: parsedNewTypebot.workspaceId,

View File

@ -104,6 +104,6 @@ const getCurrentUserMode = (
return 'write'
if (collaborator) return 'read'
if (user?.email === env.ADMIN_EMAIL) return 'read'
if (user?.email && env.ADMIN_EMAIL?.includes(user.email)) return 'read'
return 'guest'
}

View File

@ -12,9 +12,9 @@ import {
import { z } from 'zod'
import { getUserRoleInWorkspace } from '@/features/workspace/helpers/getUserRoleInWorkspace'
import { sanitizeGroups, sanitizeSettings } from '../helpers/sanitizers'
import { sendTelemetryEvents } from '@typebot.io/lib/telemetry/sendTelemetryEvent'
import { preprocessTypebot } from '@typebot.io/schemas/features/typebot/helpers/preprocessTypebot'
import { migrateTypebot } from '@typebot.io/lib/migrations/migrateTypebot'
import { trackEvents } from '@typebot.io/lib/telemetry/trackEvents'
const omittedProps = {
id: true,
@ -151,7 +151,7 @@ export const importTypebot = authenticatedProcedure
const parsedNewTypebot = typebotV6Schema.parse(newTypebot)
await sendTelemetryEvents([
await trackEvents([
{
name: 'Typebot created',
workspaceId: parsedNewTypebot.workspaceId,

View File

@ -11,11 +11,11 @@ import {
} from '@typebot.io/schemas'
import { z } from 'zod'
import { isWriteTypebotForbidden } from '../helpers/isWriteTypebotForbidden'
import { sendTelemetryEvents } from '@typebot.io/lib/telemetry/sendTelemetryEvent'
import { Plan } from '@typebot.io/prisma'
import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/constants'
import { computeRiskLevel } from '@typebot.io/radar'
import { env } from '@typebot.io/env'
import { trackEvents } from '@typebot.io/lib/telemetry/trackEvents'
export const publishTypebot = authenticatedProcedure
.meta({
@ -174,7 +174,7 @@ export const publishTypebot = authenticatedProcedure
},
})
await sendTelemetryEvents([
await trackEvents([
{
name: 'Typebot published',
workspaceId: existingTypebot.workspaceId,

View File

@ -28,7 +28,7 @@ export const isReadTypebotForbidden = async (
!user ||
typebot.workspace.isSuspended ||
typebot.workspace.isPastDue ||
(env.ADMIN_EMAIL !== user.email &&
(env.ADMIN_EMAIL?.every((email) => email !== user.email) &&
!typebot.collaborators.some(
(collaborator) => collaborator.userId === user.id
) &&

View File

@ -1,10 +1,10 @@
import { sendTelemetryEvents } from '@typebot.io/lib/telemetry/sendTelemetryEvent'
import prisma from '@typebot.io/lib/prisma'
import { authenticatedProcedure } from '@/helpers/server/trpc'
import { TRPCError } from '@trpc/server'
import { Workspace, workspaceSchema } from '@typebot.io/schemas'
import { z } from 'zod'
import { parseWorkspaceDefaultPlan } from '../helpers/parseWorkspaceDefaultPlan'
import { trackEvents } from '@typebot.io/lib/telemetry/trackEvents'
export const createWorkspace = authenticatedProcedure
.meta({
@ -62,7 +62,7 @@ export const createWorkspace = authenticatedProcedure
},
})) as Workspace
await sendTelemetryEvents([
await trackEvents([
{
name: 'Workspace created',
workspaceId: newWorkspace.id,

View File

@ -8,7 +8,7 @@ export const isReadWorkspaceFobidden = (
user: Pick<User, 'email' | 'id'>
) => {
if (
env.ADMIN_EMAIL === user.email ||
env.ADMIN_EMAIL?.some((email) => email === user.email) ||
workspace.members.find((member) => member.userId === user.id)
)
return false

View File

@ -2,7 +2,8 @@ import { env } from '@typebot.io/env'
import { Plan } from '@typebot.io/prisma'
export const parseWorkspaceDefaultPlan = (userEmail: string) => {
if (env.ADMIN_EMAIL === userEmail) return Plan.UNLIMITED
if (env.ADMIN_EMAIL?.some((email) => email === userEmail))
return Plan.UNLIMITED
const defaultPlan = env.DEFAULT_WORKSPACE_PLAN as Plan
if (defaultPlan && Object.values(Plan).includes(defaultPlan))
return defaultPlan

View File

@ -40,7 +40,8 @@ export const canReadTypebots = (
) => ({
id: typeof typebotIds === 'string' ? typebotIds : { in: typebotIds },
workspace:
user.email === env.ADMIN_EMAIL || env.NEXT_PUBLIC_E2E_TEST
env.ADMIN_EMAIL?.some((email) => email === user.email) ||
env.NEXT_PUBLIC_E2E_TEST
? undefined
: {
members: {

View File

@ -10,7 +10,6 @@ import { router } from '../trpc'
import { analyticsRouter } from '@/features/analytics/api/router'
import { collaboratorsRouter } from '@/features/collaboration/api/router'
import { customDomainsRouter } from '@/features/customDomains/api/router'
import { processTelemetryEvent } from '@/features/telemetry/api/processTelemetryEvent'
import { publicWhatsAppRouter } from '@/features/whatsapp/router'
export const publicRouter = router({
@ -25,7 +24,6 @@ export const publicRouter = router({
theme: themeRouter,
collaborators: collaboratorsRouter,
customDomains: customDomainsRouter,
processTelemetryEvent,
whatsApp: publicWhatsAppRouter,
})

View File

@ -19,13 +19,10 @@ import { NewVersionPopup } from '@/components/NewVersionPopup'
import { TypebotProvider } from '@/features/editor/providers/TypebotProvider'
import { WorkspaceProvider } from '@/features/workspace/WorkspaceProvider'
import { isCloudProdInstance } from '@/helpers/isCloudProdInstance'
import { initPostHogIfEnabled } from '@/features/telemetry/posthog'
import { TolgeeProvider, useTolgeeSSR } from '@tolgee/react'
import { tolgee } from '@/lib/tolgee'
import { Toaster } from '@/components/Toaster'
initPostHogIfEnabled()
const { ToastContainer, toast } = createStandaloneToast(customTheme)
const App = ({ Component, pageProps }: AppProps) => {

View File

@ -6,10 +6,10 @@ import { buffer } from 'micro'
import prisma from '@typebot.io/lib/prisma'
import { Plan, WorkspaceRole } from '@typebot.io/prisma'
import { RequestHandler } from 'next/dist/server/next'
import { sendTelemetryEvents } from '@typebot.io/lib/telemetry/sendTelemetryEvent'
import { Settings } from '@typebot.io/schemas'
import { env } from '@typebot.io/env'
import { prices } from '@typebot.io/lib/billing/constants'
import { trackEvents } from '@typebot.io/lib/telemetry/trackEvents'
if (!env.STRIPE_SECRET_KEY || !env.STRIPE_WEBHOOK_SECRET)
throw new Error('STRIPE_SECRET_KEY or STRIPE_WEBHOOK_SECRET missing')
@ -75,7 +75,7 @@ const webhookHandler = async (req: NextApiRequest, res: NextApiResponse) => {
},
})
await sendTelemetryEvents(
await trackEvents(
workspace.members.map((m) => ({
name: 'Subscription updated',
workspaceId,
@ -108,7 +108,7 @@ const webhookHandler = async (req: NextApiRequest, res: NextApiResponse) => {
},
})
await sendTelemetryEvents([
await trackEvents([
{
name: 'Subscription updated',
workspaceId,
@ -152,7 +152,7 @@ const webhookHandler = async (req: NextApiRequest, res: NextApiResponse) => {
isPastDue: true,
},
})
await sendTelemetryEvents(
await trackEvents(
existingWorkspace.members.map((m) => ({
name: 'Workspace past due',
workspaceId: existingWorkspace.id,
@ -202,7 +202,7 @@ const webhookHandler = async (req: NextApiRequest, res: NextApiResponse) => {
},
},
})
await sendTelemetryEvents(
await trackEvents(
updatedWorkspace.members.map((m) => ({
name: 'Workspace past due status removed',
workspaceId: updatedWorkspace.id,
@ -255,7 +255,7 @@ const webhookHandler = async (req: NextApiRequest, res: NextApiResponse) => {
},
})
await sendTelemetryEvents(
await trackEvents(
workspace.members.map((m) => ({
name: 'Subscription updated',
workspaceId: workspace.id,

View File

@ -21,7 +21,7 @@ Parameters marked with <span style={{color: '#ef5151'}}>\*</span> are required.
| ENCRYPTION_SECRET <span style={{color: '#ef5151'}}>\*</span> | | A 256-bit key used to encrypt sensitive data. It is strongly recommended to [generate](https://www.allkeysgenerator.com/Random/Security-Encryption-Key-Generator.aspx) a new one. The secret should be the same between builder and viewer. |
| NEXTAUTH_URL <span style={{color: '#ef5151'}}>\*</span> | | The builder base URL. Should be the publicly accessible URL (i.e. `https://typebot.domain.com`) |
| NEXT_PUBLIC_VIEWER_URL <span style={{color: '#ef5151'}}>\*</span> | | The viewer base URL. Should be the publicly accessible URL (i.e. `https://bot.domain.com`) |
| ADMIN_EMAIL | | The email that will get an `UNLIMITED` plan on user creation. The associated user will be able to bypass database rules. |
| ADMIN_EMAIL | | The email that will get an `UNLIMITED` plan on user creation. The associated user will be able to bypass database rules. You can provide multiple emails separated by a comma without spaces. |
| NEXTAUTH_URL_INTERNAL | | The internal builder base URL. You have to set it only when `NEXTAUTH_URL` can't be reached by your builder container / server. For a docker deployment, you should set it to `http://localhost:3000`. |
| DEFAULT_WORKSPACE_PLAN | FREE | Default workspace plan on user creation or when a user creates a new workspace. Possible values are `FREE`, `STARTER`, `PRO`, `LIFETIME`, `UNLIMITED`. The default plan for admin user is `UNLIMITED` |
| DISABLE_SIGNUP | false | Disable new user sign ups. Invited users are still able to sign up. |