Skip to content

Commit

Permalink
feat: add mixpanel analytics
Browse files Browse the repository at this point in the history
  • Loading branch information
fbelginetw committed Oct 18, 2024
1 parent 64d1996 commit 5b2022d
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 2 deletions.
14 changes: 13 additions & 1 deletion opentrons-ai-client/src/OpentronsAI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,20 @@ import { useAtom } from 'jotai'
import { useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { Loading } from './molecules/Loading'
import { tokenAtom } from './resources/atoms'
import { mixpanelAtom, tokenAtom } from './resources/atoms'
import { useGetAccessToken } from './resources/hooks'
import { initializeMixpanel } from './analytics/mixpanel'
import { useTrackEvent } from './resources/hooks/useTrackEvent'

export function OpentronsAI(): JSX.Element | null {
const { t } = useTranslation('protocol_generator')
const { isAuthenticated, logout, isLoading, loginWithRedirect } = useAuth0()
const [, setToken] = useAtom(tokenAtom)
const [mixpanel] = useAtom(mixpanelAtom)
const { getAccessToken } = useGetAccessToken()
const trackEvent = useTrackEvent()

initializeMixpanel(mixpanel)

const fetchAccessToken = async (): Promise<void> => {
try {
Expand All @@ -40,6 +46,12 @@ export function OpentronsAI(): JSX.Element | null {
}
}, [isAuthenticated, isLoading, loginWithRedirect])

useEffect(() => {
if (isAuthenticated) {
trackEvent({ name: 'user-login', properties: {} })
}
}, [isAuthenticated])

if (isLoading) {
return <Loading />
}
Expand Down
69 changes: 69 additions & 0 deletions opentrons-ai-client/src/analytics/mixpanel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import mixpanel from 'mixpanel-browser'
import { getHasOptedIn } from './selectors'

export const getIsProduction = (): boolean =>
global.location.host === 'designer.opentrons.com' // UPDATE THIS TO CORRECT URL

export type AnalyticsEvent =
| {
name: string
properties: Record<string, unknown>
superProperties?: Record<string, unknown>
}
| { superProperties: Record<string, unknown> }

// pulled in from environment at build time
const MIXPANEL_ID = getIsProduction()
? process.env.OT_AI_CLIENT_MIXPANEL_ID
: 'process.env.OT_AI_CLIENT_MIXPANEL_DEV_ID'

const MIXPANEL_OPTS = {
// opt out by default
opt_out_tracking_by_default: true,
}

export function initializeMixpanel(state: any): void {
const optedIn = getHasOptedIn(state) ?? false
if (MIXPANEL_ID != null) {
console.debug('Initializing Mixpanel', { optedIn })

mixpanel.init(MIXPANEL_ID, MIXPANEL_OPTS)
setMixpanelTracking(optedIn)
trackEvent({ name: 'appOpen', properties: {} }, optedIn) // TODO IMMEDIATELY: do we want this?
} else {
console.warn('MIXPANEL_ID not found; this is a bug if build is production')
}
}

export function trackEvent(event: AnalyticsEvent, optedIn: boolean): void {
console.debug('Trackable event', { event, optedIn })
if (MIXPANEL_ID != null && optedIn) {
if ('superProperties' in event && event.superProperties != null) {
mixpanel.register(event.superProperties)
}
if ('name' in event && event.name != null) {
mixpanel.track(event.name, event.properties)
}
}
}

export function setMixpanelTracking(optedIn: boolean): void {
if (MIXPANEL_ID != null) {
if (optedIn) {
console.debug('User has opted into analytics; tracking with Mixpanel')
mixpanel.opt_in_tracking()
// Register "super properties" which are included with all events
mixpanel.register({
appVersion: 'test', // TODO update this?
// NOTE(IL, 2020): Since PD may be in the same Mixpanel project as other OT web apps, this 'appName' property is intended to distinguish it
appName: 'opentronsAIClient',
})
} else {
console.debug(
'User has opted out of analytics; stopping Mixpanel tracking'
)
mixpanel.opt_out_tracking()
mixpanel.reset()
}
}
}
2 changes: 2 additions & 0 deletions opentrons-ai-client/src/analytics/selectors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const getHasOptedIn = (state: any): boolean | null =>
state.analytics.hasOptedIn
6 changes: 5 additions & 1 deletion opentrons-ai-client/src/resources/atoms.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
// jotai's atoms
import { atom } from 'jotai'
import type { Chat, ChatData } from './types'
import type { Chat, ChatData, Mixpanel } from './types'

/** ChatDataAtom is for chat data (user prompt and response from OpenAI API) */
export const chatDataAtom = atom<ChatData[]>([])

export const chatHistoryAtom = atom<Chat[]>([])

export const tokenAtom = atom<string | null>(null)

export const mixpanelAtom = atom<Mixpanel | null>({
analytics: { hasOptedIn: true }, // TODO: set to false
})
16 changes: 16 additions & 0 deletions opentrons-ai-client/src/resources/hooks/useTrackEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useAtom } from 'jotai'
import { trackEvent } from '../../analytics/mixpanel'
import { mixpanelAtom } from '../atoms'
import type { AnalyticsEvent } from '../types'

/**
* React hook to send an analytics tracking event directly from a component
*
* @returns {AnalyticsEvent => void} track event function
*/
export function useTrackEvent(): (e: AnalyticsEvent) => void {
const [mixpanel] = useAtom(mixpanelAtom)
return event => {
trackEvent(event, mixpanel?.analytics.hasOptedIn ?? false)
}
}
12 changes: 12 additions & 0 deletions opentrons-ai-client/src/resources/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,15 @@ export interface RouteProps {
path: string
navLinkTo: string
}

export interface Mixpanel {
analytics: {
hasOptedIn: boolean
}
}

export interface AnalyticsEvent {
name: string
properties: Record<string, unknown>
superProperties?: Record<string, unknown>
}

0 comments on commit 5b2022d

Please sign in to comment.