Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Segment events #1669 #1672

Merged
merged 17 commits into from
Aug 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions apps/docs/editor/blocks/integrations/segment.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
title: Segment
---

With the Segment block, you can send events to Twilio's Segment and trigger your Segment workflows. It uses the Segment Analytics Node.js library to send server side events to Segment, so that it works on non web browser devices.

## How to find my `Write Key`?

To find your `Write Key`, you need to go to your Segment dashboard and click on the `Sources` tab. Then click on the `API Keys` button of the source you want to use.

## Identify User

This action allows you to identify a user in Segment. It requires the `User ID` and `Email` and can optionally take a set of `Traits`.

## Alias

This action allows you to alias a user in Segment. It requires the `Previous ID` and the `User ID`.

## Event

This action allows you to track an event in Segment. It requires the `Event Name` and `User ID`, and can optionally take a set of `Properties`.

## Page

This action allows you to send a page view event to Segment. It requires the Chatbot `Name` and can optionally take a `Category` name and also a set of `Properties`.
3 changes: 2 additions & 1 deletion apps/docs/mint.json
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@
"editor/blocks/integrations/elevenlabs",
"editor/blocks/integrations/anthropic",
"editor/blocks/integrations/dify-ai",
"editor/blocks/integrations/nocodb"
"editor/blocks/integrations/nocodb",
"editor/blocks/integrations/segment"
]
}
]
Expand Down
38 changes: 38 additions & 0 deletions packages/forge/blocks/segment/actions/alias.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { createAction, option } from '@typebot.io/forge'
import { Analytics } from '@segment/analytics-node'
import { auth } from '../auth'

export const alias = createAction({
auth,
name: 'Alias',
options: option.object({
userId: option.string.layout({
label: 'User ID',
isRequired: true,
moreInfoTooltip: 'New ID of the user.',
}),
previousId: option.string.layout({
label: 'Previous ID',
moreInfoTooltip: 'Previous ID of the user to alias.',
}),
}),
run: {
server: async ({
credentials: { apiKey },
options: { userId, previousId },
}) => {
if (!userId || userId.length === 0
|| !previousId || previousId.length === 0
|| apiKey === undefined) return

const analytics = new Analytics({ writeKey: apiKey })

analytics.alias({
userId: userId,
previousId: previousId
})

await analytics.closeAndFlush()
}
},
})
71 changes: 71 additions & 0 deletions packages/forge/blocks/segment/actions/identify.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { createAction, option } from '@typebot.io/forge'
import { Analytics } from '@segment/analytics-node'
import { auth } from '../auth'

export const identify = createAction({
auth,
name: 'Identify User',
options: option.object({
userId: option.string.layout({
label: 'User ID',
isRequired: true,
}),
email: option.string.layout({
label: 'Email',
isRequired: false,
}),
traits: option.array(option.object({
key: option.string.layout({
label: 'Key',
isRequired: true,
}),
value: option.string.layout({
label: 'Value',
isRequired: true,
}),
})).layout({
itemLabel: 'trait',
}),
}),
run: {
server: async ({
credentials: { apiKey },
options: { userId, email, traits },
}) => {
if (!email || email.length === 0
|| !userId || userId.length === 0
|| apiKey === undefined) return

const analytics = new Analytics({ writeKey: apiKey })

if (traits === undefined || traits.length === 0) {
analytics.identify({
userId: userId,
traits: {
email: email
}
})
} else {
analytics.identify({
userId: userId,
traits: createTraits(traits, email)
})
}
await analytics.closeAndFlush()
}
},
})

const createTraits = (traits: { key?: string; value?: string }[], email: string) => {
const _traits: Record<string, any> = {}

// add email as a default trait
traits.push({ key: 'email', value: email })

traits.forEach(({ key, value }) => {
if (!key || !value) return
_traits[key] = value
})

return _traits
}
70 changes: 70 additions & 0 deletions packages/forge/blocks/segment/actions/trackEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { createAction, option } from '@typebot.io/forge'
import { Analytics } from '@segment/analytics-node'
import { auth } from '../auth'

export const trackEvent = createAction({
auth,
name: 'Track',
options: option.object({
eventName: option.string.layout({
label: 'Name',
isRequired: true,
}),
userId: option.string.layout({
label: 'User ID',
isRequired: true,
}),
properties: option.array(option.object({
key: option.string.layout({
label: 'Key',
isRequired: true,
}),
value: option.string.layout({
label: 'Value',
isRequired: true,
}),
})).layout({
itemLabel: 'property',
}),
}),
run: {
server: async ({
credentials: { apiKey },
options: { eventName, userId, properties },
}) => {
if (!eventName || eventName.length === 0
|| !userId || userId.length === 0
|| apiKey === undefined) return

const analytics = new Analytics({ writeKey: apiKey })

if (properties === undefined || properties.length === 0) {
analytics.track({
userId: userId,
event: eventName
})
} else {
analytics.track({
userId: userId,
event: eventName,
properties: createProperties(properties)
})
}
await analytics.closeAndFlush()
}
},
})

const createProperties = (properties: { key?: string; value?: string }[]) => {
const props: Record<string, any> = {}

properties.forEach(({ key, value }) => {
if (!key || !value) return
props[key] = value
})

return props
}



76 changes: 76 additions & 0 deletions packages/forge/blocks/segment/actions/trackPage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { createAction, option } from '@typebot.io/forge'
import { Analytics } from '@segment/analytics-node'
import { auth } from '../auth'

export const trackPage = createAction({
auth,
name: 'Page',
options: option.object({
userId: option.string.layout({
label: 'User ID',
isRequired: true,
}),
name: option.string.layout({
label: 'Name',
isRequired: true,
}),
category: option.string.layout({
label: 'Category',
isRequired: false,
}),
properties: option.array(option.object({
key: option.string.layout({
label: 'Key',
isRequired: true,
}),
value: option.string.layout({
label: 'Value',
isRequired: true,
}),
})).layout({
itemLabel: 'property',
}),
}),
run: {
server: async ({
credentials: { apiKey },
options: { userId, name, category, properties },
}) => {
if (!name || name.length === 0
|| !userId || userId.length === 0
|| apiKey === undefined) return

const analytics = new Analytics({ writeKey: apiKey })

if (properties === undefined || properties.length === 0) {
analytics.page({
userId: userId,
name: name,
category: category != undefined ? category : ''
});
} else {
analytics.page({
userId: userId,
name: name,
category: category != undefined ? category : '',
properties: createProperties(properties)
});
}
await analytics.closeAndFlush()
}
},
})

const createProperties = (properties: { key?: string; value?: string }[]) => {
const props: Record<string, any> = {}

properties.forEach(({ key, value }) => {
if (!key || !value) return
props[key] = value
})

return props
}



16 changes: 16 additions & 0 deletions packages/forge/blocks/segment/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { option, AuthDefinition } from '@typebot.io/forge'

export const auth = {
type: 'encryptedCredentials',
name: 'Segment account',
schema: option.object({
apiKey: option.string.layout({
label: 'Write Key',
isRequired: true,
inputType: 'password',
helperText: 'You can find your Write Key in your Segment source settings.',
withVariableButton: false,
isDebounceDisabled: true,
})
}),
} satisfies AuthDefinition
16 changes: 16 additions & 0 deletions packages/forge/blocks/segment/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { createBlock } from '@typebot.io/forge'
import { SegmentLogo } from './logo'
import { auth } from './auth'
import { identify } from './actions/identify'
import { trackEvent } from './actions/trackEvent'
import { alias } from './actions/alias'
import { trackPage } from './actions/trackPage'

export const segmentBlock = createBlock({
id: 'segment',
name: 'Segment',
tags: ['events', 'analytics'],
LightLogo: SegmentLogo,
auth,
actions: [alias, identify, trackPage, trackEvent]
})
30 changes: 30 additions & 0 deletions packages/forge/blocks/segment/logo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/** @jsxImportSource react */

export const SegmentLogo = (props: React.SVGProps<SVGSVGElement>) => (
<svg viewBox="0 0 32 32" {...props}>
<path
d="M30.096 10.093H13.1186C12.7596 10.093 12.4686 10.384 12.4686 10.743V12.6145C12.4686 12.9735 12.7596 13.2645 13.1186 13.2645H30.096C30.455 13.2645 30.746 12.9735 30.746 12.6145V10.743C30.746 10.384 30.455 10.093 30.096 10.093Z"
fill="#52BD95"
/>
<path
d="M18.7872 18.0176H1.80979C1.4508 18.0176 1.15979 18.3087 1.15979 18.6676V20.5391C1.15979 20.8981 1.4508 21.1891 1.80979 21.1891H18.7872C19.1462 21.1891 19.4372 20.8981 19.4372 20.5391V18.6676C19.4372 18.3087 19.1462 18.0176 18.7872 18.0176Z"
fill="#52BD95"
/>
<path
d="M3.35259 12.2631C3.66453 12.3437 3.9837 12.1602 4.07113 11.8502C6.03309 5.7089 12.3512 2.07413 18.6464 3.46509C18.9516 3.52799 19.2527 3.34116 19.3319 3.03975L19.8481 1.11331C19.8889 0.957036 19.8641 0.790884 19.7795 0.653294C19.695 0.515704 19.5579 0.418597 19.4 0.384442C11.4398 -1.40735 3.43933 3.22032 1.02352 11.0139C0.97921 11.1662 0.998011 11.3299 1.07568 11.4681C1.15336 11.6064 1.28338 11.7076 1.43647 11.749L3.35259 12.2631Z"
fill="#52BD95"
/>
<path
d="M28.5573 19.0108C28.2428 18.9262 27.9188 19.1103 27.8305 19.4238C25.8727 25.5691 19.5534 29.2091 13.2552 27.8191C12.9498 27.7551 12.648 27.9424 12.5697 28.2445L12.0639 30.1606C12.0224 30.3172 12.0469 30.4839 12.1315 30.6219C12.2161 30.76 12.3536 30.8574 12.5119 30.8915C20.4721 32.6789 28.4701 28.0517 30.8884 20.26C30.9333 20.108 30.9147 19.9443 30.8369 19.8062C30.7591 19.6681 30.6287 19.5674 30.4755 19.527L28.5573 19.0108Z"
fill="#52BD95"
/>
<path
d="M24.9894 6.61181C25.9495 6.61181 26.7279 5.83344 26.7279 4.87327C26.7279 3.9131 25.9495 3.13473 24.9894 3.13473C24.0292 3.13473 23.2508 3.9131 23.2508 4.87327C23.2508 5.83344 24.0292 6.61181 24.9894 6.61181Z"
fill="#52BD95"
/>
<path
d="M6.92052 28.1454C7.88069 28.1454 8.65906 27.367 8.65906 26.4068C8.65906 25.4467 7.88069 24.6683 6.92052 24.6683C5.96035 24.6683 5.18198 25.4467 5.18198 26.4068C5.18198 27.367 5.96035 28.1454 6.92052 28.1454Z"
fill="#52BD95"
/>
</svg>
)
18 changes: 18 additions & 0 deletions packages/forge/blocks/segment/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "@typebot.io/segment-block",
"version": "1.0.0",
"description": "",
"main": "index.ts",
"keywords": [],
"license": "AGPL-3.0-or-later",
"devDependencies": {
"@typebot.io/forge": "workspace:*",
"@typebot.io/lib": "workspace:*",
"@typebot.io/tsconfig": "workspace:*",
"@types/react": "18.2.15",
"typescript": "5.4.5"
},
"dependencies": {
"@segment/analytics-node": "^2.1.2"
}
}
10 changes: 10 additions & 0 deletions packages/forge/blocks/segment/schemas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Do not edit this file manually
import { parseBlockCredentials, parseBlockSchema } from '@typebot.io/forge'
import { segmentBlock } from '.'
import { auth } from './auth'

export const segmentBlockSchema = parseBlockSchema(segmentBlock)
export const segmentCredentialsSchema = parseBlockCredentials(
segmentBlock.id,
auth.schema
)
Loading