feat: Setup posthog analytics (#12291)
- Replace June.so analytics with PostHog integration - Maintain existing analytics API interface for seamless migration - Remove all the June references _June.so is shutting down their service, requiring migration to an alternative analytics provider. PostHog was chosen as the replacement due to its robust feature set and similar API structure._
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { AnalyticsBrowser } from '@june-so/analytics-next';
|
import posthog from 'posthog-js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AnalyticsHelper class to initialize and track user analytics
|
* AnalyticsHelper class to initialize and track user analytics
|
||||||
@@ -26,10 +26,12 @@ export class AnalyticsHelper {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let [analytics] = await AnalyticsBrowser.load({
|
posthog.init(this.analyticsToken, {
|
||||||
writeKey: this.analyticsToken,
|
api_host: 'https://app.posthog.com',
|
||||||
|
capture_pageview: false,
|
||||||
|
persistence: 'localStorage+cookie',
|
||||||
});
|
});
|
||||||
this.analytics = analytics;
|
this.analytics = posthog;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,8 +45,7 @@ export class AnalyticsHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.user = user;
|
this.user = user;
|
||||||
this.analytics.identify(this.user.email, {
|
this.analytics.identify(this.user.id.toString(), {
|
||||||
userId: this.user.id,
|
|
||||||
email: this.user.email,
|
email: this.user.email,
|
||||||
name: this.user.name,
|
name: this.user.name,
|
||||||
avatar: this.user.avatar_url,
|
avatar: this.user.avatar_url,
|
||||||
@@ -55,7 +56,7 @@ export class AnalyticsHelper {
|
|||||||
account => account.id === accountId
|
account => account.id === accountId
|
||||||
);
|
);
|
||||||
if (currentAccount) {
|
if (currentAccount) {
|
||||||
this.analytics.group(currentAccount.id, this.user.id, {
|
this.analytics.group('company', currentAccount.id.toString(), {
|
||||||
name: currentAccount.name,
|
name: currentAccount.name,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -71,12 +72,7 @@ export class AnalyticsHelper {
|
|||||||
if (!this.analytics) {
|
if (!this.analytics) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this.analytics.capture(eventName, properties);
|
||||||
this.analytics.track({
|
|
||||||
userId: this.user.id,
|
|
||||||
event: eventName,
|
|
||||||
properties,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -89,9 +85,9 @@ export class AnalyticsHelper {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.analytics.page(params);
|
this.analytics.capture('$pageview', params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This object is shared across, the init is called in app/javascript/packs/application.js
|
// This object is shared across, the init is called in app/javascript/entrypoints/dashboard.js
|
||||||
export default new AnalyticsHelper(window.analyticsConfig);
|
export default new AnalyticsHelper(window.analyticsConfig);
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
import helperObject, { AnalyticsHelper } from '../';
|
import helperObject, { AnalyticsHelper } from '../';
|
||||||
|
|
||||||
vi.mock('@june-so/analytics-next', () => ({
|
vi.mock('posthog-js', () => ({
|
||||||
AnalyticsBrowser: {
|
default: {
|
||||||
load: () => [
|
init: vi.fn(),
|
||||||
{
|
identify: vi.fn(),
|
||||||
identify: vi.fn(),
|
capture: vi.fn(),
|
||||||
track: vi.fn(),
|
group: vi.fn(),
|
||||||
page: vi.fn(),
|
|
||||||
group: vi.fn(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -26,12 +22,12 @@ describe('AnalyticsHelper', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('init', () => {
|
describe('init', () => {
|
||||||
it('should initialize the analytics browser with the correct token', async () => {
|
it('should initialize posthog with the correct token', async () => {
|
||||||
await analyticsHelper.init();
|
await analyticsHelper.init();
|
||||||
expect(analyticsHelper.analytics).not.toBe(null);
|
expect(analyticsHelper.analytics).not.toBe(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not initialize the analytics browser if token is not provided', async () => {
|
it('should not initialize posthog if token is not provided', async () => {
|
||||||
analyticsHelper = new AnalyticsHelper();
|
analyticsHelper = new AnalyticsHelper();
|
||||||
await analyticsHelper.init();
|
await analyticsHelper.init();
|
||||||
expect(analyticsHelper.analytics).toBe(null);
|
expect(analyticsHelper.analytics).toBe(null);
|
||||||
@@ -43,36 +39,36 @@ describe('AnalyticsHelper', () => {
|
|||||||
analyticsHelper.analytics = { identify: vi.fn(), group: vi.fn() };
|
analyticsHelper.analytics = { identify: vi.fn(), group: vi.fn() };
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call identify on analytics browser with correct arguments', () => {
|
it('should call identify on posthog with correct arguments', () => {
|
||||||
analyticsHelper.identify({
|
analyticsHelper.identify({
|
||||||
id: '123',
|
id: 123,
|
||||||
email: 'test@example.com',
|
email: 'test@example.com',
|
||||||
name: 'Test User',
|
name: 'Test User',
|
||||||
avatar_url: 'avatar_url',
|
avatar_url: 'avatar_url',
|
||||||
accounts: [{ id: '1', name: 'Account 1' }],
|
accounts: [{ id: 1, name: 'Account 1' }],
|
||||||
account_id: '1',
|
account_id: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(analyticsHelper.analytics.identify).toHaveBeenCalledWith(
|
expect(analyticsHelper.analytics.identify).toHaveBeenCalledWith('123', {
|
||||||
'test@example.com',
|
email: 'test@example.com',
|
||||||
{
|
name: 'Test User',
|
||||||
userId: '123',
|
avatar: 'avatar_url',
|
||||||
email: 'test@example.com',
|
});
|
||||||
name: 'Test User',
|
expect(analyticsHelper.analytics.group).toHaveBeenCalledWith(
|
||||||
avatar: 'avatar_url',
|
'company',
|
||||||
}
|
'1',
|
||||||
|
{ name: 'Account 1' }
|
||||||
);
|
);
|
||||||
expect(analyticsHelper.analytics.group).toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call identify on analytics browser without group', () => {
|
it('should call identify on posthog without group', () => {
|
||||||
analyticsHelper.identify({
|
analyticsHelper.identify({
|
||||||
id: '123',
|
id: 123,
|
||||||
email: 'test@example.com',
|
email: 'test@example.com',
|
||||||
name: 'Test User',
|
name: 'Test User',
|
||||||
avatar_url: 'avatar_url',
|
avatar_url: 'avatar_url',
|
||||||
accounts: [{ id: '1', name: 'Account 1' }],
|
accounts: [{ id: 1, name: 'Account 1' }],
|
||||||
account_id: '5',
|
account_id: 5,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(analyticsHelper.analytics.group).not.toHaveBeenCalled();
|
expect(analyticsHelper.analytics.group).not.toHaveBeenCalled();
|
||||||
@@ -87,29 +83,27 @@ describe('AnalyticsHelper', () => {
|
|||||||
|
|
||||||
describe('track', () => {
|
describe('track', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
analyticsHelper.analytics = { track: vi.fn() };
|
analyticsHelper.analytics = { capture: vi.fn() };
|
||||||
analyticsHelper.user = { id: '123' };
|
analyticsHelper.user = { id: 123 };
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call track on analytics browser with correct arguments', () => {
|
it('should call capture on posthog with correct arguments', () => {
|
||||||
analyticsHelper.track('Test Event', { prop1: 'value1', prop2: 'value2' });
|
analyticsHelper.track('Test Event', { prop1: 'value1', prop2: 'value2' });
|
||||||
expect(analyticsHelper.analytics.track).toHaveBeenCalledWith({
|
expect(analyticsHelper.analytics.capture).toHaveBeenCalledWith(
|
||||||
userId: '123',
|
'Test Event',
|
||||||
event: 'Test Event',
|
{ prop1: 'value1', prop2: 'value2' }
|
||||||
properties: { prop1: 'value1', prop2: 'value2' },
|
);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call track on analytics browser with default properties', () => {
|
it('should call capture on posthog with default properties', () => {
|
||||||
analyticsHelper.track('Test Event');
|
analyticsHelper.track('Test Event');
|
||||||
expect(analyticsHelper.analytics.track).toHaveBeenCalledWith({
|
expect(analyticsHelper.analytics.capture).toHaveBeenCalledWith(
|
||||||
userId: '123',
|
'Test Event',
|
||||||
event: 'Test Event',
|
{}
|
||||||
properties: {},
|
);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not call track on analytics browser if analytics is not initialized', () => {
|
it('should not call capture on posthog if analytics is not initialized', () => {
|
||||||
analyticsHelper.analytics = null;
|
analyticsHelper.analytics = null;
|
||||||
analyticsHelper.track('Test Event', { prop1: 'value1', prop2: 'value2' });
|
analyticsHelper.track('Test Event', { prop1: 'value1', prop2: 'value2' });
|
||||||
expect(analyticsHelper.analytics).toBe(null);
|
expect(analyticsHelper.analytics).toBe(null);
|
||||||
@@ -118,19 +112,22 @@ describe('AnalyticsHelper', () => {
|
|||||||
|
|
||||||
describe('page', () => {
|
describe('page', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
analyticsHelper.analytics = { page: vi.fn() };
|
analyticsHelper.analytics = { capture: vi.fn() };
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call the analytics.page method with the correct arguments', () => {
|
it('should call the capture method for pageview with the correct arguments', () => {
|
||||||
const params = {
|
const params = {
|
||||||
name: 'Test page',
|
name: 'Test page',
|
||||||
url: '/test',
|
url: '/test',
|
||||||
};
|
};
|
||||||
analyticsHelper.page(params);
|
analyticsHelper.page(params);
|
||||||
expect(analyticsHelper.analytics.page).toHaveBeenCalledWith(params);
|
expect(analyticsHelper.analytics.capture).toHaveBeenCalledWith(
|
||||||
|
'$pageview',
|
||||||
|
params
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not call analytics.page if analytics is null', () => {
|
it('should not call analytics.capture if analytics is null', () => {
|
||||||
analyticsHelper.analytics = null;
|
analyticsHelper.analytics = null;
|
||||||
analyticsHelper.page();
|
analyticsHelper.page();
|
||||||
expect(analyticsHelper.analytics).toBe(null);
|
expect(analyticsHelper.analytics).toBe(null);
|
||||||
|
|||||||
@@ -219,7 +219,7 @@
|
|||||||
- name: ANALYTICS_TOKEN
|
- name: ANALYTICS_TOKEN
|
||||||
value:
|
value:
|
||||||
display_title: 'Analytics Token'
|
display_title: 'Analytics Token'
|
||||||
description: 'The June.so analytics token for Chatwoot cloud'
|
description: 'The PostHog analytics token for Chatwoot cloud'
|
||||||
type: secret
|
type: secret
|
||||||
- name: CLEARBIT_API_KEY
|
- name: CLEARBIT_API_KEY
|
||||||
value:
|
value:
|
||||||
|
|||||||
@@ -40,7 +40,6 @@
|
|||||||
"@hcaptcha/vue3-hcaptcha": "^1.3.0",
|
"@hcaptcha/vue3-hcaptcha": "^1.3.0",
|
||||||
"@highlightjs/vue-plugin": "^2.1.0",
|
"@highlightjs/vue-plugin": "^2.1.0",
|
||||||
"@iconify-json/material-symbols": "^1.2.10",
|
"@iconify-json/material-symbols": "^1.2.10",
|
||||||
"@june-so/analytics-next": "^2.0.0",
|
|
||||||
"@lk77/vue3-color": "^3.0.6",
|
"@lk77/vue3-color": "^3.0.6",
|
||||||
"@radix-ui/colors": "^3.0.0",
|
"@radix-ui/colors": "^3.0.0",
|
||||||
"@rails/actioncable": "6.1.3",
|
"@rails/actioncable": "6.1.3",
|
||||||
@@ -80,6 +79,7 @@
|
|||||||
"md5": "^2.3.0",
|
"md5": "^2.3.0",
|
||||||
"mitt": "^3.0.1",
|
"mitt": "^3.0.1",
|
||||||
"opus-recorder": "^8.0.5",
|
"opus-recorder": "^8.0.5",
|
||||||
|
"posthog-js": "^1.260.2",
|
||||||
"semver": "7.6.3",
|
"semver": "7.6.3",
|
||||||
"snakecase-keys": "^8.0.1",
|
"snakecase-keys": "^8.0.1",
|
||||||
"timezone-phone-codes": "^0.0.2",
|
"timezone-phone-codes": "^0.0.2",
|
||||||
|
|||||||
1332
pnpm-lock.yaml
generated
1332
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user