feat(ui): Add click event web analytic (#10832)

* feat(ui): Add click event web analytic

* chore: remove userId check for custom event

* chore: only track event if event value if there

* chore: add unit test

* address comments

---------

Co-authored-by: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com>
This commit is contained in:
Sachin Chaurasiya 2023-04-03 11:18:14 +05:30 committed by GitHub
parent cdce2ca08c
commit 905201548d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 143 additions and 21 deletions

View File

@ -28,6 +28,7 @@ export interface Payload {
type: string;
properties: PayloadProperties;
anonymousId: string;
event: string;
meta: {
rid: string;
ts: number;
@ -35,6 +36,6 @@ export interface Payload {
};
}
export interface WebPageData extends PageData {
export interface AnalyticsData extends PageData {
payload: Payload;
}

View File

@ -11,8 +11,9 @@
* limitations under the License.
*/
import { CustomEventTypes } from 'generated/analytics/webAnalyticEventData';
import AccountActivationConfirmation from 'pages/signup/account-activation-confirmation.component';
import React, { useEffect } from 'react';
import React, { useCallback, useEffect } from 'react';
import { Redirect, Route, Switch, useLocation } from 'react-router-dom';
import { useAnalytics } from 'use-analytics';
import { ROUTES } from '../../constants/constants';
@ -86,6 +87,24 @@ const AppRouter = () => {
}
}, [location.pathname]);
const handleClickEvent = useCallback(
(event: MouseEvent) => {
const eventValue =
(event.target as HTMLElement)?.textContent || CustomEventTypes.Click;
if (eventValue) {
analytics.track(eventValue);
}
},
[analytics]
);
useEffect(() => {
const targetNode = document.body;
targetNode.addEventListener('click', handleClickEvent);
return () => targetNode.removeEventListener('click', handleClickEvent);
}, [handleClickEvent]);
if (loading) {
return <Loader />;
}

View File

@ -15,7 +15,7 @@ import { AxiosResponse } from 'axios';
import { WebAnalyticEventData } from '../generated/analytics/webAnalyticEventData';
import APIClient from './index';
export const postPageView = async (
export const postWebAnalyticEvent = async (
webAnalyticEventData: WebAnalyticEventData
) => {
const response = await APIClient.put<

View File

@ -11,10 +11,63 @@
* limitations under the License.
*/
import { getAnalyticInstance, getReferrerPath } from './WebAnalyticsUtils';
import { AnalyticsData } from 'components/WebAnalytics/WebAnalytics.interface';
import { postWebAnalyticEvent } from 'rest/WebAnalyticsAPI';
import {
getAnalyticInstance,
getReferrerPath,
trackCustomEvent,
} from './WebAnalyticsUtils';
const userId = 'userId';
jest.mock('rest/WebAnalyticsAPI', () => ({
postWebAnalyticEvent: jest.fn().mockImplementation(() => Promise.resolve()),
}));
jest.mock('@analytics/session-utils', () => ({
...jest.requireActual('@analytics/session-utils'),
getSession: jest
.fn()
.mockReturnValue({ id: '19c85e4f-7679-4fba-813f-e72108d914c4' }),
}));
const MOCK_ANALYTICS_DATA: AnalyticsData = {
payload: {
type: 'page',
properties: {
title: 'OpenMetadata',
url: 'http://localhost/',
path: '/',
hash: '',
search: '?page=1',
width: 1440,
height: 284,
referrer: 'http://localhost:3000/explore/tables?page=1',
},
event: 'Explore',
meta: {
rid: '7a14e508-5cbc-4bf9-b922-0607a2ff2aa5',
ts: 1680246874535,
hasCallback: true,
},
anonymousId: '7a14e508-5cbc-4bf9-b922-0607a2ff2aa5',
},
};
const CUSTOM_EVENT_PAYLOAD = {
eventType: 'CustomEvent',
eventData: {
url: '/',
fullUrl: 'http://localhost/',
hostname: 'localhost',
eventType: 'CLICK',
sessionId: '19c85e4f-7679-4fba-813f-e72108d914c4',
eventValue: 'Explore',
},
timestamp: 1680246874535,
};
const mockReferrer =
'http://localhost:3000/settings/members/teams/Organization';
@ -42,4 +95,10 @@ describe('Web Analytics utils', () => {
expect(pathname).toBe('');
});
it('trackCustomEvent should call postWebAnalyticEvent', () => {
trackCustomEvent(MOCK_ANALYTICS_DATA);
expect(postWebAnalyticEvent).toHaveBeenCalledWith(CUSTOM_EVENT_PAYLOAD);
});
});

View File

@ -17,8 +17,12 @@ import {
setSession,
} from '@analytics/session-utils';
import Analytics, { AnalyticsInstance } from 'analytics';
import { WebPageData } from 'components/WebAnalytics/WebAnalytics.interface';
import { postPageView } from 'rest/WebAnalyticsAPI';
import { AnalyticsData } from 'components/WebAnalytics/WebAnalytics.interface';
import {
CustomEvent,
CustomEventTypes,
} from 'generated/analytics/webAnalyticEventType/customEvent';
import { postWebAnalyticEvent } from 'rest/WebAnalyticsAPI';
import {
WebAnalyticEventData,
WebAnalyticEventType,
@ -58,12 +62,30 @@ export const getPageLoadTime = (performance: Performance) => {
);
};
const handlePostAnalytic = async (
webAnalyticEventData: WebAnalyticEventData
) => {
try {
/**
* extend the session expiry if user continues to interact
* Let say expiry is at "5:45:23 PM", and user spent some time and then
* interact with other page in 2 minutes so expiry time will be extended to "5:47:23 PM"
*/
setSession(30, {}, true);
// collect the event data
await postWebAnalyticEvent(webAnalyticEventData);
} catch (_error) {
// silently ignore the error
}
};
/**
* track the page view if user id is available.
* @param pageData PageData
* @param userId string
*/
export const trackPageView = async (pageData: WebPageData, userId: string) => {
export const trackPageView = (pageData: AnalyticsData, userId: string) => {
// Get the current session
const currentSession = getSession();
@ -100,22 +122,40 @@ export const trackPageView = async (pageData: WebPageData, userId: string) => {
timestamp,
};
try {
/**
* extend the session expiry if user continues to interact
* Let say expiry is at "5:45:23 PM", and user spent some time and then
* interact with other page in 2 minutes so expiry time will be extended to "5:47:23 PM"
*/
setSession(30, {}, true);
// collect the page event
await postPageView(webAnalyticEventData);
} catch (_error) {
// handle page view error
}
handlePostAnalytic(webAnalyticEventData);
}
};
export const trackCustomEvent = (eventData: AnalyticsData) => {
// Get the current session
const currentSession = getSession();
const { payload } = eventData;
const { meta, event: eventValue } = payload;
const { location } = window;
// timestamp for the current event
const timestamp = meta.ts;
const customEventData: CustomEvent = {
url: location.pathname,
fullUrl: location.href,
hostname: location.hostname,
eventType: CustomEventTypes.Click,
sessionId: currentSession.id,
eventValue,
};
const webAnalyticEventData: WebAnalyticEventData = {
eventType: WebAnalyticEventType.CustomEvent,
eventData: customEventData,
timestamp,
};
handlePostAnalytic(webAnalyticEventData);
};
/**
*
* @param userId string
@ -127,9 +167,12 @@ export const getAnalyticInstance = (userId: string): AnalyticsInstance => {
plugins: [
{
name: 'OM-Plugin',
page: (pageData: WebPageData) => {
page: (pageData: AnalyticsData) => {
trackPageView(pageData, userId);
},
track: (trackingData: AnalyticsData) => {
trackCustomEvent(trackingData);
},
},
],
});