Skip to content

Commit

Permalink
Merge pull request #40484 from Expensify/fullstory-integration
Browse files Browse the repository at this point in the history
Fullstory integration
  • Loading branch information
luacmartins authored Apr 23, 2024
2 parents 5e54d5a + 1ce4272 commit df57111
Show file tree
Hide file tree
Showing 15 changed files with 311 additions and 2 deletions.
10 changes: 9 additions & 1 deletion android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,20 @@ apply plugin: "com.android.application"
apply plugin: "org.jetbrains.kotlin.android"
apply plugin: "com.facebook.react"
apply plugin: "com.google.firebase.firebase-perf"
apply plugin: "fullstory"
apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle"

/**
* This is the configuration block to customize your React Native Android app.
* By default you don't need to apply any configuration, just uncomment the lines you need.
*/

/* Fullstory settings */
fullstory {
org 'o-1WN56P-na1'
enabledVariants 'all'
}

react {
/* Folders */
// The root of your project, i.e. where "package.json" lives. Default is '..'
Expand Down Expand Up @@ -162,7 +170,7 @@ android {
signingConfig null
// buildTypes take precedence over productFlavors when it comes to the signing configuration,
// thus we need to manually set the signing config, so that the e2e uses the debug config again.
// In other words, the signingConfig setting above will be ignored when we build the flavor in release mode.
// In other words, the signingConfig setting above will be ignored when we build the flavor in release mode.
productFlavors.all { flavor ->
// All release builds should be signed with the release config ...
flavor.signingConfig signingConfigs.release
Expand Down
6 changes: 5 additions & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,17 @@ buildscript {
repositories {
google()
mavenCentral()
maven {url "https://maven.fullstory.com"}
}
dependencies {
classpath("com.android.tools.build:gradle")
classpath("com.facebook.react:react-native-gradle-plugin")
classpath("com.google.gms:google-services:4.3.4")
classpath("com.google.firebase:firebase-crashlytics-gradle:2.7.1")
classpath("com.google.firebase:perf-plugin:1.4.1")
// Fullstory integration
classpath ("com.fullstory:gradle-plugin-local:1.45.1")

// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
Expand Down Expand Up @@ -70,7 +74,7 @@ allprojects {
// 'mapbox' is the fixed username for Mapbox's Maven repository.
username = 'mapbox'

// The value for password is read from the 'MAPBOX_DOWNLOADS_TOKEN' gradle property.
// The value for password is read from the 'MAPBOX_DOWNLOADS_TOKEN' gradle property.
// Run "npm run setup-mapbox-sdk" to set this property in «USER_HOME»/.gradle/gradle.properties

// Example gradle.properties entry:
Expand Down
17 changes: 17 additions & 0 deletions babel.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,23 @@ const defaultPlugins = [
// source code transformation as we do not use class property assignment.
'transform-class-properties',

/* Fullstory */
[
'@fullstory/react-native',
{
version: '1.4.0',
org: 'o-1WN56P-na1',
enabledVariants: 'all',
},
],
[
'@fullstory/babel-plugin-annotate-react',
{
native: true,
setFSTagName: true,
},
],

// Keep it last
'react-native-reanimated/plugin',
];
Expand Down
4 changes: 4 additions & 0 deletions ios/NewExpensify.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,7 @@
"${PODS_ROOT}/Target Support Files/Pods-NewExpensify-NewExpensifyTests/Pods-NewExpensify-NewExpensifyTests-frameworks.sh",
"${BUILT_PRODUCTS_DIR}/MapboxMaps/MapboxMaps.framework",
"${BUILT_PRODUCTS_DIR}/Turf/Turf.framework",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/FullStory/FullStory.framework/FullStory",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/MapboxCommon/MapboxCommon.framework/MapboxCommon",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/MapboxCoreMaps/MapboxCoreMaps.framework/MapboxCoreMaps",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/MapboxMobileEvents/MapboxMobileEvents.framework/MapboxMobileEvents",
Expand All @@ -692,6 +693,7 @@
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxMaps.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Turf.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FullStory.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxCommon.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxCoreMaps.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxMobileEvents.framework",
Expand Down Expand Up @@ -735,6 +737,7 @@
"${PODS_ROOT}/Target Support Files/Pods-NewExpensify/Pods-NewExpensify-frameworks.sh",
"${BUILT_PRODUCTS_DIR}/MapboxMaps/MapboxMaps.framework",
"${BUILT_PRODUCTS_DIR}/Turf/Turf.framework",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/FullStory/FullStory.framework/FullStory",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/MapboxCommon/MapboxCommon.framework/MapboxCommon",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/MapboxCoreMaps/MapboxCoreMaps.framework/MapboxCoreMaps",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/MapboxMobileEvents/MapboxMobileEvents.framework/MapboxMobileEvents",
Expand All @@ -746,6 +749,7 @@
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxMaps.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Turf.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FullStory.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxCommon.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxCoreMaps.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxMobileEvents.framework",
Expand Down
5 changes: 5 additions & 0 deletions ios/NewExpensify/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
</array>
<key>FullStory</key>
<dict>
<key>OrgId</key>
<string>o-1WN56P-na1</string>
</dict>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
Expand Down
28 changes: 28 additions & 0 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,27 @@ PODS:
- GoogleUtilities/Environment (~> 7.7)
- "GoogleUtilities/NSData+zlib (~> 7.7)"
- fmt (6.2.1)
- FullStory (1.43.1)
- fullstory_react-native (1.4.2):
- FullStory (~> 1.14)
- glog
- hermes-engine
- RCT-Folly (= 2022.05.16.00)
- RCTRequired
- RCTTypeSafety
- React-Codegen
- React-Core
- React-debug
- React-Fabric
- React-graphics
- React-ImageManager
- React-NativeModulesApple
- React-RCTFabric
- React-rendererdebug
- React-utils
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- glog (0.3.5)
- GoogleAppMeasurement (8.8.0):
- GoogleAppMeasurement/AdIdSupport (= 8.8.0)
Expand Down Expand Up @@ -2176,6 +2197,7 @@ SPEC REPOS:
- FirebasePerformance
- FirebaseRemoteConfig
- fmt
- FullStory
- GoogleAppMeasurement
- GoogleDataTransport
- GoogleSignIn
Expand Down Expand Up @@ -2224,6 +2246,10 @@ EXTERNAL SOURCES:
:path: "../node_modules/expo-modules-core"
FBLazyVector:
:path: "../node_modules/react-native/Libraries/FBLazyVector"
fullstory_react-native:
:path: "../node_modules/@fullstory/react-native"
FBReactNativeSpec:
:path: "../node_modules/react-native/React/FBReactNativeSpec"
glog:
:podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec"
hermes-engine:
Expand Down Expand Up @@ -2439,6 +2465,8 @@ SPEC CHECKSUMS:
FirebasePerformance: 0c01a7a496657d7cea86d40c0b1725259d164c6c
FirebaseRemoteConfig: 2d6e2cfdb49af79535c8af8a80a4a5009038ec2b
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
FullStory: e035758fef275fb59c6471f61b179652aeca452b
fullstory_react-native: a56e2bb52753b69f01aab3ae876087db08488034
glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2
GoogleAppMeasurement: 5ba1164e3c844ba84272555e916d0a6d3d977e91
GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a
Expand Down
2 changes: 2 additions & 0 deletions jest/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import '@shopify/flash-list/jestSetup';
import 'react-native-gesture-handler/jestSetup';
import mockStorage from 'react-native-onyx/dist/storage/__mocks__';
import 'setimmediate';
import mockFSLibrary from './setupMockFullstoryLib';
import setupMockImages from './setupMockImages';

setupMockImages();
mockFSLibrary();

// This mock is required as per setup instructions for react-navigation testing
// https://reactnavigation.org/docs/testing/#mocking-native-modules
Expand Down
24 changes: 24 additions & 0 deletions jest/setupMockFullstoryLib.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
type FSPageInterface = {
start: jest.Mock<void, []>;
};

export default function mockFSLibrary() {
jest.mock('@fullstory/react-native', () => {
class Fullstory {
consent = jest.fn();

anonymize = jest.fn();

identify = jest.fn();
}

return {
FSPage(): FSPageInterface {
return {
start: jest.fn(),
};
},
default: Fullstory,
};
});
}
48 changes: 48 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@
"@formatjs/intl-locale": "^3.3.0",
"@formatjs/intl-numberformat": "^8.5.0",
"@formatjs/intl-pluralrules": "^5.2.2",
"@fullstory/browser": "^2.0.3",
"@fullstory/react-native": "^1.4.0",
"@gorhom/portal": "^1.0.14",
"@invertase/react-native-apple-authentication": "^2.2.2",
"@kie/act-js": "^2.6.0",
Expand Down
3 changes: 3 additions & 0 deletions src/libs/Navigation/NavigationRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import useActiveWorkspace from '@hooks/useActiveWorkspace';
import useCurrentReportID from '@hooks/useCurrentReportID';
import useTheme from '@hooks/useTheme';
import useWindowDimensions from '@hooks/useWindowDimensions';
import {FSPage} from '@libs/fullstory';
import Log from '@libs/Log';
import {getPathFromURL} from '@libs/Url';
import {updateLastVisitedPath} from '@userActions/App';
Expand Down Expand Up @@ -57,6 +58,8 @@ function parseAndLogRoute(state: NavigationState) {
}

Navigation.setIsNavigationReady();
// Fullstory Page navigation tracking
new FSPage(focusedRoute?.name ?? '', {path: currentPath}).start();
}

function NavigationRoot({authenticated, lastVisitedPath, initialUrl, onReady}: NavigationRootProps) {
Expand Down
6 changes: 6 additions & 0 deletions src/libs/actions/Session/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import type SignInUserParams from '@libs/API/parameters/SignInUserParams';
import {READ_COMMANDS, SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
import * as Authentication from '@libs/Authentication';
import * as ErrorUtils from '@libs/ErrorUtils';
import Fullstory from '@libs/fullstory';
import HttpUtils from '@libs/HttpUtils';
import Log from '@libs/Log';
import Navigation from '@libs/Navigation/Navigation';
Expand Down Expand Up @@ -63,6 +64,11 @@ Onyx.connect({
},
});

Onyx.connect({
key: ONYXKEYS.SESSION,
callback: Fullstory.consentAndIdentify,
});

let stashedSession: Session = {};
Onyx.connect({
key: ONYXKEYS.STASHED_SESSION,
Expand Down
59 changes: 59 additions & 0 deletions src/libs/fullstory/index.native.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import FullStory, {FSPage} from '@fullstory/react-native';
import type {OnyxEntry} from 'react-native-onyx';
import type Session from '@src/types/onyx/Session';
import type {UserSession} from './types';

/**
* Fullstory React-Native lib adapter
* Proxy function calls to React-Native lib
* */
const FS = {
/**
* Sets the identity as anonymous using the FullStory library.
*/
anonymize: () => FullStory.anonymize(),

/**
* Sets the identity consent status using the FullStory library.
*/
consent: (c: boolean) => FullStory.consent(c),

/**
* Initializes the FullStory session with the provided session information.
*/
consentAndIdentify: (value: OnyxEntry<Session>) => {
try {
const session: UserSession = {
email: value?.email,
accountID: value?.accountID,
};
// set consent
FullStory.consent(true);
FS.fsIdentify(session);
} catch (e) {
// error handler
}
},

/**
* Sets the FullStory user identity based on the provided session information.
* If the session is null or the email is 'undefined', the user identity is anonymized.
* If the session contains an email, the user identity is defined with the email and account ID.
*/
fsIdentify: (session: UserSession) => {
if (!session || session.email === 'undefined') {
// anonymize FullStory user identity session
FullStory.anonymize();
} else {
// define FullStory user identity
FullStory.identify(String(session.accountID), {
properties: {
accountID: session.accountID,
},
});
}
},
};

export default FS;
export {FSPage};
Loading

0 comments on commit df57111

Please sign in to comment.