feat: more events tracking for SaaS (#6234)
This commit is contained in:
@@ -1,9 +1,73 @@
|
||||
export const EXECUTED_A_MACRO = 'Executed a macro';
|
||||
export const SENT_MESSAGE = 'Sent a message';
|
||||
export const SENT_PRIVATE_NOTE = 'Sent a private note';
|
||||
export const INSERTED_A_CANNED_RESPONSE = 'Inserted a canned response';
|
||||
export const USED_MENTIONS = 'Used mentions';
|
||||
export const MERGED_CONTACTS = 'Used merge contact option';
|
||||
export const ADDED_TO_CANNED_RESPONSE = 'Used added to canned response option';
|
||||
export const ADDED_A_CUSTOM_ATTRIBUTE = 'Added a custom attribute';
|
||||
export const ADDED_AN_INBOX = 'Added an inbox';
|
||||
export const CONVERSATION_EVENTS = Object.freeze({
|
||||
EXECUTED_A_MACRO: 'Executed a macro',
|
||||
SENT_MESSAGE: 'Sent a message',
|
||||
SENT_PRIVATE_NOTE: 'Sent a private note',
|
||||
INSERTED_A_CANNED_RESPONSE: 'Inserted a canned response',
|
||||
USED_MENTIONS: 'Used mentions',
|
||||
});
|
||||
|
||||
export const ACCOUNT_EVENTS = Object.freeze({
|
||||
ADDED_TO_CANNED_RESPONSE: 'Used added to canned response option',
|
||||
ADDED_A_CUSTOM_ATTRIBUTE: 'Added a custom attribute',
|
||||
ADDED_AN_INBOX: 'Added an inbox',
|
||||
});
|
||||
|
||||
export const LABEL_EVENTS = Object.freeze({
|
||||
CREATE: 'Created a label',
|
||||
UPDATE: 'Updated a label',
|
||||
DELETED: 'Deleted a label',
|
||||
APPLY_LABEL: 'Applied a label',
|
||||
});
|
||||
|
||||
// REPORTS EVENTS
|
||||
export const REPORTS_EVENTS = Object.freeze({
|
||||
DOWNLOAD_REPORT: 'Downloaded a report',
|
||||
FILTER_REPORT: 'Used filters in the reports',
|
||||
});
|
||||
|
||||
// CONTACTS PAGE EVENTS
|
||||
export const CONTACTS_EVENTS = Object.freeze({
|
||||
APPLY_FILTER: 'Applied filters in the contacts list',
|
||||
SAVE_FILTER: 'Saved a filter in the contacts list',
|
||||
DELETE_FILTER: 'Deleted a filter in the contacts list',
|
||||
|
||||
APPLY_SORT: 'Sorted contacts list',
|
||||
SEARCH: 'Searched contacts list',
|
||||
CREATE_CONTACT: 'Created a contact',
|
||||
MERGED_CONTACTS: 'Used merge contact option',
|
||||
IMPORT_MODAL_OPEN: 'Opened import contacts modal',
|
||||
IMPORT_FAILURE: 'Import contacts failed',
|
||||
IMPORT_SUCCESS: 'Imported contacts successfully',
|
||||
});
|
||||
|
||||
// CAMPAIGN EVENTS
|
||||
export const CAMPAIGNS_EVENTS = Object.freeze({
|
||||
OPEN_NEW_CAMPAIGN_MODAL: 'Opened new campaign modal',
|
||||
CREATE_CAMPAIGN: 'Created a new campaign',
|
||||
UPDATE_CAMPAIGN: 'Updated a campaign',
|
||||
DELETE_CAMPAIGN: 'Deleted a campaign',
|
||||
});
|
||||
|
||||
// PORTAL EVENTS
|
||||
export const PORTALS_EVENTS = Object.freeze({
|
||||
ONBOARD_BASIC_INFORMATION: 'New Portal: Completed basic information',
|
||||
ONBOARD_CUSTOMIZATION: 'New portal: Completed customization',
|
||||
CREATE_PORTAL: 'Created a portal',
|
||||
DELETE_PORTAL: 'Deleted a portal',
|
||||
UPDATE_PORTAL: 'Updated a portal',
|
||||
|
||||
CREATE_LOCALE: 'Created a portal locale',
|
||||
SET_DEFAULT_LOCALE: 'Set default portal locale',
|
||||
DELETE_LOCALE: 'Deleted a portal locale',
|
||||
SWITCH_LOCALE: 'Switched portal locale',
|
||||
|
||||
CREATE_CATEGORY: 'Created a portal category',
|
||||
DELETE_CATEGORY: 'Deleted a portal category',
|
||||
EDIT_CATEGORY: 'Edited a portal category',
|
||||
|
||||
CREATE_ARTICLE: 'Created an article',
|
||||
PUBLISH_ARTICLE: 'Published an article',
|
||||
ARCHIVE_ARTICLE: 'Archived an article',
|
||||
DELETE_ARTICLE: 'Deleted an article',
|
||||
PREVIEW_ARTICLE: 'Previewed article',
|
||||
});
|
||||
|
||||
@@ -1,12 +1,26 @@
|
||||
import { AnalyticsBrowser } from '@june-so/analytics-next';
|
||||
|
||||
class AnalyticsHelper {
|
||||
/**
|
||||
* AnalyticsHelper class to initialize and track user analytics
|
||||
* @class AnalyticsHelper
|
||||
*/
|
||||
export class AnalyticsHelper {
|
||||
/**
|
||||
* @constructor
|
||||
* @param {Object} [options={}] - options for analytics
|
||||
* @param {string} [options.token] - analytics token
|
||||
*/
|
||||
constructor({ token: analyticsToken } = {}) {
|
||||
this.analyticsToken = analyticsToken;
|
||||
this.analytics = null;
|
||||
this.user = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize analytics
|
||||
* @function
|
||||
* @async
|
||||
*/
|
||||
async init() {
|
||||
if (!this.analyticsToken) {
|
||||
return;
|
||||
@@ -18,6 +32,11 @@ class AnalyticsHelper {
|
||||
this.analytics = analytics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identify the user
|
||||
* @function
|
||||
* @param {Object} user - User object
|
||||
*/
|
||||
identify(user) {
|
||||
if (!this.analytics) {
|
||||
return;
|
||||
@@ -41,6 +60,12 @@ class AnalyticsHelper {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Track any event
|
||||
* @function
|
||||
* @param {string} eventName - event name
|
||||
* @param {Object} [properties={}] - event properties
|
||||
*/
|
||||
track(eventName, properties = {}) {
|
||||
if (!this.analytics) {
|
||||
return;
|
||||
@@ -53,6 +78,11 @@ class AnalyticsHelper {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Track the page views
|
||||
* @function
|
||||
* @param {Object} params - Page view properties
|
||||
*/
|
||||
page(params) {
|
||||
if (!this.analytics) {
|
||||
return;
|
||||
@@ -62,6 +92,5 @@ class AnalyticsHelper {
|
||||
}
|
||||
}
|
||||
|
||||
export * as ANALYTICS_EVENTS from './events';
|
||||
|
||||
// This object is shared across, the init is called in app/javascript/packs/application.js
|
||||
export default new AnalyticsHelper(window.analyticsConfig);
|
||||
|
||||
11
app/javascript/dashboard/helper/AnalyticsHelper/plugin.js
Normal file
11
app/javascript/dashboard/helper/AnalyticsHelper/plugin.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import analyticsHelper from '.';
|
||||
|
||||
export default {
|
||||
// This function is called when the Vue plugin is installed
|
||||
install(Vue) {
|
||||
analyticsHelper.init();
|
||||
Vue.prototype.$analytics = analyticsHelper;
|
||||
// Add a shorthand function for the track method on the helper module
|
||||
Vue.prototype.$track = analyticsHelper.track.bind(analyticsHelper);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
import * as AnalyticsEvents from '../events';
|
||||
|
||||
describe('Analytics Events', () => {
|
||||
it('should be frozen', () => {
|
||||
Object.entries(AnalyticsEvents).forEach(([, value]) => {
|
||||
expect(Object.isFrozen(value)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('event names should be unique across the board', () => {
|
||||
const allValues = Object.values(AnalyticsEvents).reduce(
|
||||
(acc, curr) => acc.concat(Object.values(curr)),
|
||||
[]
|
||||
);
|
||||
const uniqueValues = new Set(allValues);
|
||||
expect(allValues.length).toBe(uniqueValues.size);
|
||||
});
|
||||
|
||||
it('should not allow properties to be modified', () => {
|
||||
Object.values(AnalyticsEvents).forEach(eventsObject => {
|
||||
expect(() => {
|
||||
eventsObject.NEW_PROPERTY = 'new value';
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,139 @@
|
||||
import helperObject, { AnalyticsHelper } from '../';
|
||||
|
||||
jest.mock('@june-so/analytics-next', () => ({
|
||||
AnalyticsBrowser: {
|
||||
load: () => [
|
||||
{
|
||||
identify: jest.fn(),
|
||||
track: jest.fn(),
|
||||
page: jest.fn(),
|
||||
group: jest.fn(),
|
||||
},
|
||||
],
|
||||
},
|
||||
}));
|
||||
|
||||
describe('helperObject', () => {
|
||||
it('should return an instance of AnalyticsHelper', () => {
|
||||
expect(helperObject).toBeInstanceOf(AnalyticsHelper);
|
||||
});
|
||||
});
|
||||
|
||||
describe('AnalyticsHelper', () => {
|
||||
let analyticsHelper;
|
||||
beforeEach(() => {
|
||||
analyticsHelper = new AnalyticsHelper({ token: 'test_token' });
|
||||
});
|
||||
|
||||
describe('init', () => {
|
||||
it('should initialize the analytics browser with the correct token', async () => {
|
||||
await analyticsHelper.init();
|
||||
expect(analyticsHelper.analytics).not.toBe(null);
|
||||
});
|
||||
|
||||
it('should not initialize the analytics browser if token is not provided', async () => {
|
||||
analyticsHelper = new AnalyticsHelper();
|
||||
await analyticsHelper.init();
|
||||
expect(analyticsHelper.analytics).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('identify', () => {
|
||||
beforeEach(() => {
|
||||
analyticsHelper.analytics = { identify: jest.fn(), group: jest.fn() };
|
||||
});
|
||||
|
||||
it('should call identify on analytics browser with correct arguments', () => {
|
||||
analyticsHelper.identify({
|
||||
id: '123',
|
||||
email: 'test@example.com',
|
||||
name: 'Test User',
|
||||
avatar_url: 'avatar_url',
|
||||
accounts: [{ id: '1', name: 'Account 1' }],
|
||||
account_id: '1',
|
||||
});
|
||||
|
||||
expect(analyticsHelper.analytics.identify).toHaveBeenCalledWith(
|
||||
'test@example.com',
|
||||
{
|
||||
userId: '123',
|
||||
email: 'test@example.com',
|
||||
name: 'Test User',
|
||||
avatar: 'avatar_url',
|
||||
}
|
||||
);
|
||||
expect(analyticsHelper.analytics.group).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call identify on analytics browser without group', () => {
|
||||
analyticsHelper.identify({
|
||||
id: '123',
|
||||
email: 'test@example.com',
|
||||
name: 'Test User',
|
||||
avatar_url: 'avatar_url',
|
||||
accounts: [{ id: '1', name: 'Account 1' }],
|
||||
account_id: '5',
|
||||
});
|
||||
|
||||
expect(analyticsHelper.analytics.group).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not call analytics.page if analytics is null', () => {
|
||||
analyticsHelper.analytics = null;
|
||||
analyticsHelper.identify({});
|
||||
expect(analyticsHelper.analytics).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('track', () => {
|
||||
beforeEach(() => {
|
||||
analyticsHelper.analytics = { track: jest.fn() };
|
||||
analyticsHelper.user = { id: '123' };
|
||||
});
|
||||
|
||||
it('should call track on analytics browser with correct arguments', () => {
|
||||
analyticsHelper.track('Test Event', { prop1: 'value1', prop2: 'value2' });
|
||||
expect(analyticsHelper.analytics.track).toHaveBeenCalledWith({
|
||||
userId: '123',
|
||||
event: 'Test Event',
|
||||
properties: { prop1: 'value1', prop2: 'value2' },
|
||||
});
|
||||
});
|
||||
|
||||
it('should call track on analytics browser with default properties', () => {
|
||||
analyticsHelper.track('Test Event');
|
||||
expect(analyticsHelper.analytics.track).toHaveBeenCalledWith({
|
||||
userId: '123',
|
||||
event: 'Test Event',
|
||||
properties: {},
|
||||
});
|
||||
});
|
||||
|
||||
it('should not call track on analytics browser if analytics is not initialized', () => {
|
||||
analyticsHelper.analytics = null;
|
||||
analyticsHelper.track('Test Event', { prop1: 'value1', prop2: 'value2' });
|
||||
expect(analyticsHelper.analytics).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('page', () => {
|
||||
beforeEach(() => {
|
||||
analyticsHelper.analytics = { page: jest.fn() };
|
||||
});
|
||||
|
||||
it('should call the analytics.page method with the correct arguments', () => {
|
||||
const params = {
|
||||
name: 'Test page',
|
||||
url: '/test',
|
||||
};
|
||||
analyticsHelper.page(params);
|
||||
expect(analyticsHelper.analytics.page).toHaveBeenCalledWith(params);
|
||||
});
|
||||
|
||||
it('should not call analytics.page if analytics is null', () => {
|
||||
analyticsHelper.analytics = null;
|
||||
analyticsHelper.page();
|
||||
expect(analyticsHelper.analytics).toBe(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import Vue from 'vue';
|
||||
import plugin from '../plugin';
|
||||
import analyticsHelper from '../index';
|
||||
|
||||
describe('Vue Analytics Plugin', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(analyticsHelper, 'init');
|
||||
jest.spyOn(analyticsHelper, 'track');
|
||||
Vue.use(plugin);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetModules();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should call the init method on the analyticsHelper', () => {
|
||||
expect(analyticsHelper.init).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should add the analyticsHelper to the Vue prototype', () => {
|
||||
expect(Vue.prototype.$analytics).toBe(analyticsHelper);
|
||||
});
|
||||
|
||||
it('should add the track method to the Vue prototype', () => {
|
||||
expect(typeof Vue.prototype.$track).toBe('function');
|
||||
Vue.prototype.$track('eventName');
|
||||
expect(analyticsHelper.track).toHaveBeenCalledWith('eventName');
|
||||
});
|
||||
|
||||
it('should call the track method on the analyticsHelper when $track is called', () => {
|
||||
Vue.prototype.$track('eventName');
|
||||
expect(analyticsHelper.track).toHaveBeenCalledWith('eventName');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user