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

Change Fullstory recording start conditions #50516

Merged
merged 13 commits into from
Oct 16, 2024
61 changes: 17 additions & 44 deletions src/Expensify.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React, {useCallback, useContext, useEffect, useLayoutEffect, useMemo, use
import type {NativeEventSubscription} from 'react-native';
import {AppState, Linking, NativeModules, Platform} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import Onyx, {useOnyx, withOnyx} from 'react-native-onyx';
import Onyx, {useOnyx} from 'react-native-onyx';
import ConfirmModal from './components/ConfirmModal';
import DeeplinkWrapper from './components/DeeplinkWrapper';
import EmojiPicker from './components/EmojiPicker/EmojiPicker';
Expand Down Expand Up @@ -55,7 +55,7 @@ Onyx.registerLogger(({level, message}) => {
}
});

type ExpensifyOnyxProps = {
type ExpensifyProps = {
/** Whether the app is waiting for the server's response to determine if a room is public */
isCheckingPublicRoom: OnyxEntry<boolean>;

Expand All @@ -77,18 +77,7 @@ type ExpensifyOnyxProps = {
/** Last visited path in the app */
lastVisitedPath: OnyxEntry<string | undefined>;
};

type ExpensifyProps = ExpensifyOnyxProps;

function Expensify({
isCheckingPublicRoom = true,
updateAvailable,
isSidebarLoaded = false,
screenShareRequest,
updateRequired = false,
focusModeNotification = false,
lastVisitedPath,
}: ExpensifyProps) {
function Expensify() {
const appStateChangeListener = useRef<NativeEventSubscription | null>(null);
const [isNavigationReady, setIsNavigationReady] = useState(false);
const [isOnyxMigrated, setIsOnyxMigrated] = useState(false);
Expand All @@ -98,7 +87,15 @@ function Expensify({
const [account] = useOnyx(ONYXKEYS.ACCOUNT);
const [session] = useOnyx(ONYXKEYS.SESSION);
const [lastRoute] = useOnyx(ONYXKEYS.LAST_ROUTE);
const [userMetadata] = useOnyx(ONYXKEYS.USER_METADATA);
const [shouldShowRequire2FAModal, setShouldShowRequire2FAModal] = useState(false);
const [isCheckingPublicRoom] = useOnyx(ONYXKEYS.IS_CHECKING_PUBLIC_ROOM);
const [updateAvailable] = useOnyx(ONYXKEYS.UPDATE_AVAILABLE);
const [updateRequired] = useOnyx(ONYXKEYS.UPDATE_REQUIRED);
const [isSidebarLoaded] = useOnyx(ONYXKEYS.IS_SIDEBAR_LOADED);
const [screenShareRequest] = useOnyx(ONYXKEYS.SCREEN_SHARE_REQUEST);
const [focusModeNotification] = useOnyx(ONYXKEYS.FOCUS_MODE_NOTIFICATION);
const [lastVisitedPath] = useOnyx(ONYXKEYS.LAST_VISITED_PATH);

useEffect(() => {
if (!account?.needsTwoFactorAuthSetup || account.requiresTwoFactorAuth) {
Expand Down Expand Up @@ -148,15 +145,17 @@ function Expensify({
// Initialize this client as being an active client
ActiveClientManager.init();

// Initialize Fullstory lib
FS.init();

// Used for the offline indicator appearing when someone is offline
const unsubscribeNetInfo = NetworkConnection.subscribeToNetInfo();

return unsubscribeNetInfo;
}, []);

useEffect(() => {
// Initialize Fullstory lib
FS.init(userMetadata);
}, [userMetadata]);

// Log the platform and config to debug .env issues
useEffect(() => {
Log.info('App launched', false, {Platform, CONFIG});
Expand Down Expand Up @@ -304,30 +303,4 @@ function Expensify({

Expensify.displayName = 'Expensify';

export default withOnyx<ExpensifyProps, ExpensifyOnyxProps>({
isCheckingPublicRoom: {
key: ONYXKEYS.IS_CHECKING_PUBLIC_ROOM,
initWithStoredValues: false,
},
updateAvailable: {
key: ONYXKEYS.UPDATE_AVAILABLE,
initWithStoredValues: false,
},
updateRequired: {
key: ONYXKEYS.UPDATE_REQUIRED,
initWithStoredValues: false,
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@danieldoglas I think there's a very likely possibility this change has been leading to issues with the updateRequired logic. The intended behavior is that this value will only be true when it is explicitly set to true AFTER the component has mounted. If the value gets stuck on true then the user will get stuck and be unable to upgrade since they'd be on the correct version already, but have out of date Onyx values.

isSidebarLoaded: {
key: ONYXKEYS.IS_SIDEBAR_LOADED,
},
screenShareRequest: {
key: ONYXKEYS.SCREEN_SHARE_REQUEST,
},
focusModeNotification: {
key: ONYXKEYS.FOCUS_MODE_NOTIFICATION,
initWithStoredValues: false,
},
lastVisitedPath: {
key: ONYXKEYS.LAST_VISITED_PATH,
},
})(Expensify);
export default Expensify;
51 changes: 22 additions & 29 deletions src/libs/Fullstory/index.native.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import FullStory, {FSPage} from '@fullstory/react-native';
import type {OnyxEntry} from 'react-native-onyx';
import {useOnyx} from 'react-native-onyx';
import CONST from '@src/CONST';
import * as Environment from '@src/libs/Environment/Environment';
import ONYXKEYS from '@src/ONYXKEYS';
import type {UserMetadata} from '@src/types/onyx';

/**
Expand All @@ -14,16 +12,8 @@ const FS = {
/**
* Initializes FullStory
*/
init: () => {
Environment.getEnvironment().then((envName: string) => {
// We only want to start fullstory if the app is running in production
if (envName !== CONST.ENVIRONMENT.PRODUCTION) {
return;
}
FullStory.restart();
const [session] = useOnyx(ONYXKEYS.USER_METADATA);
FS.fsIdentify(session);
});
init: (value: OnyxEntry<UserMetadata>) => {
FS.consentAndIdentify(value);
},

/**
Expand All @@ -40,32 +30,35 @@ const FS = {
* Initializes the FullStory metadata with the provided metadata information.
*/
consentAndIdentify: (value: OnyxEntry<UserMetadata>) => {
// On the first subscribe for UserMetadta, this function will be called. We need
// to confirm that we actually have any value here before proceeding.
if (!value?.accountID) {
return;
}
try {
// We only use FullStory in production environment
FullStory.consent(true);
FS.fsIdentify(value);
// We only use FullStory in production environment. We need to check this here
// after the init function since this function is also called on updates for
// UserMetadata onyx key.
Environment.getEnvironment().then((envName: string) => {
if (envName !== CONST.ENVIRONMENT.PRODUCTION) {
return;
}
FullStory.restart();
FullStory.consent(true);
FS.fsIdentify(value, envName);
});
} catch (e) {
// error handler
}
},

/**
* Sets the FullStory user identity based on the provided metadata information.
* If the metadata is null or the email is 'undefined', the user identity is anonymized.
* If the metadata contains an accountID, the user identity is defined with it.
*/
fsIdentify: (metadata: OnyxEntry<UserMetadata>) => {
if (!metadata?.accountID) {
// anonymize FullStory user identity metadata
FullStory.anonymize();
} else {
Environment.getEnvironment().then((envName: string) => {
// define FullStory user identity
const localMetadata = metadata;
localMetadata.environment = envName;
FullStory.identify(String(localMetadata.accountID), localMetadata);
});
}
fsIdentify: (metadata: UserMetadata, envName: string) => {
const localMetadata = metadata;
localMetadata.environment = envName;
FullStory.identify(String(localMetadata.accountID), localMetadata);
},
};

Expand Down
48 changes: 20 additions & 28 deletions src/libs/Fullstory/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,11 @@ const FS = {
*/
onReady: () =>
new Promise((resolve) => {
Environment.getEnvironment().then((envName: string) => {
if (CONST.ENVIRONMENT.PRODUCTION !== envName) {
return;
}
// Initialised via HEAD snippet
if (!isInitialized()) {
init({orgId: ''}, resolve);
} else {
FullStory('observe', {type: 'start', callback: resolve});
}
});
if (!isInitialized()) {
init({orgId: ''}, resolve);
} else {
FullStory('observe', {type: 'start', callback: resolve});
}
}),

/**
Expand All @@ -56,18 +50,21 @@ const FS = {
* Initializes the FullStory metadata with the provided metadata information.
*/
consentAndIdentify: (value: OnyxEntry<UserMetadata>) => {
// On the first subscribe for UserMetadata, this function will be called. We need
// to confirm that we actually have any value here before proceeding.
if (!value?.accountID) {
return;
}
try {
Environment.getEnvironment().then((envName: string) => {
if (CONST.ENVIRONMENT.PRODUCTION !== envName) {
return;
}
FS.onReady().then(() => {
FS.consent(true);
if (value) {
const localMetadata = value;
localMetadata.environment = envName;
FS.fsIdentify(localMetadata);
}
const localMetadata = value;
localMetadata.environment = envName;
FS.fsIdentify(localMetadata);
});
});
} catch (e) {
Expand All @@ -80,23 +77,18 @@ const FS = {
* If the metadata does not contain an email, the user identity is anonymized.
* If the metadata contains an accountID, the user identity is defined with it.
*/
fsIdentify: (metadata: OnyxEntry<UserMetadata>) => {
if (!metadata?.accountID) {
// anonymize FullStory user identity metadata
FS.anonymize();
} else {
// define FullStory user identity
FullStory('setIdentity', {
uid: String(metadata.accountID),
properties: metadata,
});
}
fsIdentify: (metadata: UserMetadata) => {
FullStory('setIdentity', {
uid: String(metadata.accountID),
properties: metadata,
});
},

/**
* Init function, created so we're consistent with the native file
*/
init: () => {},
// eslint-disable-next-line @typescript-eslint/no-unused-vars
init: (_value: OnyxEntry<UserMetadata>) => {},
};

export default FS;
Expand Down
Loading