mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-09-28 02:13:09 +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;
|
type: string;
|
||||||
properties: PayloadProperties;
|
properties: PayloadProperties;
|
||||||
anonymousId: string;
|
anonymousId: string;
|
||||||
|
event: string;
|
||||||
meta: {
|
meta: {
|
||||||
rid: string;
|
rid: string;
|
||||||
ts: number;
|
ts: number;
|
||||||
@ -35,6 +36,6 @@ export interface Payload {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WebPageData extends PageData {
|
export interface AnalyticsData extends PageData {
|
||||||
payload: Payload;
|
payload: Payload;
|
||||||
}
|
}
|
||||||
|
@ -11,8 +11,9 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { CustomEventTypes } from 'generated/analytics/webAnalyticEventData';
|
||||||
import AccountActivationConfirmation from 'pages/signup/account-activation-confirmation.component';
|
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 { Redirect, Route, Switch, useLocation } from 'react-router-dom';
|
||||||
import { useAnalytics } from 'use-analytics';
|
import { useAnalytics } from 'use-analytics';
|
||||||
import { ROUTES } from '../../constants/constants';
|
import { ROUTES } from '../../constants/constants';
|
||||||
@ -86,6 +87,24 @@ const AppRouter = () => {
|
|||||||
}
|
}
|
||||||
}, [location.pathname]);
|
}, [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) {
|
if (loading) {
|
||||||
return <Loader />;
|
return <Loader />;
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ import { AxiosResponse } from 'axios';
|
|||||||
import { WebAnalyticEventData } from '../generated/analytics/webAnalyticEventData';
|
import { WebAnalyticEventData } from '../generated/analytics/webAnalyticEventData';
|
||||||
import APIClient from './index';
|
import APIClient from './index';
|
||||||
|
|
||||||
export const postPageView = async (
|
export const postWebAnalyticEvent = async (
|
||||||
webAnalyticEventData: WebAnalyticEventData
|
webAnalyticEventData: WebAnalyticEventData
|
||||||
) => {
|
) => {
|
||||||
const response = await APIClient.put<
|
const response = await APIClient.put<
|
||||||
|
@ -11,10 +11,63 @@
|
|||||||
* limitations under the License.
|
* 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';
|
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 =
|
const mockReferrer =
|
||||||
'http://localhost:3000/settings/members/teams/Organization';
|
'http://localhost:3000/settings/members/teams/Organization';
|
||||||
|
|
||||||
@ -42,4 +95,10 @@ describe('Web Analytics utils', () => {
|
|||||||
|
|
||||||
expect(pathname).toBe('');
|
expect(pathname).toBe('');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('trackCustomEvent should call postWebAnalyticEvent', () => {
|
||||||
|
trackCustomEvent(MOCK_ANALYTICS_DATA);
|
||||||
|
|
||||||
|
expect(postWebAnalyticEvent).toHaveBeenCalledWith(CUSTOM_EVENT_PAYLOAD);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -17,8 +17,12 @@ import {
|
|||||||
setSession,
|
setSession,
|
||||||
} from '@analytics/session-utils';
|
} from '@analytics/session-utils';
|
||||||
import Analytics, { AnalyticsInstance } from 'analytics';
|
import Analytics, { AnalyticsInstance } from 'analytics';
|
||||||
import { WebPageData } from 'components/WebAnalytics/WebAnalytics.interface';
|
import { AnalyticsData } from 'components/WebAnalytics/WebAnalytics.interface';
|
||||||
import { postPageView } from 'rest/WebAnalyticsAPI';
|
import {
|
||||||
|
CustomEvent,
|
||||||
|
CustomEventTypes,
|
||||||
|
} from 'generated/analytics/webAnalyticEventType/customEvent';
|
||||||
|
import { postWebAnalyticEvent } from 'rest/WebAnalyticsAPI';
|
||||||
import {
|
import {
|
||||||
WebAnalyticEventData,
|
WebAnalyticEventData,
|
||||||
WebAnalyticEventType,
|
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.
|
* track the page view if user id is available.
|
||||||
* @param pageData PageData
|
* @param pageData PageData
|
||||||
* @param userId string
|
* @param userId string
|
||||||
*/
|
*/
|
||||||
export const trackPageView = async (pageData: WebPageData, userId: string) => {
|
export const trackPageView = (pageData: AnalyticsData, userId: string) => {
|
||||||
// Get the current session
|
// Get the current session
|
||||||
const currentSession = getSession();
|
const currentSession = getSession();
|
||||||
|
|
||||||
@ -100,22 +122,40 @@ export const trackPageView = async (pageData: WebPageData, userId: string) => {
|
|||||||
timestamp,
|
timestamp,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
handlePostAnalytic(webAnalyticEventData);
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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
|
* @param userId string
|
||||||
@ -127,9 +167,12 @@ export const getAnalyticInstance = (userId: string): AnalyticsInstance => {
|
|||||||
plugins: [
|
plugins: [
|
||||||
{
|
{
|
||||||
name: 'OM-Plugin',
|
name: 'OM-Plugin',
|
||||||
page: (pageData: WebPageData) => {
|
page: (pageData: AnalyticsData) => {
|
||||||
trackPageView(pageData, userId);
|
trackPageView(pageData, userId);
|
||||||
},
|
},
|
||||||
|
track: (trackingData: AnalyticsData) => {
|
||||||
|
trackCustomEvent(trackingData);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user