mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-09-25 08:50:18 +00:00
✨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:
parent
cdce2ca08c
commit
905201548d
@ -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;
|
||||
}
|
||||
|
@ -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 />;
|
||||
}
|
||||
|
@ -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<
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
@ -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);
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user