Skip to content

Commit

Permalink
feat: SKFP-734 add GA4 metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
aperron-ferlab committed Feb 14, 2024
1 parent 2f103b8 commit 25ae4b9
Show file tree
Hide file tree
Showing 18 changed files with 242 additions and 31 deletions.
6 changes: 5 additions & 1 deletion src/components/Cavatica/AnalyzeButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { BooleanOperators } from '@ferlab/ui/core/data/sqon/operators';
import { ISqonGroupFilter } from '@ferlab/ui/core/data/sqon/types';
import { CAVATICA_FILE_BATCH_SIZE } from 'views/DataExploration/utils/constant';

import { trackCavaticaAction } from 'services/analytics';
import { CavaticaApi } from 'services/api/cavatica';
import { ICavaticaCreateProjectBody } from 'services/api/cavatica/models';
import { fetchAllFencesAuthentificationStatus } from 'store/fences/thunks';
Expand All @@ -27,13 +28,15 @@ interface OwnProps {
sqon?: ISqonGroupFilter;
type?: 'default' | 'primary';
disabled?: boolean;
index: string;
}

const CavaticaAnalyzeButton: React.FC<OwnProps> = ({
fileIds,
sqon,
type = 'default',
disabled = false,
index,
}) => {
const dispatch = useDispatch();
const cavatica = useCavaticaPassport();
Expand All @@ -54,7 +57,7 @@ const CavaticaAnalyzeButton: React.FC<OwnProps> = ({

useEffect(() => {
dispatch(fetchAllFencesAuthentificationStatus());
}, []);
}, [dispatch]);

// If the user is not connected to cavatica
useEffect(() => {
Expand Down Expand Up @@ -93,6 +96,7 @@ const CavaticaAnalyzeButton: React.FC<OwnProps> = ({
);
}}
handleImportBulkData={(value) => {
trackCavaticaAction(index);
dispatch(startBulkImportJob(value));
}}
dictionary={{
Expand Down
3 changes: 3 additions & 0 deletions src/components/Layout/Header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import LineStyleIcon from 'components/Icons/LineStyleIcon';
import HeaderLink from 'components/Layout/Header/HeaderLink';
import styles from 'components/Layout/Header/index.module.scss';
import GradientAccent from 'components/uiKit/GradientAccent';
import { trackLogout, trackVisitResources } from 'services/analytics';
import { usePersona } from 'store/persona';
import { personaActions } from 'store/persona/slice';
import { userActions } from 'store/user/slice';
Expand Down Expand Up @@ -117,6 +118,7 @@ const Header = () => {
trigger={['click']}
overlay={
<Menu
onClick={({ key }) => trackVisitResources(key)}
items={[
{
key: 'website',
Expand Down Expand Up @@ -234,6 +236,7 @@ const Header = () => {
</Space>
),
onClick: () => {
trackLogout();
dispatch(personaActions.cleanLogout());
dispatch(userActions.cleanLogout());
window.sessionStorage.clear();
Expand Down
3 changes: 3 additions & 0 deletions src/components/uiKit/FilterList/CustomFilterContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { IExtendedMappingResults, IGqlResults } from '@ferlab/ui/core/graphql/ty
import { getFilters } from 'graphql/utils/Filters';
import { isUndefined } from 'lodash';

import { trackFacetSearch } from 'services/analytics';
import { getFacetsDictionary, getFiltersDictionary } from 'utils/translation';

import CustomFilterSelector from './CustomFilterSelector';
Expand Down Expand Up @@ -58,6 +59,8 @@ const CustomFilterContainer = ({
}, [filtersOpen]);

const onChange = (fg: IFilterGroup, f: IFilter[]) => {
trackFacetSearch(index, fg.field);

updateActiveQueryFilters({
queryBuilderId,
filterGroup: fg,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React, { useCallback, useEffect, useState } from 'react';
import Empty from '@ferlab/ui/core/components/Empty';
import { Select, Tag } from 'antd';
import debounce from 'lodash/debounce';
import take from 'lodash/take';
import Empty from '@ferlab/ui/core/components/Empty';

import SearchLabel from 'components/uiKit/search/SearchLabel';

export type OptionsType = {
Expand Down
8 changes: 5 additions & 3 deletions src/components/uiKit/search/GlobalSearch/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { get } from 'lodash';

import Search, { TCustomHandleSearch } from 'components/uiKit/search/GlobalSearch/Search';
import { OptionsType } from 'components/uiKit/search/GlobalSearch/Search/SearchAutocomplete';
import { trackFacetSearch } from 'services/analytics';

export interface ICustomSearchProps {
queryBuilderId: string;
Expand Down Expand Up @@ -42,15 +43,16 @@ const GlobalSearch = <T,>({
tooltipText,
}: OwnProps<T>) => (
<Search<T>
onSelect={(values) =>
onSelect={(values) => {
trackFacetSearch(index, field);
updateActiveQueryField({
queryBuilderId,
field,
value: values,
index,
merge_strategy: MERGE_VALUES_STRATEGIES.OVERRIDE_VALUES,
})
}
});
}}
index={index}
tooltipText={tooltipText}
emptyDescription={emptyDescription}
Expand Down
23 changes: 11 additions & 12 deletions src/provider/KeycloakProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
import React, { ReactElement } from "react";
import {
AuthClientError,
AuthClientEvent,
} from "@react-keycloak/core/"
import EnvVariables from "helpers/EnvVariables";
import { ReactKeycloakProvider as KeycloakProvider } from "@react-keycloak/web";
import keycloak from "auth/keycloak-api/keycloak";
import { trackAuthSuccess } from "services/analytics";
import React, { ReactElement } from 'react';
import { AuthClientError, AuthClientEvent } from '@react-keycloak/core/';
import { ReactKeycloakProvider as KeycloakProvider } from '@react-keycloak/web';
import keycloak from 'auth/keycloak-api/keycloak';
import EnvVariables from 'helpers/EnvVariables';

import { trackAuthError, trackAuthSuccess } from 'services/analytics';

export interface IProvider {
children: React.ReactNode;
}

const eventLogger = (eventType: AuthClientEvent, error?: AuthClientError) => {
if (EnvVariables.configFor("ENV") === "development" && error) {
console.error("eventLogger ", "eventType ", eventType);
console.error("eventLogger ", error);
if (EnvVariables.configFor('ENV') === 'development' && error) {
trackAuthError();
console.error('eventLogger ', 'eventType ', eventType);
console.error('eventLogger ', error);
}

if (eventType === 'onAuthSuccess') {
Expand Down
151 changes: 150 additions & 1 deletion src/services/analytics.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import EnvironmentVariables from 'helpers/EnvVariables';
import ReactGA from 'react-ga4';
import EnvironmentVariables from 'helpers/EnvVariables';
import { capitalize } from 'lodash';
import { FilterActionType } from 'views/DataExploration/components/PageContent';
import { SetActionType } from 'views/DataExploration/components/SetsManagementDropdown';

import { SetType } from 'services/api/savedSet/models';

const measurementId = EnvironmentVariables.configFor('MEASUREMENT_ID');
const isDev = EnvironmentVariables.configFor('ENV') === 'development';
Expand All @@ -19,3 +24,147 @@ export const trackAuthSuccess = () => {
});
}
};

export const trackAuthError = () => {
if (isGaActive) {
ReactGA.event({
category: 'Authentication',
action: 'login failed',
});
}
};

export const trackLogout = () => {
if (isGaActive) {
ReactGA.event({
category: 'Authentication',
action: 'logout',
});
}
};

export const trackFacetSearch = (page: string, field: string) => {
if (isGaActive) {
ReactGA.event({
category: 'FacetSearch',
action: `${capitalize(page)} -- ${field}`,
});
}
};

export const trackReportDownload = (reportCategory: string) => {
if (isGaActive) {
ReactGA.event({
category: 'Reports',
action: `report download -- ${reportCategory}`,
});
}
};

/**
* DONE needs to be tested
**/
export const trackCavaticaAction = (page: string) => {
if (isGaActive) {
ReactGA.event({
category: 'Cavatica',
action: `Analyze -- ${capitalize(page)}`,
});
}
};

export const trackSetActions = (action: string, setType: SetType) => {
let message = '';
switch (action) {
case SetActionType.CREATE_SET:
message = 'Creating new Set';
break;
case SetActionType.UPDATE_SET:
message = 'Updating existing Set';
break;
case SetActionType.REMOVE_SET:
message = 'Removing existing Set';
break;
case SetActionType.ADD_IDS:
message = 'Adding file(s) to existing Set';
break;
case SetActionType.REMOVE_IDS:
message = 'Removing file(s) from existing Set';
break;
default:
message = 'Unknown Action';
break;
}
if (isGaActive) {
ReactGA.event({
category: 'Sets',
action: `${setType} - ${message}`,
});
}
};

export const trackFilterActions = (action: string, tabId: string) => {
let message = '';
switch (action) {
case FilterActionType.CREATE_FILTER:
message = 'Creating new Filter';
break;
case FilterActionType.UPDATE_FILER:
message = 'Updating existing Filter';
break;
case FilterActionType.REMOVE_FILTER:
message = 'Removing existing Filter';
break;
case FilterActionType.FAVORITE_FILTER:
message = 'Adding Filter to favorites';
break;
case FilterActionType.SHARE_FILTER:
message = 'Sharing Filter';
break;
default:
message = 'Unknown Action';
break;
}
if (isGaActive) {
ReactGA.event({
category: 'Filters',
action: `${tabId} - ${message}`,
});
}
};

export const trackVisitResources = (resource: string) => {
if (isGaActive) {
ReactGA.event({
category: 'Resources',
action: `Visit -- ${resource}`,
});
}
};

export const trackRegistrationStarted = () => {
if (isGaActive) {
ReactGA.event({
category: 'Registration',
action: 'Registration started',
});
}
};

export const trackNCIConnection = (connected: boolean) => {
if (isGaActive) {
ReactGA.event({
category: 'FencesConnections',
action: `${connected ? 'Connected to' : 'Disconnected from'} NCI CRDC Framework Services`,
});
}
};

export const trackKFConnection = (connected: boolean) => {
if (isGaActive) {
ReactGA.event({
category: 'FencesConnections',
action: `${connected ? 'Connected to' : 'Disconnected from'} Kids First Framework Services`,
});
}
};
4 changes: 3 additions & 1 deletion src/services/api/reports/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import EnvironmentVariables from 'helpers/EnvVariables';
import isEmpty from 'lodash/isEmpty';

import downloader from 'common/downloader';
import { trackReportDownload } from 'services/analytics';

import { ReportConfig, ReportType } from './models';

Expand Down Expand Up @@ -42,7 +43,8 @@ export const headers = () => ({
});

const generateReport = (config: ReportConfig) => {
//TODO do we need google analytics tracking?
trackReportDownload(config.name);

let reportSqon;

if (!config.sqon || isEmpty(config.sqon)) {
Expand Down
7 changes: 6 additions & 1 deletion src/store/report/thunks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { startCase } from 'lodash';
import { v4 } from 'uuid';

import { getDefaultContentType } from 'common/downloader';
import { trackReportDownload } from 'services/analytics';
import { ArrangerApi } from 'services/api/arranger';
import { ArrangerColumnStateResults } from 'services/api/arranger/models';
import { ReportApi } from 'services/api/reports';
Expand Down Expand Up @@ -61,7 +62,7 @@ const fetchReport = createAsyncThunk<
duration: 0,
}),
);
await ReportApi.generateReport(args.data).then((_) => {
await ReportApi.generateReport(args.data).then(() => {
thunkAPI.dispatch(globalActions.destroyMessages([messageKey]));
thunkAPI.dispatch(
globalActions.displayNotification({
Expand All @@ -82,6 +83,8 @@ const fetchTsvReport = createAsyncThunk<void, TFetchTSVArgs, { rejectValue: stri
async (args, thunkAPI) => {
const messageKey = 'report_pending';

trackReportDownload(`${args.index}Tsv`);

thunkAPI.dispatch(
globalActions.displayMessage({
type: 'loading',
Expand Down Expand Up @@ -150,6 +153,8 @@ const generateLocalTsvReport = createAsyncThunk<
// !! This function assumes that it is called only when the table is not empty. Said otherwise, data is never empty !!
const messageKey = 'report_pending';

trackReportDownload(`${args.index}-${args.fileName}-tsv`);

try {
const formattedDate = format(new Date(), 'yyyy-MM-dd');
const formattedFileName = `kidsfirst-${args.fileName ?? args.index}-table-${formattedDate}.tsv`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { DATA_EXPLORATION_QB_ID } from 'views/DataExploration/utils/constant';
import { FENCE_NAMES } from 'common/fenceTypes';
import KidsFirstLoginIcon from 'components/Icons/KidsFirstLoginIcon';
import NciIcon from 'components/Icons/NciIcon';
import { trackKFConnection, trackNCIConnection } from 'services/analytics';
import { useFenceAuthentification, useFencesAuthorizedStudies } from 'store/fences';
import {
fenceDisconnection,
Expand All @@ -34,9 +35,11 @@ const AuthorizedStudies = ({ id, className = '' }: DashboardCardProps) => {
name: 'Kids First Framework Services',
icon: <KidsFirstLoginIcon width={45} height={45} />,
onConnectToFence: () => {
trackKFConnection(true);
dispatch(fenceOpenAuhentificationTab(FENCE_NAMES.gen3));
},
onDisconnectFromFence: () => {
trackKFConnection(false);
dispatch(fenceDisconnection(FENCE_NAMES.gen3));
},
},
Expand All @@ -45,9 +48,11 @@ const AuthorizedStudies = ({ id, className = '' }: DashboardCardProps) => {
name: 'NCI CRDC Framework Services',
icon: <NciIcon width={45} height={45} />,
onConnectToFence: () => {
trackNCIConnection(true);
dispatch(fenceOpenAuhentificationTab(FENCE_NAMES.dcf));
},
onDisconnectFromFence: () => {
trackNCIConnection(false);
dispatch(fenceDisconnection(FENCE_NAMES.dcf));
},
},
Expand Down
Loading

0 comments on commit 25ae4b9

Please sign in to comment.