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

Context overrides + page view tracking + docs #29

Merged
merged 5 commits into from
Mar 17, 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
15 changes: 15 additions & 0 deletions doc/gtag_setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,18 @@ const App = () => {
);
};
```


## Usage

```js
import {
EventTypes,
pushGaEvent,
} from '@freshheads/analytics-essentials';

pushGaEvent({
type: EventTypes.CLICK,
name: 'hero_button_click'
});
```
149 changes: 149 additions & 0 deletions doc/mixpanel_setup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# Mixpanel setup

## Introduction
This implementation of mixpanel relies on a backend service to send the data to mixpanel. The goal of the frontend lib is to provide a typechecked event to help developers push events the same way.
For the backend Freshheads created a php bundle, [FHMixpanelBundle](https://github.com/freshheads/FHMixpanelBundle) and a node library will also be created in the future.

## Usage

Add the MixpanelProvider to your app:

```tsx
import { MixpanelProvider } from '@freshheads/analytics-essentials';

const App = () => {
return (
<MixpanelProvider eventApiClient={sendTrackEvent}>
<YourApp />
</MixpanelProvider>
);
};
```

`sendTrackEvent` is a function that sends the event to the backend. It should have the following signature:

```tsx

import { MixpanelEvent } from '@freshheads/analytics-essentials';
import { executePostRequest } from '@/api/client';

export const sendTrackEvent = async (data: MixpanelEvent) => {
return executePostRequest('_mixpanel/track', data);
};
```

Then you can use the `useMixpanelContext` hook to send events:

```tsx
import { useMixpanelContext } from '@freshheads/analytics-essentials';

const { trackEvent } = useMixpanelContext();

trackEvent({
name: 'Add to cart',
data: {
product_name: product.title,
},
});
```

### Context and overrides
By default, each event will have a context property that contains more information about the page it was sent from.
This is resolved from window.location because we have no access to the router state.

```
{
title: 'Product Page', // What page is the event triggered on
pathname: '/product/123', // Make sure there aren't any personal info in the path
href: 'https://www.example.com/product/123', // Make sure there aren't any personal info in the href
}
```

You can override this context by providing a `context` property to the event.

```tsx
trackEvent({
name: 'Add to cart',
context: {
section: 'footer',
},
});
```

Or by providing a default context to the MixpanelProvider:

```tsx
const router = useRouter();

const defaultMixpanelEventContext = {
pathname: router.pathname,
route: router.route,
audience: 'Freelancer',
};

const App = () => {
return (
<MixpanelProvider eventApiClient={sendTrackEvent} defaultEventContext={defaultMixpanelEventContext}>
<YourApp />
</MixpanelProvider>
);
};
```

### Page view events

For page view events, you can use the `trackPageView` function.
First create a component that will be used to track page views.
The reason we don't make this part of the essential package is because we don't want to make any assumptions about the router you are using.
Or you might want to send additional data with the page view event.

```tsx
export default function TrackPageView() {
const { trackPageView } = useMixpanelContext();
const router = useRouter();
const pathname = usePathname();

useEffect(() => {
trackPageView({
data: {
title: document.title,
pathname: pathname,
route: router.route,
},
});
}, [pathname]);

return null;
}
```

Then add this component to your app:

```tsx

const App = () => {
return (
<MixpanelProvider eventApiClient={sendTrackEvent} defaultEventContext={defaultMixpanelEventContext}>
<TrackPageView />
{children}
</MixpanelProvider>
);
};
```

### UTM tracking

UTM tags are automatically added to the context of the event if they are present in the URL.
They will be remembered for the duration of the session. Even if the user navigates to a different page, the UTM tags will be added to new events.

## Mixpanel users

TODO how to handle reset mixpanel user

## Event naming conventions

TODO - how to name events

## Event types

TODO - how to override the event types
28 changes: 28 additions & 0 deletions doc/tagmanager_setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,31 @@ const App = () => {
);
};
```

## Usage

```js
import {
EventTypes,
pushDataLayerEvent,
} from '@freshheads/analytics-essentials';

pushDataLayerEvent({
type: EventTypes.CLICK,
name: 'hero_button_click',
});
```

## Optional params

```js
pushDataLayerEvent({
type: EventTypes.CLICK,
name: 'hero_button_click',
context: {
// context is typed based on type
// you can always extend context with your custom properties
}
}),

```
40 changes: 32 additions & 8 deletions lib/mixpanel/context.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
'use client';

import React, { createContext, useContext, useEffect } from 'react';
import { MixpanelEvent as DTO } from './types';
import { MixpanelEvent, MixpanelPageViewEvent } from './types';
import {
extractUtmParams,
isStandalonePWA,
writeUtmParamsToSessionStorage,
} from './utils.ts';

interface MixpanelContextProps<DTO> {
interface MixpanelContextProps<DTO extends MixpanelEvent> {
trackEvent: (event: DTO) => void;
trackPageView: (event: MixpanelPageViewEvent) => void;
}

interface MixpanelProviderProps<DTO> {
interface MixpanelProviderProps<DTO extends MixpanelEvent> {
children: React.ReactNode;
eventApiClient: (args: DTO) => Promise<unknown>;
defaultEventContext?: DTO['context'];
}

const MixpanelContext = createContext<MixpanelContextProps<DTO> | null>(null);
const MixpanelContext =
createContext<MixpanelContextProps<MixpanelEvent> | null>(null);

export function useMixpanelContext() {
const context = useContext(MixpanelContext);
Expand All @@ -32,8 +35,9 @@ export function useMixpanelContext() {
export function MixpanelProvider({
children,
eventApiClient,
}: MixpanelProviderProps<DTO>) {
const trackEvent = (event: DTO) => {
defaultEventContext,
}: MixpanelProviderProps<MixpanelEvent>) {
const trackEvent = (event: MixpanelEvent) => {
// only send events on the client
if (typeof window === 'undefined') {
return;
Expand All @@ -45,8 +49,27 @@ export function MixpanelProvider({
...event,
context: {
title: document.title,
href: window.location.href,
path: window.location.pathname,
pathname: window.location.pathname,
pwa: isStandalonePWA(),
...defaultEventContext,
...utmParams,
...event.context,
},
}).catch((e) => console.error(e));
};

const trackPageView = (event: MixpanelPageViewEvent) => {
// only send events on the client
if (typeof window === 'undefined') {
return;
}

const utmParams = extractUtmParams(window.location.search);

eventApiClient({
...event,
name: 'Page view',
context: {
pwa: isStandalonePWA(),
...utmParams,
...event.context,
Expand All @@ -62,6 +85,7 @@ export function MixpanelProvider({
<MixpanelContext.Provider
value={{
trackEvent,
trackPageView,
}}
>
{children}
Expand Down
52 changes: 49 additions & 3 deletions lib/mixpanel/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
* the other properties are suggestions for a "normal" event.
* @example
* const event: MixpanelEvent = {
* name: 'Contact', // e.g. "Update profile", "Add to cart", "Purchase", "Page view"
* name: 'Contact', // e.g. "Update profile", "Add to cart", "Purchase"
* context: { // Give some context to the event. Where is it triggered and by who
* title: 'Product Page', // What page is the event triggered on
* path: '/product/123', // Make sure there aren't any personal info in the path
* pathname: '/product/123', // Make sure there aren't any personal info in the path
* href: 'https://www.example.com/product/123', // Make sure there aren't any personal info in the href
* route: '/product/:id',
* audience: 'Freelancer', // Who is triggering this event e.g. a role or "new user"
* section: 'footer', // What section is the event triggered in
* pwa: true, // Is the event triggered in a PWA
* utm_source: 'Facebook', // track the source where traffic is coming from, including a website or advertiser
* utm_medium: 'advertising', // track the advertising medium, including email and banner ads
* utm_campaign: 'Black friday', // track the campaign name associated with the traffic
Expand All @@ -27,7 +28,7 @@ export type MixpanelEvent = {
name: string;
context?: {
title?: string;
path?: string;
pathname?: string;
href?: string;
route?: string;
audience?: string;
Expand All @@ -38,8 +39,53 @@ export type MixpanelEvent = {
utm_campaign?: string;
utm_content?: string;
utm_term?: string;
[key: string]: unknown;
};
data?: {
[key: string]: unknown;
};
};

/**
* @description
* When sending a page view event to Mixpanel we will pass this event to our own backend that is used as a proxy for Mixpanel.
* It differs from the `MixpanelEvent` in that it has a fixed `name` and information about the page should be provided
*
* @example
* const event: MixpanelPageViewEvent = {
* data: {
* title: 'Product Page', // What page is the event triggered on
* pathname: '/product/123', // Make sure there aren't any personal info in the path
* href: 'https://www.example.com/product/123', // Make sure there aren't any personal info in the href
* route: '/product/:id',
* audience: 'Freelancer', // The audience that is viewing the page
* },
* context: { // This is optional and will be mostly provided by the MixpanelProvider
* pwa: true, // Is the event triggered in a PWA
* utm_source: 'Facebook', // track the source where traffic is coming from, including a website or advertiser
* utm_medium: 'advertising', // track the advertising medium, including email and banner ads
* utm_campaign: 'Black friday', // track the campaign name associated with the traffic
* utm_content: 'cta button', //track the specific link within in an ad that a user clicked
* utm_term: 'tv sale', // track keywords associated with campaigns
* }
*}
*/
export type MixpanelPageViewEvent = {
context?: {
pwa?: boolean;
utm_source?: string;
utm_medium?: string;
utm_campaign?: string;
utm_content?: string;
utm_term?: string;
[key: string]: unknown;
};
data?: {
title?: string;
pathname: string;
href?: string;
route?: string;
audience?: string;
[key: string]: unknown;
};
};
Loading
Loading