Skip to content

Commit

Permalink
feat(fe2): proper utm collection + idempotent Route Visited tracking (#…
Browse files Browse the repository at this point in the history
…2497)

* fix(fe2): utm not being tracked like it was in fe1

* fix(fe2): idempotent mp Route Visited calls
  • Loading branch information
fabis94 authored Jul 11, 2024
1 parent b0c8a0e commit 4da196e
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 10 deletions.
41 changes: 40 additions & 1 deletion packages/frontend-2/lib/core/clients/mp.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable camelcase */
import { type Nullable, resolveMixpanelServerId } from '@speckle/shared'
import mixpanel from 'mixpanel-browser'
import { isString, mapKeys } from 'lodash-es'
import { useOnAuthStateChange } from '~/lib/auth/composables/auth'
import {
HOST_APP,
Expand All @@ -16,6 +16,29 @@ function getMixpanelServerId(): string {
return resolveMixpanelServerId(window.location.hostname)
}

function useMixpanelUtmCollection() {
const route = useRoute()
return () => {
const campaignKeywords = [
'utm_source',
'utm_medium',
'utm_campaign',
'utm_content',
'utm_term'
]

const result: Record<string, string> = {}
for (const campaignKeyword of campaignKeywords) {
const value = route.query[campaignKeyword]
if (value && isString(value)) {
result[campaignKeyword] = value
}
}

return result
}
}

/**
* Composable that builds the user (re-)identification function. Needs to be invoked on app
* init and when the active user changes (e.g. after signing out/in)
Expand Down Expand Up @@ -59,8 +82,12 @@ export const useClientsideMixpanelClientBuilder = () => {
} = useRuntimeConfig()
const { reidentify } = useMixpanelUserIdentification()
const onAuthStateChange = useOnAuthStateChange()
const logger = useLogger()
const collectUtmTags = useMixpanelUtmCollection()

return async (): Promise<Nullable<MixpanelClient>> => {
// Dynamic import to be able to suppress loading errors that happen because of adblock
const mixpanel = (await import('mixpanel-browser')).default
if (!mixpanel || !mixpanelTokenId.length || !mixpanelApiHost.length) {
return null
}
Expand All @@ -70,12 +97,24 @@ export const useClientsideMixpanelClientBuilder = () => {
api_host: mixpanelApiHost,
debug: !!import.meta.dev && logCsrEmitProps
})
const utmParams = collectUtmTags()

// Reidentify on auth change
await onAuthStateChange(() => reidentify(mixpanel), { immediate: true })

// Track UTM (only on initial visit)
if (Object.values(utmParams).length) {
const firstTouch = mapKeys(utmParams, (_val, key) => `${key} [first touch]`)
const lastTouch = mapKeys(utmParams, (_val, key) => `${key} [last touch]`)

mixpanel.people.set(lastTouch)
mixpanel.people.set_once(firstTouch)
mixpanel.register(lastTouch)
}

// Track app visit
mixpanel.track(`Visit ${HOST_APP_DISPLAY_NAME}`)
logger.info('MP client initialized')

return mixpanel
}
Expand Down
10 changes: 2 additions & 8 deletions packages/frontend-2/plugins/007-mpClient.client.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { LogicError } from '@speckle/ui-components'
import { provideApolloClient } from '@vue/apollo-composable'
import { useApolloClientFromNuxt } from '~/lib/common/composables/graphql'
import { fakeMixpanelClient, type MixpanelClient } from '~/lib/common/helpers/mp'
import { useClientsideMixpanelClientBuilder } from '~/lib/core/clients/mp'

/**
* mixpanel-browser only supports being ran on the client-side (hence the name)! So it's only going to be accessible
Expand All @@ -10,17 +9,12 @@ import { fakeMixpanelClient, type MixpanelClient } from '~/lib/common/helpers/mp

export default defineNuxtPlugin(async () => {
const logger = useLogger()
const apollo = useApolloClientFromNuxt()
const build = useClientsideMixpanelClientBuilder()

let mixpanel: MixpanelClient | undefined = undefined

try {
// Dynamic import to allow suppressing loading errors that happen because of adblock
const builder = (await import('~/lib/core/clients/mp'))
.useClientsideMixpanelClientBuilder

// Not sure why, but apollo client is inaccessible so we have to explicitly provide it
const build = provideApolloClient(apollo)(() => builder())
mixpanel = (await build()) || undefined
} catch (e) {
logger.warn(e, 'Failed to load mixpanel in CSR')
Expand Down
7 changes: 6 additions & 1 deletion packages/frontend-2/plugins/008-mp.client.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import { useMixpanel } from '~/lib/core/composables/mp'
import type { RouteLocationNormalized } from 'vue-router'
import type { Optional } from '@speckle/shared'

export default defineNuxtPlugin(() => {
const mp = useMixpanel()
const router = useRouter()
const route = useRoute()

let previousPath: Optional<string> = undefined
const track = (to: RouteLocationNormalized) => {
const pathDefinition = getRouteDefinition(to)
const path = to.path
if (path === previousPath) return

const pathDefinition = getRouteDefinition(to)
mp.track('Route Visited', {
path,
pathDefinition
})
previousPath = path
}

// Track init page view
Expand Down

0 comments on commit 4da196e

Please sign in to comment.