Skip to content

Commit

Permalink
Merge pull request #466 from wyne/more-analytics
Browse files Browse the repository at this point in the history
Log more analytics
  • Loading branch information
wyne authored Jul 19, 2024
2 parents 393d6be + 8935847 commit 3813b04
Show file tree
Hide file tree
Showing 33 changed files with 279 additions and 96 deletions.
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 @@ -155,7 +155,7 @@ export const asyncRematchGame = createAsyncThunk(

dispatch(setCurrentGameId(newGameId));

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

Expand Down Expand Up @@ -227,9 +227,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

0 comments on commit 3813b04

Please sign in to comment.