Feat (#8159)Implement Web Analytics logic from frontend (#8181)

* Feat  (#8159)Implement Web Analytics logic from frontend

* Add login to prepare the event data and make log it

* Add Unit Test

* Move comment to the top

* Addressing comment

* Addressing review comments

* Mock app state

* Fix import issue

* Addressing comments

* Fix unit test
This commit is contained in:
Sachin Chaurasiya 2022-10-19 14:02:20 +05:30 committed by GitHub
parent 26efe40e87
commit 05ee89fdd6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 363 additions and 12 deletions

View File

@ -27,6 +27,7 @@
"@okta/okta-react": "^6.4.3",
"@rjsf/core": "^4.1.1",
"@toast-ui/react-editor": "^3.1.8",
"analytics": "^0.8.1",
"antd": "^4.20.6",
"antlr4": "4.9.2",
"autoprefixer": "^9.8.6",
@ -93,7 +94,8 @@
"tailwindcss": "^2.1.4",
"to-arraybuffer": "^1.0.1",
"turndown": "^7.1.1",
"url": "^0.11.0"
"url": "^0.11.0",
"use-analytics": "^0.0.5"
},
"scripts": {
"start": "NODE_ENV=development BABEL_ENV=development webpack serve --config ./webpack.config.dev.js --env development",
@ -164,6 +166,7 @@
"@types/showdown": "^2.0.0",
"@types/testing-library__jest-dom": "^5.9.5",
"@types/turndown": "^5.0.1",
"@types/use-analytics": "^0.0.0",
"@typescript-eslint/eslint-plugin": "^2.10.0",
"@typescript-eslint/parser": "^2.10.0",
"babel-eslint": "^10.1.0",

View File

@ -33,6 +33,7 @@ import Appbar from './components/app-bar/Appbar';
import GlobalSearchProvider from './components/GlobalSearchProvider/GlobalSearchProvider';
import PermissionProvider from './components/PermissionProvider/PermissionProvider';
import WebSocketProvider from './components/web-scoket/web-scoket.provider';
import WebAnalyticsProvider from './components/WebAnalytics/WebAnalyticsProvider';
import { toastOptions } from './constants/toast.constants';
import ErrorBoundry from './ErrorBoundry/ErrorBoundry';
import AppRouter from './router/AppRouter';
@ -57,14 +58,16 @@ const App: FunctionComponent = () => {
<Router>
<ErrorBoundry>
<AuthProvider childComponentType={AppRouter}>
<PermissionProvider>
<WebSocketProvider>
<GlobalSearchProvider>
<Appbar />
<AppRouter />
</GlobalSearchProvider>
</WebSocketProvider>
</PermissionProvider>
<WebAnalyticsProvider>
<PermissionProvider>
<WebSocketProvider>
<GlobalSearchProvider>
<Appbar />
<AppRouter />
</GlobalSearchProvider>
</WebSocketProvider>
</PermissionProvider>
</WebAnalyticsProvider>
</AuthProvider>
</ErrorBoundry>
</Router>

View File

@ -0,0 +1,40 @@
/*
* Copyright 2021 Collate
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { PageData } from 'analytics';
export interface PayloadProperties {
title: string;
url: string;
path: string;
hash: string;
search: string;
width: number;
height: number;
referrer: string;
}
export interface Payload {
type: string;
properties: PayloadProperties;
anonymousId: string;
meta: {
rid: string;
ts: number;
hasCallback: boolean;
};
}
export interface WebPageData extends PageData {
payload: Payload;
}

View File

@ -0,0 +1,56 @@
/*
* Copyright 2021 Collate
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { act, render, screen } from '@testing-library/react';
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import WebAnalyticsProvider from './WebAnalyticsProvider';
jest.mock('../../AppState', () => {
const mockUser = {
id: '011bdb24-90a7-4a97-ba66-24002adb2b12',
type: 'user',
name: 'aaron_johnson0',
fullyQualifiedName: 'aaron_johnson0',
displayName: 'Aaron Johnson',
deleted: false,
isAdmin: true,
href: 'http://localhost:8585/api/v1/users/011bdb24-90a7-4a97-ba66-24002adb2b12',
teams: [{ id: '8754b53f-15cd-4d9a-af52-bdb3a2abffss' }],
};
return {
getCurrentUserDetails: jest.fn().mockReturnValue(mockUser),
userDetails: undefined,
nonSecureUserDetails: mockUser,
};
});
describe('Test WebAnalytics Component', () => {
it('Should render the child component', async () => {
await act(async () => {
render(
<WebAnalyticsProvider>
<div data-testid="mock-children">Mock Children</div>
</WebAnalyticsProvider>,
{
wrapper: MemoryRouter,
}
);
});
const children = await screen.findByTestId('mock-children');
expect(children).toBeInTheDocument();
});
});

View File

@ -0,0 +1,38 @@
/*
* Copyright 2021 Collate
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { observer } from 'mobx-react';
import React, { ReactNode, useMemo } from 'react';
import { AnalyticsProvider } from 'use-analytics';
import AppState from '../../AppState';
import { getAnalyticInstance } from '../../utils/WebAnalyticsUtils';
interface WebAnalyticsProps {
children: ReactNode;
}
const WebAnalyticsProvider = ({ children }: WebAnalyticsProps) => {
// get current user details
const currentUser = useMemo(
() => AppState.getCurrentUserDetails(),
[AppState.userDetails, AppState.nonSecureUserDetails]
);
return (
<AnalyticsProvider instance={getAnalyticInstance(currentUser?.id ?? '')}>
{children}
</AnalyticsProvider>
);
};
export default observer(WebAnalyticsProvider);

View File

@ -14,7 +14,8 @@
import { AxiosError } from 'axios';
import { SlackChatConfig } from 'Models';
import React, { useEffect, useState } from 'react';
import { Redirect, Route, Switch } from 'react-router-dom';
import { Redirect, Route, Switch, useLocation } from 'react-router-dom';
import { useAnalytics } from 'use-analytics';
import { useAuthContext } from '../authentication/auth-provider/AuthProvider';
import { fetchSlackConfig } from '../axiosAPIs/miscAPI';
import Loader from '../components/Loader/Loader';
@ -47,6 +48,11 @@ const BasicSignupPage = withSuspenseFallback(
);
const AppRouter = () => {
const location = useLocation();
// web analytics instance
const analytics = useAnalytics();
const {
authConfig,
isAuthDisabled,
@ -95,6 +101,11 @@ const AppRouter = () => {
<SlackChat slackConfig={slackConfig} />
) : null;
useEffect(() => {
// track page view on route change
analytics.page();
}, [location]);
return loading ? (
<Loader />
) : (

View File

@ -0,0 +1,30 @@
/*
* Copyright 2021 Collate
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { getAnalyticInstance } from './WebAnalyticsUtils';
const userId = 'userId';
describe('Web Analytics utils', () => {
it('getAnalyticInstance Should return the analytic instance and must have plugins, storage, page and setAnonymousId property', () => {
const instance = getAnalyticInstance(userId);
expect(instance).toHaveProperty('plugins');
expect(instance).toHaveProperty('storage');
expect(instance).toHaveProperty('page');
expect(instance).toHaveProperty('setAnonymousId');
});
});

View File

@ -0,0 +1,79 @@
/*
* Copyright 2021 Collate
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Analytics, { AnalyticsInstance } from 'analytics';
import { WebPageData } from '../components/WebAnalytics/WebAnalytics.interface';
/**
* track the page view if user id is available.
* @param _pageData PageData
* @param userId string
*/
export const trackPageView = (pageData: WebPageData, userId: string) => {
const { location, navigator } = window;
const { hostname, pathname } = location;
// store the current path
let currentPathRef = pathname;
if (userId) {
const { payload } = pageData;
const { anonymousId, properties } = payload;
// get the previous page url
const previousURL = new URL(properties.referrer);
/**
* Check if the previous path and current path is not matching
* then only collect the data
*/
if (currentPathRef !== previousURL.pathname) {
currentPathRef = properties.path;
const eventData = {
fullUrl: properties.url,
url: properties.path,
hostname,
language: navigator.language,
screenSize: `${properties.width}x${properties.height}`,
userId,
sessionId: anonymousId,
referrer: properties.referrer,
};
// TODO: make an api call to collect the data once backend API is ready and remove the console log
// eslint-disable-next-line
console.log(eventData);
}
}
};
/**
*
* @param userId string
* @returns AnalyticsInstance
*/
export const getAnalyticInstance = (userId: string): AnalyticsInstance => {
return Analytics({
app: 'OpenMetadata',
plugins: [
{
name: 'OM-Plugin',
page: (pageData: WebPageData) => {
trackPageView(pageData, userId);
},
},
],
});
};

View File

@ -10,6 +10,59 @@
"@jridgewell/gen-mapping" "^0.1.0"
"@jridgewell/trace-mapping" "^0.3.9"
"@analytics/cookie-utils@^0.2.10":
version "0.2.10"
resolved "https://registry.yarnpkg.com/@analytics/cookie-utils/-/cookie-utils-0.2.10.tgz#94d1ad0766c58c4a294e5ca54f59b0a69bd574b4"
integrity sha512-k7oyW1PkMCnEdCYmHCLLXAsKavQh5ZRh5EeugBEUTlPJs8x8ajF+W1T0PYVDS26Impkea1sJR2etv/6l2pKtlg==
dependencies:
"@analytics/global-storage-utils" "^0.1.5"
"@analytics/core@^0.11.1":
version "0.11.1"
resolved "https://registry.yarnpkg.com/@analytics/core/-/core-0.11.1.tgz#3d26858aeaf77b80810e269a4ecfd70173032501"
integrity sha512-vHuiMpeGV6jGRRoOuScUJ4CHpVd1Zu9M+fjEWgN+boMAxIwFCtRTkSbhhIP337JuEBLpMT8w37OxA4Yh3NZFZw==
dependencies:
"@analytics/global-storage-utils" "^0.1.5"
"@analytics/type-utils" "^0.6.0"
analytics-utils "^1.0.10"
"@analytics/global-storage-utils@^0.1.5":
version "0.1.5"
resolved "https://registry.yarnpkg.com/@analytics/global-storage-utils/-/global-storage-utils-0.1.5.tgz#54cc14f8d678610b5fca1e98215fe29fff662f31"
integrity sha512-DzOCd0qK7PGnz/dgBYNzLmaFBkxcEHM6/CpWMY7hel1IBUldMsyet4sdRgI2Nk+Wc6B/YvqVqos68p5ntB59zA==
dependencies:
"@analytics/type-utils" "^0.6.0"
"@analytics/localstorage-utils@^0.1.8":
version "0.1.8"
resolved "https://registry.yarnpkg.com/@analytics/localstorage-utils/-/localstorage-utils-0.1.8.tgz#eab6a9690ccd3c05d131e3a9788b4443affdb800"
integrity sha512-tU5rLEHdYRc4EmRXkWacMJSmu4SeJfQuxvthSHHRVx3hGBQ00MJRDGyDHMXcrLKQXrBpsJo6t/8YRavHLOxDPQ==
dependencies:
"@analytics/global-storage-utils" "^0.1.5"
"@analytics/session-storage-utils@^0.0.5":
version "0.0.5"
resolved "https://registry.yarnpkg.com/@analytics/session-storage-utils/-/session-storage-utils-0.0.5.tgz#f0501ef73d1b94a34e94057056cadaeee4f65fdf"
integrity sha512-BXRNQ73GSkkkny//a/SNCYRyrIhEJBwOTcdNkr2gq6ghXgekQGrTfVzwybZ/tAEHA0XYkkaOszelRsDPxa+UOQ==
dependencies:
"@analytics/global-storage-utils" "^0.1.5"
"@analytics/storage-utils@^0.4.0":
version "0.4.0"
resolved "https://registry.yarnpkg.com/@analytics/storage-utils/-/storage-utils-0.4.0.tgz#12e5c0683f0124e189650a4989a627f3318d7995"
integrity sha512-RY9nhoSQcuQxkmVUZBG3ULOwiFxi+H5N0WHn+FPUScnoM4b+tC48dWzyFxD9qKyWPah8/ndjCj6r+6wTGSl1zw==
dependencies:
"@analytics/cookie-utils" "^0.2.10"
"@analytics/global-storage-utils" "^0.1.5"
"@analytics/localstorage-utils" "^0.1.8"
"@analytics/session-storage-utils" "^0.0.5"
"@analytics/type-utils" "^0.6.0"
"@analytics/type-utils@^0.6.0":
version "0.6.0"
resolved "https://registry.yarnpkg.com/@analytics/type-utils/-/type-utils-0.6.0.tgz#1b8745516da5a654d292ed3f2db893c61cd3d9c5"
integrity sha512-1Yw7u/COtxx06BfwlI+kVhsa/upKYzmCNrT4c8QDeCY2KMYlnijkUjtHiPU08HxyTIVB5j6d75O0YWVIHwQS8g==
"@ant-design/colors@^6.0.0":
version "6.0.0"
resolved "https://registry.yarnpkg.com/@ant-design/colors/-/colors-6.0.0.tgz#9b9366257cffcc47db42b9d0203bb592c13c0298"
@ -3872,6 +3925,14 @@
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d"
integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==
"@types/use-analytics@^0.0.0":
version "0.0.0"
resolved "https://registry.yarnpkg.com/@types/use-analytics/-/use-analytics-0.0.0.tgz#72b3787fb73fa938ae1c24509d549e6db1a78f16"
integrity sha512-skUZqBkik1v6cb4ILzxnfnDMAx6PdRrRszFZ/OtvOub7sW02W7WG8yf2CurlGo0wpHVeGJ1gkXarHpq2BJ1skg==
dependencies:
"@types/react" "*"
analytics "^0.8.1"
"@types/webpack-sources@*":
version "3.2.0"
resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-3.2.0.tgz#16d759ba096c289034b26553d2df1bf45248d38b"
@ -4310,6 +4371,22 @@ amdefine@>=0.0.4:
resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5"
integrity sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==
analytics-utils@^1.0.10:
version "1.0.10"
resolved "https://registry.yarnpkg.com/analytics-utils/-/analytics-utils-1.0.10.tgz#e44078176c67005e5b11aae1dc538202744a5ca5"
integrity sha512-ZKYKhip7Sf09qE85l4vZMBPR3fz6ISUTlwzkWSwJHbzLLzP5qrWQtcQlBcP9Pah7BMNSq8pqho+PX4ZKB014Yg==
dependencies:
"@analytics/type-utils" "^0.6.0"
dlv "^1.1.3"
analytics@^0.8.1:
version "0.8.1"
resolved "https://registry.yarnpkg.com/analytics/-/analytics-0.8.1.tgz#e8ed0d5e729e666c1ab64dc5bdaa5f8231f05a1b"
integrity sha512-mXOe8zTGDfiYqw9MZsgul8HrOBmHsIwk/0xbrkGZr75yvWqAcyKfZA0WjOalwI9tzIKv8WNfHV5yhnrtQcXJpw==
dependencies:
"@analytics/core" "^0.11.1"
"@analytics/storage-utils" "^0.4.0"
ansi-colors@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
@ -7980,7 +8057,7 @@ history@^4.9.0:
tiny-warning "^1.0.0"
value-equal "^1.0.1"
hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.1:
hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
@ -10381,7 +10458,7 @@ min-indent@^1.0.0:
resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
mini-create-react-context@^0.4.0:
mini-create-react-context@^0.4.0, mini-create-react-context@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz#072171561bfdc922da08a60c2197a497cc2d1d5e"
integrity sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==
@ -14134,6 +14211,11 @@ tiny-invariant@^1.0.2:
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875"
integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==
tiny-invariant@^1.1.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642"
integrity sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==
tiny-warning@^1.0.0, tiny-warning@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
@ -14613,6 +14695,15 @@ use-sync-external-store@1.2.0:
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
use-analytics@^0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/use-analytics/-/use-analytics-0.0.5.tgz#5f0f2750c0f3393b8cd1f86a7aecbf02dae415ca"
integrity sha512-IM+H/ujkJK9cFqg/8O8uyFIRtU/fASYo35USyVNN/T6uswls65f8ZyexFfsAbg4jdqHS5xDyPiHICVxCZtfUpA==
dependencies:
hoist-non-react-statics "^3.3.2"
mini-create-react-context "^0.4.1"
tiny-invariant "^1.1.0"
use@^3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"