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

feat: Alternative Ophan Implementation #2379

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions projects/Mallard/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,8 @@ PODS:
- React-Core
- react-native-geolocation (3.1.0):
- React-Core
- react-native-get-random-values (1.11.0):
- React-Core
- react-native-in-app-utils (6.1.0):
- React
- react-native-netinfo (11.3.0):
Expand Down Expand Up @@ -731,6 +733,7 @@ DEPENDENCIES:
- React-logger (from `../node_modules/react-native/ReactCommon/logger`)
- react-native-config (from `../node_modules/react-native-config`)
- "react-native-geolocation (from `../node_modules/@react-native-community/geolocation`)"
- react-native-get-random-values (from `../node_modules/react-native-get-random-values`)
- react-native-in-app-utils (from `../node_modules/react-native-in-app-utils`)
- "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)"
- react-native-pager-view (from `../node_modules/react-native-pager-view`)
Expand Down Expand Up @@ -861,6 +864,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-config"
react-native-geolocation:
:path: "../node_modules/@react-native-community/geolocation"
react-native-get-random-values:
:path: "../node_modules/react-native-get-random-values"
react-native-in-app-utils:
:path: "../node_modules/react-native-in-app-utils"
react-native-netinfo:
Expand Down Expand Up @@ -1012,6 +1017,7 @@ SPEC CHECKSUMS:
React-logger: 9fd8d34baa7930b42a70669ec0f0971083ae5a7b
react-native-config: 86038147314e2e6d10ea9972022aa171e6b1d4d8
react-native-geolocation: ef66fb798d96284c6043f0b16c15d9d1d4955db4
react-native-get-random-values: 21325b2244dfa6b58878f51f9aa42821e7ba3d06
react-native-in-app-utils: 96cdefc90ad74a79a95d19239a183995a6face0b
react-native-netinfo: 299dad906cdbf3b67bcc6f693c807f98bdd127cc
react-native-pager-view: da490aa1f902c9a5aeecf0909cc975ad0e92e53e
Expand Down
2 changes: 2 additions & 0 deletions projects/Mallard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"jetifier": "^1.6.3",
"moment": "^2.29.2",
"moment-timezone": "^0.5.27",
"nanoid": "^5.0.5",
"node-fetch": "^2.6.7",
"react": "18.2.0",
"react-native": "0.72.11",
Expand All @@ -77,6 +78,7 @@
"react-native-fast-image": "^8.6.3",
"react-native-fs": "^2.20.0",
"react-native-gesture-handler": "2.12.0",
"react-native-get-random-values": "^1.10.0",
"react-native-iap": "^12.13.0",
"react-native-image-zoom-viewer": "^3.0.1",
"react-native-in-app-utils": "^6.0.2",
Expand Down
6 changes: 1 addition & 5 deletions projects/Mallard/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,11 @@ const WithProviders = nestProviders(
);

const handleIdStatus = (attempt: AnyAttempt<IdentityAuthData>) =>
logUserId(
isValid(attempt) ? attempt.data.userDetails.id : null,
'identity',
);
logUserId(isValid(attempt) ? attempt.data.userDetails.id : null);

const handleOktaStatus = (attempt: AnyAttempt<OktaAuthData>) =>
logUserId(
isValid(attempt) ? attempt.data.userDetails.legacy_identity_id : null,
'okta',
);

const App = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import React from 'react';
import type { StyleProp, ViewStyle } from 'react-native';
import { StyleSheet, TouchableHighlight, View } from 'react-native';
import type { CAPIArticle, Issue, ItemSizes } from 'src/common';
import { logPageView } from 'src/helpers/analytics';
import type { MainStackParamList } from 'src/navigation/NavigationModels';
import { RouteNames } from 'src/navigation/NavigationModels';
import type { PathToArticle } from 'src/paths';
Expand Down Expand Up @@ -56,6 +57,7 @@ const ItemTappable = ({
const navigation = useNavigation<StackNavigationProp<MainStackParamList>>();

const handlePress = () => {
article?.webUrl && logPageView(article.webUrl);
article.type === 'crossword'
? navigation.navigate(RouteNames.Crossword, {
path,
Expand Down
51 changes: 24 additions & 27 deletions projects/Mallard/src/helpers/analytics/__tests__/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,43 @@
import { logEvent, logPageView, logScreenView, logUserId } from '..';

const mockLogEvent = jest.fn();
const mockLogScreenView = jest.fn();
const mockSetUserId = jest.fn();
const mockSetUserProperty = jest.fn();

jest.mock('@react-native-firebase/analytics', () =>
jest.fn().mockImplementation(() => ({
logEvent: mockLogEvent,
logScreenView: mockLogScreenView,
setUserId: mockSetUserId,
setUserProperty: mockSetUserProperty,
})),
);
import * as Ophan from 'src/services/ophan';

describe('analytics', () => {
describe('logEvent', () => {
it('should call "logEvent" from firebase analytics', async () => {
it('should call "sendComponentEvent" from the Ophan service', async () => {
const mockSendComponentEvent = jest
.spyOn(Ophan, 'sendComponentEvent')
.mockResolvedValue(true);
await logEvent({ name: 'testComponent', value: 'testing' });
expect(mockLogEvent).toHaveBeenCalled();
expect(mockSendComponentEvent).toHaveBeenCalled();
});
});
describe('logPageView', () => {
it('should call "logEvent" from firebase analyticse', async () => {
await logPageView('test/path/to/article');
expect(mockLogEvent).toHaveBeenCalled();
it('should call "sendPageViewEvent" from the Ophan service', async () => {
const mockSendPageViewEvent = jest
.spyOn(Ophan, 'sendPageViewEvent')
.mockResolvedValue(true);
await logPageView(
'http://www.theguardian.com/test/path/to/article',
);
expect(mockSendPageViewEvent).toHaveBeenCalled();
});
});
describe('logScreenView', () => {
it('should call "logScreenView" from firebase analyticse', async () => {
it('should call "logScreenView" from the Ophan service', async () => {
const mockAppScreenEvent = jest
.spyOn(Ophan, 'sendAppScreenEvent')
.mockResolvedValue(true);
await logScreenView('IssueScreen');
expect(mockLogEvent).toHaveBeenCalled();
expect(mockAppScreenEvent).toHaveBeenCalled();
});
});
describe('logUserId', () => {
it('should call "setUserId" from firebase analyticse', async () => {
await logUserId('12345', 'identity');
it('should call "setUserId" from the Ophan service', async () => {
const mockSetUserId = jest
.spyOn(Ophan, 'setUserId')
.mockResolvedValue(true);
await logUserId('12345');
expect(mockSetUserId).toHaveBeenCalled();
expect(mockSetUserProperty).toHaveBeenCalledWith(
'authType',
'identity',
);
});
});
});
55 changes: 24 additions & 31 deletions projects/Mallard/src/helpers/analytics/index.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
import analytics from '@react-native-firebase/analytics';
import {
Action,
ComponentType,
sendAppScreenEvent,
sendComponentEvent,
sendPageViewEvent,
setUserId,
} from 'src/services/ophan';
import type { AnalyticsEvent, AnalyticsUserId } from './types';

const toggleAnalyticsRecording = async (enable: boolean): Promise<boolean> => {
try {
await analytics().setAnalyticsCollectionEnabled(enable);
return true;
} catch {
return false;
}
};

const logScreenView = async (routeName: string): Promise<boolean> => {
const logScreenView = async (
routeName: string,
value?: string,
): Promise<boolean> => {
try {
await analytics().logScreenView({
screen_name: routeName,
screen_class: routeName,
});
await sendAppScreenEvent({ screenName: routeName, value });
return true;
} catch {
return false;
Expand All @@ -24,40 +22,35 @@ const logScreenView = async (routeName: string): Promise<boolean> => {

const logEvent = async ({ name, value }: AnalyticsEvent): Promise<boolean> => {
try {
await analytics().logEvent(name, { value });
await sendComponentEvent({
componentType: ComponentType.AppButton,
action: Action.Click,
value,
componentId: name,
});
return true;
} catch {
return false;
}
};

// This differs from a screen view as this is a "screen" that has significance to journalism e.g. Article screen view
const logPageView = async (path: string): Promise<boolean> => {
const logPageView = async (url: string): Promise<boolean> => {
try {
await analytics().logEvent('pageView', { path });
await sendPageViewEvent({ url });
return true;
} catch {
return false;
}
};

const logUserId = async (
userId: AnalyticsUserId,
authType: 'identity' | 'okta',
): Promise<boolean> => {
const logUserId = async (userId: AnalyticsUserId): Promise<boolean> => {
try {
await analytics().setUserId(userId);
await analytics().setUserProperty('authType', authType);
await setUserId(userId);
return true;
} catch {
return false;
}
};

export {
logEvent,
logPageView,
logScreenView,
logUserId,
toggleAnalyticsRecording,
};
export { logEvent, logPageView, logScreenView, logUserId };
7 changes: 2 additions & 5 deletions projects/Mallard/src/hooks/use-gdpr.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React, { createContext, useContext, useEffect, useState } from 'react';
import { toggleAnalyticsRecording } from 'src/helpers/analytics';
import {
gdprAllowFunctionalityCache,
gdprAllowPerformanceCache,
Expand All @@ -20,8 +19,9 @@ import { errorService } from 'src/services/errors';
* v8 - Add Firebase Analytics as PERFORMANCE
* v9 - Remove additional Logging
* v10 - Remove Sentry from the app
* v11 - Enable Ophan as essential
*/
const CURRENT_CONSENT_VERSION = 10;
const CURRENT_CONSENT_VERSION = 11;

/*
Consent switches can be 'unset' or null
Expand Down Expand Up @@ -117,9 +117,6 @@ export const GDPRProvider = ({ children }: { children: React.ReactNode }) => {
// Local state modifier
setGdprAllowPerformance(setting);
setGdprConsentVersion(CURRENT_CONSENT_VERSION);
setting
? toggleAnalyticsRecording(setting)
: toggleAnalyticsRecording(false);
// Persisted state modifier
setting === null
? gdprAllowPerformanceCache.reset()
Expand Down
4 changes: 3 additions & 1 deletion projects/Mallard/src/screens/article/slider/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,9 @@ const ArticleSlider = React.memo(
showsVerticalScrollIndicator={false}
scrollEventThrottle={1}
onMomentumScrollEnd={() => {
logPageView(flattenedArticles[current].article);
logPageView(
`https://www.theguardian.com/${flattenedArticles[current].article}`,
);
}}
onScroll={Animated.event(
[
Expand Down
3 changes: 2 additions & 1 deletion projects/Mallard/src/screens/issue-screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,8 @@ const IssueScreenWithPath = ({
const { initialFrontKey } = useIssueSummary();

useEffect(() => {
issue && logPageView(`editions/${issue.key}`);
issue &&
logPageView(`https://www.theguardian.com/editions/${issue.key}`);
}, [issue?.key]);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ type EssentialGdprSwitch = Omit<GdprSwitch, 'key' | 'modifier' | 'value'>;
const essentials: EssentialGdprSwitch = {
name: 'Essential',
services:
'YouTube Player - Firebase Cloud Messaging - Firebase Remote Config',
'Ophan - YouTube Player - Firebase Cloud Messaging - Firebase Remote Config',
description:
'These are essential to provide you with services that you have requested. These services support the ability for you to watch videos, see service-related messages, download content automatically and receive new features without app releases.',
};
Expand Down
Loading
Loading