Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Log more analytics #466

Merged
merged 17 commits into from
Jul 19, 2024
1 change: 1 addition & 0 deletions __mocks__/Analytics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const logEvent = jest.fn();
10 changes: 6 additions & 4 deletions redux/GamesSlice.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import analytics from '@react-native-firebase/analytics';
import { PayloadAction, createAsyncThunk, createEntityAdapter, createSelector, createSlice } from '@reduxjs/toolkit';
import { getContrastRatio } from 'colorsheet';
import * as Crypto from 'expo-crypto';

import { logEvent } from '../src/Analytics';
import { getPalette } from '../src/ColorPalette';
import { SortDirectionKey, SortSelectorKey } from '../src/components/ScoreLog/SortHelper';
import logger from '../src/Logger';

import { playerAdd, selectPlayerById, updatePlayer } from './PlayersSlice';
import { setCurrentGameId } from './SettingsSlice';
import { incrementRollingGameCounter, setCurrentGameId } from './SettingsSlice';
import { RootState } from './store';

export interface GameState {
Expand Down Expand Up @@ -153,7 +153,7 @@ export const asyncRematchGame = createAsyncThunk(

dispatch(setCurrentGameId(newGameId));

await analytics().logEvent('rematch_game', {
await logEvent('rematch_game', {
gameId: game.id,
});

Expand Down Expand Up @@ -225,9 +225,11 @@ export const asyncCreateGame = createAsyncThunk(
}));

dispatch(setCurrentGameId(newGameId));
dispatch(incrementRollingGameCounter());

await analytics().logEvent('new_game', {
await logEvent('new_game', {
index: gameCount,
player_count: playerCount,
});

return newGameId;
Expand Down
14 changes: 13 additions & 1 deletion redux/SettingsSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export interface SettingsState {
lastStoreReviewPrompt: number;
devMenuEnabled?: boolean;
appOpens: number;
installId: string | undefined;
rollingGameCounter?: number;
};

const initialState: SettingsState = {
Expand All @@ -34,6 +36,8 @@ const initialState: SettingsState = {
interactionType: InteractionType.SwipeVertical,
lastStoreReviewPrompt: 0,
appOpens: 0,
installId: undefined,
rollingGameCounter: 0,
};

const settingsSlice = createSlice({
Expand Down Expand Up @@ -81,7 +85,13 @@ const settingsSlice = createSlice({
},
increaseAppOpens(state) {
state.appOpens += 1;
}
},
setInstallId(state, action: PayloadAction<string>) {
state.installId = action.payload;
},
incrementRollingGameCounter(state) {
state.rollingGameCounter = (state.rollingGameCounter ?? 0) + 1;
},
}
});

Expand All @@ -99,6 +109,8 @@ export const {
setLastStoreReviewPrompt,
toggleDevMenuEnabled,
increaseAppOpens,
setInstallId,
incrementRollingGameCounter,
} = settingsSlice.actions;

export default settingsSlice.reducer;
2 changes: 2 additions & 0 deletions redux/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ const settingsPersistConfig = {
'lastStoreReviewPrompt',
'devMenuEnabled',
'appOpens',
'installId',
'rollingGameCounter',
],
};

Expand Down
23 changes: 23 additions & 0 deletions src/Analytics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import analytics from '@react-native-firebase/analytics';

import logger from './Logger';

/**
* Logs an event to Firebase Analytics with additional logging.
* @param eventName The name of the event to log.
* @param params The parameters of the event.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const logEvent = async (eventName: string, params?: Record<string, any>) => {
// Additional logging logic here (e.g., console.log for debugging)
logger.info(
'\x1b[34m', // Set the color to blue
'EVENT',
eventName,
JSON.stringify(params, null, 2),
'\x1b[0m' // Reset the color
);

// Log the event to Firebase Analytics
await analytics().logEvent(eventName, params);
};
11 changes: 8 additions & 3 deletions src/components/AppInfo/RotatingIcon.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react';

import analytics from '@react-native-firebase/analytics';
import * as Haptics from 'expo-haptics';
import { Image } from 'expo-image';
import { TouchableWithoutFeedback } from 'react-native';
Expand All @@ -11,12 +10,15 @@ import Animated, {
withTiming
} from 'react-native-reanimated';

import { useAppDispatch } from '../../../redux/hooks';
import { useAppDispatch, useAppSelector } from '../../../redux/hooks';
import { toggleDevMenuEnabled } from '../../../redux/SettingsSlice';
import { logEvent } from '../../Analytics';

const RotatingIcon: React.FunctionComponent = ({ }) => {
const dispatch = useAppDispatch();

const installId = useAppSelector(state => state.settings.installId);

const rotation = useSharedValue(0);
const rotationCount = useSharedValue(1);
const animatedStyles = useAnimatedStyle(() => {
Expand All @@ -33,6 +35,9 @@ const RotatingIcon: React.FunctionComponent = ({ }) => {
holdCallback = setTimeout(() => {
dispatch(toggleDevMenuEnabled());
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy);
logEvent('dev_menu', {
installId,
});
}, 5000);
};

Expand All @@ -48,7 +53,7 @@ const RotatingIcon: React.FunctionComponent = ({ }) => {
rotationCount.value = rotationCount.value + 1;
rotation.value = withTiming((rotationCount.value * 90), { duration: 1000, easing: Easing.elastic(1) });

await analytics().logEvent('app_icon');
await logEvent('app_icon');
}}>
<Animated.View style={[animatedStyles]}>
<Image source={require('../../../assets/icon.png')}
Expand Down
6 changes: 6 additions & 0 deletions src/components/Buttons/AddendButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React from 'react';
import { StyleSheet, Text, TouchableHighlight, View } from 'react-native';

import { useAppSelector } from '../../../redux/hooks';
import { logEvent } from '../../Analytics';
import { systemBlue } from '../../constants';
import { InteractionType } from '../Interactions/InteractionType';
import { useAddendModalContext } from '../Sheets/AddendModalContext';
Expand All @@ -15,6 +16,7 @@ const AddendButton: React.FunctionComponent = ({ }) => {
const addendOne = useAppSelector(state => state.settings.addendOne);
const addendTwo = useAppSelector(state => state.settings.addendTwo);
const interactionType = useAppSelector(state => state.settings.interactionType);
const installId = useAppSelector(state => state.settings.installId);

const adddendModalRef = useAddendModalContext();
const gameSheetRef = useGameSheetContext();
Expand All @@ -26,6 +28,10 @@ const AddendButton: React.FunctionComponent = ({ }) => {

gameSheetRef?.current?.snapToIndex(0);
adddendModalRef.current?.present();

logEvent('addend_sheet', {
installId
});
};

const gestureIcons: { [key: string]: React.FunctionComponent; } = {
Expand Down
8 changes: 5 additions & 3 deletions src/components/Buttons/AppInfoButton.test.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import analytics from '@react-native-firebase/analytics';
import { render, fireEvent, waitFor } from '@testing-library/react-native';
import { fireEvent, render, waitFor } from '@testing-library/react-native';

import { useNavigationMock } from '../../../test/test-helpers';
import { logEvent } from '../../Analytics';

import AppInfoButton from './AppInfoButton';

jest.mock('../../Analytics');

describe('AppInfoButton', () => {
const navigation = useNavigationMock();

Expand All @@ -22,7 +24,7 @@ describe('AppInfoButton', () => {
fireEvent.press(button);

await waitFor(() => {
expect(analytics().logEvent).toHaveBeenCalledWith('app_info');
expect(logEvent).toHaveBeenCalledWith('app_info');
});
});
});
4 changes: 2 additions & 2 deletions src/components/Buttons/AppInfoButton.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React from 'react';

import analytics from '@react-native-firebase/analytics';
import { ParamListBase } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { Icon } from 'react-native-elements/dist/icons/Icon';

import { logEvent } from '../../Analytics';
import { systemBlue } from '../../constants';

import HeaderButton from './HeaderButton';
Expand All @@ -17,7 +17,7 @@ const AppInfoButton: React.FunctionComponent<Props> = ({ navigation }) => {
return (
<HeaderButton accessibilityLabel='App Info' onPress={async () => {
navigation.navigate('AppInfo');
await analytics().logEvent('app_info');
await logEvent('app_info');
}}>
<Icon name="gear"
type="font-awesome"
Expand Down
4 changes: 2 additions & 2 deletions src/components/Buttons/BackButton.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React from 'react';

import analytics from '@react-native-firebase/analytics';
import { ParamListBase } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { Icon } from 'react-native-elements/dist/icons/Icon';

import { logEvent } from '../../Analytics';
import { systemBlue } from '../../constants';

import HeaderButton from './HeaderButton';
Expand All @@ -17,7 +17,7 @@ const BackButton: React.FunctionComponent<Props> = ({ navigation }) => {
return (
<HeaderButton accessibilityLabel='Home' onPress={async () => {
navigation.goBack();
await analytics().logEvent('menu');
await logEvent('menu');
}}>
<Icon name="bars"
type="font-awesome-5"
Expand Down
8 changes: 5 additions & 3 deletions src/components/Buttons/CheckButton.test.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import analytics from '@react-native-firebase/analytics';
import { render, fireEvent, waitFor } from '@testing-library/react-native';
import { fireEvent, render, waitFor } from '@testing-library/react-native';

import { useNavigationMock } from '../../../test/test-helpers';
import { logEvent } from '../../Analytics';

import CheckButton from './CheckButton';

jest.mock('../../Analytics');

describe('CheckButton', () => {
const navigation = useNavigationMock();

Expand Down Expand Up @@ -33,7 +35,7 @@ describe('CheckButton', () => {
fireEvent.press(button);

await waitFor(() => {
expect(analytics().logEvent).toHaveBeenCalledWith('save_game');
expect(logEvent).toHaveBeenCalledWith('save_game');
});
});
});
4 changes: 2 additions & 2 deletions src/components/Buttons/CheckButton.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React from 'react';

import analytics from '@react-native-firebase/analytics';
import { ParamListBase, RouteProp } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { Text } from 'react-native';

import { logEvent } from '../../Analytics';
import { systemBlue } from '../../constants';

import HeaderButton from './HeaderButton';
Expand All @@ -23,7 +23,7 @@ const CheckButton: React.FunctionComponent<Props> = ({ navigation, route }) => {

return (
<HeaderButton accessibilityLabel='Save Game' onPress={async () => {
await analytics().logEvent('save_game');
await logEvent('save_game');
if (route?.params?.source === 'list_screen') {
navigation.navigate('List');
} else {
Expand Down
6 changes: 4 additions & 2 deletions src/components/Buttons/FullscreenButton.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React from 'react';

import analytics from '@react-native-firebase/analytics';
import { Icon } from 'react-native-elements/dist/icons/Icon';

import { useAppDispatch, useAppSelector } from '../../../redux/hooks';
import { toggleHomeFullscreen } from '../../../redux/SettingsSlice';
import { logEvent } from '../../Analytics';
import { systemBlue } from '../../constants';

import HeaderButton from './HeaderButton';
Expand All @@ -14,8 +14,10 @@ const FullscreenButton: React.FunctionComponent = ({ }) => {
const fullscreen = useAppSelector(state => state.settings.home_fullscreen);

const expandHandler = async () => {
await logEvent('fullscreen', {
fullscreen: !fullscreen
});
dispatch(toggleHomeFullscreen());
await analytics().logEvent('fullscreen');
};

return (
Expand Down
11 changes: 8 additions & 3 deletions src/components/Buttons/HomeButton.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react';

import analytics from '@react-native-firebase/analytics';
import { ParamListBase } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import * as StoreReview from 'expo-store-review';
Expand All @@ -10,6 +9,7 @@ import { Icon } from 'react-native-elements/dist/icons/Icon';
import { useAppDispatch, useAppSelector } from '../../../redux/hooks';
import { selectLastStoreReviewPrompt } from '../../../redux/selectors';
import { setLastStoreReviewPrompt } from '../../../redux/SettingsSlice';
import { logEvent } from '../../Analytics';
import { systemBlue } from '../../constants';

import HeaderButton from './HeaderButton';
Expand All @@ -20,6 +20,7 @@ interface Props {

const HomeButton: React.FunctionComponent<Props> = ({ navigation }) => {
const gameCount = useAppSelector((state) => state.games.ids.length);
const installId = useAppSelector((state) => state.settings.installId);
const lastStoreReviewPrompt = useAppSelector(selectLastStoreReviewPrompt);
const dispatch = useAppDispatch();

Expand All @@ -30,7 +31,11 @@ const HomeButton: React.FunctionComponent<Props> = ({ navigation }) => {
if (gameCount < 3) { return; }
if (daysSinceLastPrompt < 90) { return; }

await analytics().logEvent('review_prompt');
await logEvent('review_prompt', {
daysSinceLastPrompt,
gameCount,
installId
});

dispatch(setLastStoreReviewPrompt(Date.now()));

Expand All @@ -49,7 +54,7 @@ const HomeButton: React.FunctionComponent<Props> = ({ navigation }) => {
return (
<HeaderButton accessibilityLabel='Home' onPress={async () => {
navigation.navigate('List');
await analytics().logEvent('menu');
await logEvent('menu');

storePrompt();
}}>
Expand Down
Loading