Skip to content

New Posthog user id pattern: [inspectionId]-[UUID-distinctId] #931

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

Merged
merged 1 commit into from
Jun 23, 2025
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
2 changes: 2 additions & 0 deletions configs/test-utils/src/__mocks__/@monkvision/analytics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ class AnalyticsAdapterMock {
resetUser = jest.fn();
trackEvent = jest.fn();
setEventsProperties = jest.fn();
getUserId = jest.fn(() => 'test-user-id');
}

export = {
Expand All @@ -18,6 +19,7 @@ export = {
resetUser: jest.fn(),
trackEvent: jest.fn(),
setEventsProperties: jest.fn(),
getUserId: jest.fn(() => 'test-user-id'),
})),
AnalyticsProvider: jest.fn(({ children }) => <>{children}</>),
};
11 changes: 8 additions & 3 deletions packages/analytics/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ function MyCustomComponent() {
resetUser,
trackEvent,
setEventsProperties,
getUserId,
} = useAnalytics();
}
```
Expand Down Expand Up @@ -89,6 +90,10 @@ class MyCustomAnalyticsAdapter implements AnalyticsAdapter {
setEventsProperties(context: Record<string, Primitive>): void {
// Set properties for every events
}

getUserId(): string {
// Get the current user ID
}
}
```

Expand Down Expand Up @@ -142,12 +147,12 @@ trackEvent: (name: string, context?: Record<string, Primitive>) => void

This method track a event and send it to the analytics tool. The name of the event is required and an optional context can be provided that can contain tags or properties associated to the event.

#### setEventsProperties
#### getUserId
```typescript
trackEvent: (context: Record<string, Primitive>) => void
getUserId: () => string
```

This method set properties that will be sent with every `trackEvent`.
This method retrieves the current user ID.

## Analytics Adapters
### EmptyAnalyticsAdapter
Expand Down
4 changes: 4 additions & 0 deletions packages/analytics/src/adapters/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,8 @@ export interface AnalyticsAdapter {
* @param context context of every event.
*/
setEventsProperties: (context: Record<string, Primitive>) => void;
/**
* Get the ID of the user currently using the application.
*/
getUserId: () => string;
}
10 changes: 10 additions & 0 deletions packages/analytics/src/adapters/emptyAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,14 @@ export class EmptyAnalyticsAdapter implements AnalyticsAdapter {
);
}
}

getUserId(): string {
if (this.options.showUnsupportedMethodWarnings) {
console.warn(
'Application users are not supported by the current Monk Analytics Adapter and calling getUserId will have no effect.',
);
}

return '[UserId]';
}
}
1 change: 1 addition & 0 deletions packages/analytics/src/react/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export function useAnalytics(): AnalyticsAdapter {
resetUser: adapter.resetUser.bind(adapter),
trackEvent: adapter.trackEvent.bind(adapter),
setEventsProperties: adapter.setEventsProperties.bind(adapter),
getUserId: adapter.getUserId.bind(adapter),
}),
[],
);
Expand Down
11 changes: 9 additions & 2 deletions packages/analytics/test/react/provider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { fireEvent, render, screen } from '@testing-library/react';
import { AnalyticsAdapter, AnalyticsProvider, useAnalytics } from '../../src';

function TestComponent() {
const { setUserId, setUserProperties, trackEvent, resetUser, setEventsProperties } =
const { setUserId, setUserProperties, trackEvent, resetUser, setEventsProperties, getUserId } =
useAnalytics();

return (
Expand All @@ -29,7 +29,10 @@ function TestComponent() {
data-testid='set-events-properties-btn'
onClick={() => setEventsProperties({ test: 'test' })}
>
trackEvent
setEventsProperties
</button>
<button data-testid='get-user-id-btn' onClick={() => getUserId()}>
setEventsProperties
</button>
</div>
);
Expand All @@ -43,12 +46,14 @@ describe('AnalyticsProvider component', () => {
resetUser: jest.fn(),
trackEvent: jest.fn(),
setEventsProperties: jest.fn(),
getUserId: jest.fn(() => 'test-user-id'),
};
const setUserIdSpy = jest.spyOn(adapter, 'setUserId');
const setUserPropertiesSpy = jest.spyOn(adapter, 'setUserProperties');
const resetUserSpy = jest.spyOn(adapter, 'resetUser');
const trackEventSpy = jest.spyOn(adapter, 'trackEvent');
const setEventsPropertiesSpy = jest.spyOn(adapter, 'setEventsProperties');
const getUserId = jest.spyOn(adapter, 'getUserId');

const { unmount } = render(
<AnalyticsProvider adapter={adapter}>
Expand All @@ -66,6 +71,8 @@ describe('AnalyticsProvider component', () => {
expect(trackEventSpy).toHaveBeenCalledTimes(1);
fireEvent.click(screen.getByTestId('set-events-properties-btn'));
expect(setEventsPropertiesSpy).toHaveBeenCalledTimes(1);
fireEvent.click(screen.getByTestId('get-user-id-btn'));
expect(getUserId).toHaveBeenCalledTimes(1);
unmount();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
useMonkAppState,
} from '@monkvision/common';
import { useMonitoring } from '@monkvision/monitoring';
import { useAnalytics } from '@monkvision/analytics';
import { styles } from './CreateInspection.styles';
import { i18nCreateInspection } from './i18n';
import { Spinner } from '../Spinner';
Expand Down Expand Up @@ -57,7 +56,6 @@ export const CreateInspection = i18nWrap(function CreateInspection({
apiDomain: config.apiDomain,
thumbnailDomain: config.thumbnailDomain,
});
const analytics = useAnalytics();
const isMounted = useIsMounted();

useEffect(() => {
Expand All @@ -84,10 +82,9 @@ export const CreateInspection = i18nWrap(function CreateInspection({
}
} else {
setTags({ inspectionId });
analytics.setUserId(inspectionId);
onInspectionCreated?.();
}
}, [inspectionId, setTags, analytics, retry, isMounted]);
}, [inspectionId, setTags, retry, isMounted]);

return (
<div style={styles['container']}>
Expand Down
3 changes: 0 additions & 3 deletions packages/common-ui-web/src/components/Login/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
useMonkTheme,
} from '@monkvision/common';
import { useMonitoring } from '@monkvision/monitoring';
import { useAnalytics } from '@monkvision/analytics';
import { Button } from '../Button';
import { i18nLogin } from './i18n';
import { styles } from './Login.styles';
Expand Down Expand Up @@ -55,7 +54,6 @@ export const Login = i18nWrap(function Login({ onLoginSuccessful, lang, style =
const loading = useLoadingState();
const { authToken, setAuthToken, config } = useMonkAppState();
const { handleError, setUserId } = useMonitoring();
const analytics = useAnalytics();
const { login, logout } = useAuth();
const { t } = useTranslation();
const { rootStyles } = useMonkTheme();
Expand Down Expand Up @@ -95,7 +93,6 @@ export const Login = i18nWrap(function Login({ onLoginSuccessful, lang, style =
const userId = token ? decodeMonkJwt(token) : undefined;
if (userId?.sub) {
setUserId(userId.sub);
analytics.setUserProperties({ authToken: userId.sub });
}
}
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { AllOrNone, VehicleType } from '@monkvision/types';
import { RefObject, useEffect, useMemo, useRef, useState } from 'react';
import { decodeMonkJwt, MonkApiConfig, useMonkApi } from '@monkvision/network';
import { useMonitoring } from '@monkvision/monitoring';
import { useAnalytics } from '@monkvision/analytics';
import { styles } from './VehicleTypeSelection.styles';
import { i18nVehicleTypeSelection } from './i18n';
import { Button } from '../Button';
Expand Down Expand Up @@ -88,7 +87,6 @@ export const VehicleTypeSelection = i18nWrap(function VehicleTypeSelection(
});
const loading = useLoadingState();
const { handleError, setTags, setUserId } = useMonitoring();
const analytics = useAnalytics();
const [initialScroll, setInitialScroll] = useState(true);
const vehicleTypes = useMemo(
() => getVehicleTypes(props.availableVehicleTypes),
Expand All @@ -105,14 +103,12 @@ export const VehicleTypeSelection = i18nWrap(function VehicleTypeSelection(
useEffect(() => {
if (props.inspectionId) {
setTags({ inspectionId: props.inspectionId });
analytics.setUserId(props.inspectionId);
}
const userId = props.authToken ? decodeMonkJwt(props.authToken) : undefined;
if (userId?.sub) {
setUserId(userId.sub);
analytics.setUserProperties({ authToken: userId.sub });
}
}, [props.inspectionId, props.authToken, analytics, setTags, setUserId]);
}, [props.inspectionId, props.authToken, setTags, setUserId]);

useEffect(() => {
loading.start();
Expand Down
4 changes: 1 addition & 3 deletions packages/common/src/apps/appStateProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
VideoCaptureAppConfig,
} from '@monkvision/types';
import { sights } from '@monkvision/sights';
import React, { PropsWithChildren, useContext, useEffect, useMemo, useState } from 'react';
import { PropsWithChildren, useContext, useEffect, useMemo, useState } from 'react';
import { useIsMounted, useLoadingState } from '../hooks';
import { MonkSearchParam, useMonkSearchParams } from './searchParams';
import {
Expand All @@ -17,7 +17,6 @@ import {
VideoCaptureAppState,
} from './appState';
import { useAppStateMonitoring } from './monitoring';
import { useAppStateAnalytics } from './analytics';
import { getAvailableVehicleTypes } from '../utils';

/**
Expand Down Expand Up @@ -98,7 +97,6 @@ export function MonkAppStateProvider({
const monkSearchParams = useMonkSearchParams({ availableVehicleTypes });
const isMounted = useIsMounted();
useAppStateMonitoring({ authToken, inspectionId, vehicleType, steeringWheel });
useAppStateAnalytics({ inspectionId });

useEffect(() => {
loading.onSuccess();
Expand Down
8 changes: 8 additions & 0 deletions packages/inspection-capture-web/src/hooks/useTracking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,17 @@ export function useTracking({ inspectionId, authToken }: TrackingParams) {
const monitoring = useMonitoring();

useEffect(() => {
const currentAnalyticsUserId = analytics.getUserId();
let newAnalyticsUserId = `${inspectionId}:${currentAnalyticsUserId}`;
if (currentAnalyticsUserId.includes(':')) {
newAnalyticsUserId = `${inspectionId}:${currentAnalyticsUserId.split(':')[1]}`;
}
analytics.setUserId(newAnalyticsUserId);
console.log('posthog id : ', newAnalyticsUserId, ', inspectionId: ', inspectionId);
monitoring.setTags({
inspectionId,
});

const userId = decodeMonkJwt(authToken)?.sub;
if (userId) {
monitoring.setUserId(userId);
Expand Down
4 changes: 4 additions & 0 deletions packages/posthog/src/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,8 @@ export class PosthogAnalyticsAdapter implements AnalyticsAdapter {
setEventsProperties(context: Record<string, Primitive>): void {
posthog.register(context);
}

getUserId(): string {
return posthog.get_distinct_id();
}
}