From 4f2ff72cf80c919c2de0d96cfc73f15ed7a923cc Mon Sep 17 00:00:00 2001 From: OSBotify Date: Wed, 24 Apr 2024 11:57:30 +0000 Subject: [PATCH 01/14] Update version to 1.4.65-1 (cherry picked from commit e58ec4940cbed9352266760bebee0a03257c4717) --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- ios/NotificationServiceExtension/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 998a43a8c3ff..0cb1ea6d7383 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -106,8 +106,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001046500 - versionName "1.4.65-0" + versionCode 1001046501 + versionName "1.4.65-1" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 38221d8cf645..7826883e1ab7 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.65.0 + 1.4.65.1 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index bdf73b5e5695..422f2c8e696e 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.4.65.0 + 1.4.65.1 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 1a4de746addf..1d9557d9da7f 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 1.4.65 CFBundleVersion - 1.4.65.0 + 1.4.65.1 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index e92667992321..07f406f4607e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.65-0", + "version": "1.4.65-1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.65-0", + "version": "1.4.65-1", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 89d80e9651fe..e66cd9ca6cbd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.65-0", + "version": "1.4.65-1", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From a94f2718cd05fc2ddb1b738f1548dae6fde62b84 Mon Sep 17 00:00:00 2001 From: Daniel Silva Date: Wed, 24 Apr 2024 13:52:35 +0200 Subject: [PATCH 02/14] Merge pull request #40865 from Expensify/revert-35226-fix/34442 [CP Staging] Revert "Implement suggestion for edit composer" (cherry picked from commit 95aa19a5a3f795a4d5bcec135188b765073b0a37) --- src/App.tsx | 2 - src/CONST.ts | 2 - .../BaseAutoCompleteSuggestions.tsx | 5 +- .../AutoCompleteSuggestions/index.native.tsx | 5 +- .../AutoCompleteSuggestions/index.tsx | 13 +-- .../AutoCompleteSuggestions/types.ts | 5 +- src/components/EmojiSuggestions.tsx | 2 +- src/libs/SuggestionUtils.ts | 23 +---- .../ComposerWithSuggestionsEdit.tsx | 98 ------------------- .../SuggestionsContext.tsx | 56 ----------- .../ReportActionCompose.tsx | 3 +- .../ReportActionCompose/SuggestionEmoji.tsx | 15 +-- .../ReportActionCompose/SuggestionMention.tsx | 7 +- .../ReportActionCompose/Suggestions.tsx | 16 +-- src/pages/home/report/ReportActionItem.tsx | 1 - .../report/ReportActionItemMessageEdit.tsx | 89 ++--------------- src/pages/home/report/ReportActionsList.tsx | 14 --- src/styles/utils/index.ts | 5 +- 18 files changed, 21 insertions(+), 340 deletions(-) delete mode 100644 src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx delete mode 100644 src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/SuggestionsContext.tsx diff --git a/src/App.tsx b/src/App.tsx index 006028271c80..a3a9f7a3f3b6 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -33,7 +33,6 @@ import {WindowDimensionsProvider} from './components/withWindowDimensions'; import Expensify from './Expensify'; import useDefaultDragAndDrop from './hooks/useDefaultDragAndDrop'; import OnyxUpdateManager from './libs/actions/OnyxUpdateManager'; -import {SuggestionsContextProvider} from './pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/SuggestionsContext'; import {ReportAttachmentsProvider} from './pages/home/report/ReportAttachmentsContext'; import type {Route} from './ROUTES'; @@ -80,7 +79,6 @@ function App({url}: AppProps) { ActiveElementRoleProvider, ActiveWorkspaceContextProvider, PlaybackContextProvider, - SuggestionsContextProvider, FullScreenContextProvider, VolumeContextProvider, VideoPopoverMenuContextProvider, diff --git a/src/CONST.ts b/src/CONST.ts index c2299f242b6b..f0139d82e614 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -72,7 +72,6 @@ type OnboardingPurposeType = ValueOf; const CONST = { MERGED_ACCOUNT_PREFIX: 'MERGED_', DEFAULT_POLICY_ROOM_CHAT_TYPES: [chatTypes.POLICY_ADMINS, chatTypes.POLICY_ANNOUNCE, chatTypes.DOMAIN_ALL], - DEFAULT_COMPOSER_PORTAL_HOST_NAME: 'suggestions_0', // Note: Group and Self-DM excluded as these are not tied to a Workspace WORKSPACE_ROOM_TYPES: [chatTypes.POLICY_ADMINS, chatTypes.POLICY_ANNOUNCE, chatTypes.DOMAIN_ALL, chatTypes.POLICY_ROOM, chatTypes.POLICY_EXPENSE_CHAT], @@ -1172,7 +1171,6 @@ const CONST = { EMOJI_PICKER_HEADER_HEIGHT: 32, RECIPIENT_LOCAL_TIME_HEIGHT: 25, AUTO_COMPLETE_SUGGESTER: { - EDIT_SUGGESTER_PADDING: 8, SUGGESTER_PADDING: 6, SUGGESTER_INNER_PADDING: 8, SUGGESTION_ROW_HEIGHT: 40, diff --git a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx index a11fb2a01cc8..ccd0f21626a0 100644 --- a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx +++ b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx @@ -10,7 +10,6 @@ import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; -import {useSuggestionsContext} from '@pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/SuggestionsContext'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import viewForwardedRef from '@src/types/utils/viewForwardedRef'; @@ -40,7 +39,6 @@ function BaseAutoCompleteSuggestions( suggestions, isSuggestionPickerLarge, keyExtractor, - shouldBeDisplayedBelowParentContainer = false, }: AutoCompleteSuggestionsProps, ref: ForwardedRef, ) { @@ -49,7 +47,6 @@ function BaseAutoCompleteSuggestions( const StyleUtils = useStyleUtils(); const rowHeight = useSharedValue(0); const scrollRef = useRef>(null); - const {activeID} = useSuggestionsContext(); /** * Render a suggestion menu item component. */ @@ -71,7 +68,7 @@ function BaseAutoCompleteSuggestions( ); const innerHeight = CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT * suggestions.length; - const animatedStyles = useAnimatedStyle(() => StyleUtils.getAutoCompleteSuggestionContainerStyle(rowHeight.value, shouldBeDisplayedBelowParentContainer, Boolean(activeID))); + const animatedStyles = useAnimatedStyle(() => StyleUtils.getAutoCompleteSuggestionContainerStyle(rowHeight.value)); const estimatedListSize = useMemo( () => ({ height: CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT * suggestions.length, diff --git a/src/components/AutoCompleteSuggestions/index.native.tsx b/src/components/AutoCompleteSuggestions/index.native.tsx index 863e806b143e..fbfa7d953581 100644 --- a/src/components/AutoCompleteSuggestions/index.native.tsx +++ b/src/components/AutoCompleteSuggestions/index.native.tsx @@ -1,14 +1,11 @@ import {Portal} from '@gorhom/portal'; import React from 'react'; -import {useSuggestionsContext} from '@pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/SuggestionsContext'; -import CONST from '@src/CONST'; import BaseAutoCompleteSuggestions from './BaseAutoCompleteSuggestions'; import type {AutoCompleteSuggestionsProps} from './types'; function AutoCompleteSuggestions({measureParentContainer, ...props}: AutoCompleteSuggestionsProps) { - const {activeID} = useSuggestionsContext(); return ( - + {/* eslint-disable-next-line react/jsx-props-no-spreading */} {...props} /> diff --git a/src/components/AutoCompleteSuggestions/index.tsx b/src/components/AutoCompleteSuggestions/index.tsx index 66ea0de6f9f3..baca4011a177 100644 --- a/src/components/AutoCompleteSuggestions/index.tsx +++ b/src/components/AutoCompleteSuggestions/index.tsx @@ -4,7 +4,6 @@ import {View} from 'react-native'; import useStyleUtils from '@hooks/useStyleUtils'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; -import {measureHeightOfSuggestionsContainer} from '@libs/SuggestionUtils'; import BaseAutoCompleteSuggestions from './BaseAutoCompleteSuggestions'; import type {AutoCompleteSuggestionsProps} from './types'; @@ -19,13 +18,11 @@ function AutoCompleteSuggestions({measureParentContainer = () => {} const StyleUtils = useStyleUtils(); const containerRef = React.useRef(null); const {windowHeight, windowWidth} = useWindowDimensions(); - const suggestionsContainerHeight = measureHeightOfSuggestionsContainer(props.suggestions.length, props.isSuggestionPickerLarge); const [{width, left, bottom}, setContainerState] = React.useState({ width: 0, left: 0, bottom: 0, }); - const [shouldShowBelowContainer, setShouldShowBelowContainer] = React.useState(false); React.useEffect(() => { const container = containerRef.current; if (!container) { @@ -44,19 +41,13 @@ function AutoCompleteSuggestions({measureParentContainer = () => {} if (!measureParentContainer) { return; } - - measureParentContainer((x, y, w, h) => { - const currentBottom = y < suggestionsContainerHeight ? windowHeight - y - suggestionsContainerHeight - h : windowHeight - y; - setShouldShowBelowContainer(y < suggestionsContainerHeight); - setContainerState({left: x, bottom: currentBottom, width: w}); - }); - }, [measureParentContainer, windowHeight, windowWidth, suggestionsContainerHeight]); + measureParentContainer((x, y, w) => setContainerState({left: x, bottom: windowHeight - y, width: w})); + }, [measureParentContainer, windowHeight, windowWidth]); const componentToRender = ( // eslint-disable-next-line react/jsx-props-no-spreading {...props} - shouldBeDisplayedBelowParentContainer={shouldShowBelowContainer} ref={containerRef} /> ); diff --git a/src/components/AutoCompleteSuggestions/types.ts b/src/components/AutoCompleteSuggestions/types.ts index d9824db1988d..61d614dcf2e4 100644 --- a/src/components/AutoCompleteSuggestions/types.ts +++ b/src/components/AutoCompleteSuggestions/types.ts @@ -1,6 +1,6 @@ import type {ReactElement} from 'react'; -type MeasureParentContainerCallback = (x: number, y: number, width: number, height: number) => void; +type MeasureParentContainerCallback = (x: number, y: number, width: number) => void; type RenderSuggestionMenuItemProps = { item: TSuggestion; @@ -33,9 +33,6 @@ type AutoCompleteSuggestionsProps = { /** Meaures the parent container's position and dimensions. */ measureParentContainer?: (callback: MeasureParentContainerCallback) => void; - - /** Whether suggestion should be displayed below the parent container or not */ - shouldBeDisplayedBelowParentContainer?: boolean; }; export type {AutoCompleteSuggestionsProps, RenderSuggestionMenuItemProps}; diff --git a/src/components/EmojiSuggestions.tsx b/src/components/EmojiSuggestions.tsx index c95288f43164..1c0306741048 100644 --- a/src/components/EmojiSuggestions.tsx +++ b/src/components/EmojiSuggestions.tsx @@ -9,7 +9,7 @@ import getStyledTextArray from '@libs/GetStyledTextArray'; import AutoCompleteSuggestions from './AutoCompleteSuggestions'; import Text from './Text'; -type MeasureParentContainerCallback = (x: number, y: number, width: number, height: number) => void; +type MeasureParentContainerCallback = (x: number, y: number, width: number) => void; type EmojiSuggestionsProps = { /** The index of the highlighted emoji */ diff --git a/src/libs/SuggestionUtils.ts b/src/libs/SuggestionUtils.ts index a38ba83a04bf..96379ce49ef3 100644 --- a/src/libs/SuggestionUtils.ts +++ b/src/libs/SuggestionUtils.ts @@ -20,25 +20,4 @@ function hasEnoughSpaceForLargeSuggestionMenu(listHeight: number, composerHeight return availableHeight > menuHeight; } -const measureHeightOfSuggestionsContainer = (numRows: number, isSuggestionsPickerLarge: boolean): number => { - // Autocomplete suggestions has inner padding 8px and border-width 1px - const borderAndPadding = CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTER_INNER_PADDING + 2; - let suggestionsHeight = 0; - - if (isSuggestionsPickerLarge) { - if (numRows > CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_VISIBLE_SUGGESTIONS_IN_CONTAINER) { - // On large screens, if there are more than 5 suggestions, we display a scrollable window with a height of 5 items, indicating that there are more items available - suggestionsHeight = CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_VISIBLE_SUGGESTIONS_IN_CONTAINER * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; - } else { - suggestionsHeight = numRows * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; - } - } else if (numRows > 2) { - // On small screens, we display a scrollable window with a height of 2.5 items, indicating that there are more items available beyond what is currently visible - suggestionsHeight = CONST.AUTO_COMPLETE_SUGGESTER.SMALL_CONTAINER_HEIGHT_FACTOR * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; - } else { - suggestionsHeight = numRows * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; - } - return suggestionsHeight + borderAndPadding; -}; - -export {trimLeadingSpace, hasEnoughSpaceForLargeSuggestionMenu, measureHeightOfSuggestionsContainer}; +export {trimLeadingSpace, hasEnoughSpaceForLargeSuggestionMenu}; diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx deleted file mode 100644 index c7510710828d..000000000000 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import type {Dispatch, ForwardedRef, RefObject, SetStateAction} from 'react'; -import React, {useState} from 'react'; -import type {MeasureInWindowOnSuccessCallback, TextInput} from 'react-native'; -import Composer from '@components/Composer'; -import type {ComposerProps} from '@components/Composer/types'; -import type {SuggestionsRef} from '@pages/home/report/ReportActionCompose/ReportActionCompose'; -import Suggestions from '@pages/home/report/ReportActionCompose/Suggestions'; - -type Selection = { - start: number; - end: number; -}; - -type ComposerWithSuggestionsEditProps = ComposerProps & { - setValue: Dispatch>; - setSelection: Dispatch>; - resetKeyboardInput: () => void; - isComposerFocused: boolean; - suggestionsRef: RefObject; - updateDraft: (newValue: string) => void; - measureParentContainer: (callback: MeasureInWindowOnSuccessCallback) => void; - value: string; - selection: Selection; - isGroupPolicyReport: boolean; -}; - -function ComposerWithSuggestionsEdit( - { - value, - maxLines = -1, - onKeyPress = () => {}, - style, - onSelectionChange = () => {}, - selection = { - start: 0, - end: 0, - }, - onBlur = () => {}, - onFocus = () => {}, - onChangeText = () => {}, - setValue = () => {}, - setSelection = () => {}, - resetKeyboardInput = () => {}, - isComposerFocused, - suggestionsRef, - updateDraft, - measureParentContainer, - id = undefined, - isGroupPolicyReport, - }: ComposerWithSuggestionsEditProps, - ref: ForwardedRef, -) { - const [composerHeight, setComposerHeight] = useState(0); - - return ( - <> - { - const composerLayoutHeight = e.nativeEvent.layout.height; - if (composerHeight === composerLayoutHeight) { - return; - } - setComposerHeight(composerLayoutHeight); - }} - /> - - - - ); -} - -export default React.forwardRef(ComposerWithSuggestionsEdit); diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/SuggestionsContext.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/SuggestionsContext.tsx deleted file mode 100644 index ceecb56af450..000000000000 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestionsEdit/SuggestionsContext.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import type {MutableRefObject, ReactNode} from 'react'; -import React, {createContext, useCallback, useContext, useMemo, useRef, useState} from 'react'; -import type {SuggestionsRef} from '@pages/home/report/ReportActionCompose/ReportActionCompose'; - -type SuggestionsContextProviderProps = { - children?: ReactNode; -}; - -type SuggestionsContextProps = { - activeID: string | null; - currentActiveSuggestionsRef: MutableRefObject; - updateCurrentActiveSuggestionsRef: (ref: SuggestionsRef | null, id: string) => void; - clearActiveSuggestionsRef: () => void; - isActiveSuggestions: (id: string) => boolean; -}; - -const SuggestionsContext = createContext({ - activeID: null, - currentActiveSuggestionsRef: {current: null}, - updateCurrentActiveSuggestionsRef: () => {}, - clearActiveSuggestionsRef: () => {}, - isActiveSuggestions: () => false, -}); - -function SuggestionsContextProvider({children}: SuggestionsContextProviderProps) { - const currentActiveSuggestionsRef = useRef(null); - const [activeID, setActiveID] = useState(null); - - const updateCurrentActiveSuggestionsRef = useCallback((ref: SuggestionsRef | null, id: string) => { - currentActiveSuggestionsRef.current = ref; - setActiveID(id); - }, []); - - const clearActiveSuggestionsRef = useCallback(() => { - currentActiveSuggestionsRef.current = null; - setActiveID(null); - }, []); - - const isActiveSuggestions = useCallback((id: string) => id === activeID, [activeID]); - - const contextValue = useMemo( - () => ({activeID, currentActiveSuggestionsRef, updateCurrentActiveSuggestionsRef, clearActiveSuggestionsRef, isActiveSuggestions}), - [activeID, currentActiveSuggestionsRef, updateCurrentActiveSuggestionsRef, clearActiveSuggestionsRef, isActiveSuggestions], - ); - - return {children}; -} - -function useSuggestionsContext() { - const context = useContext(SuggestionsContext); - return context; -} - -SuggestionsContextProvider.displayName = 'SuggestionsContextProvider'; - -export {SuggestionsContextProvider, useSuggestionsContext}; diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index 838e2466c6f3..b9b9025bb02b 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -61,7 +61,6 @@ type SuggestionsRef = { updateShouldShowSuggestionMenuToFalse: (shouldShowSuggestionMenu?: boolean) => void; setShouldBlockSuggestionCalc: (shouldBlock: boolean) => void; getSuggestions: () => Mention[] | Emoji[]; - updateShouldShowSuggestionMenuAfterScrolling: () => void; }; type ReportActionComposeOnyxProps = { @@ -379,7 +378,7 @@ function ReportActionCompose({ {shouldShowReportRecipientLocalTime && hasReportRecipient && } - + { - setSuggestionValues((prevState) => ({...prevState, shouldShowSuggestionMenu: !!prevState.suggestedEmojis.length})); - }, []); - /** * Listens for keyboard shortcuts and applies the action */ @@ -219,17 +215,8 @@ function SuggestionEmoji( setShouldBlockSuggestionCalc, updateShouldShowSuggestionMenuToFalse, getSuggestions, - updateShouldShowSuggestionMenuAfterScrolling, }), - [ - onSelectionChange, - resetSuggestions, - setShouldBlockSuggestionCalc, - triggerHotkeyActions, - updateShouldShowSuggestionMenuToFalse, - getSuggestions, - updateShouldShowSuggestionMenuAfterScrolling, - ], + [onSelectionChange, resetSuggestions, setShouldBlockSuggestionCalc, triggerHotkeyActions, updateShouldShowSuggestionMenuToFalse, getSuggestions], ); if (!isEmojiSuggestionsMenuVisible) { diff --git a/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx b/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx index 37032a2550fe..dbd550e1cd7c 100644 --- a/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx +++ b/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx @@ -360,10 +360,6 @@ function SuggestionMention( }); }, []); - const updateShouldShowSuggestionMenuAfterScrolling = useCallback(() => { - setSuggestionValues((prevState) => ({...prevState, shouldShowSuggestionMenu: !!prevState.suggestedMentions.length})); - }, []); - const setShouldBlockSuggestionCalc = useCallback( (shouldBlockSuggestionCalc: boolean) => { shouldBlockCalc.current = shouldBlockSuggestionCalc; @@ -381,9 +377,8 @@ function SuggestionMention( setShouldBlockSuggestionCalc, updateShouldShowSuggestionMenuToFalse, getSuggestions, - updateShouldShowSuggestionMenuAfterScrolling, }), - [resetSuggestions, setShouldBlockSuggestionCalc, triggerHotkeyActions, updateShouldShowSuggestionMenuToFalse, getSuggestions, updateShouldShowSuggestionMenuAfterScrolling], + [resetSuggestions, setShouldBlockSuggestionCalc, triggerHotkeyActions, updateShouldShowSuggestionMenuToFalse, getSuggestions], ); if (!isMentionSuggestionsMenuVisible) { diff --git a/src/pages/home/report/ReportActionCompose/Suggestions.tsx b/src/pages/home/report/ReportActionCompose/Suggestions.tsx index 288a8b1a6d81..8ebd52f62428 100644 --- a/src/pages/home/report/ReportActionCompose/Suggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/Suggestions.tsx @@ -127,11 +127,6 @@ function Suggestions( suggestionMentionRef.current?.updateShouldShowSuggestionMenuToFalse(); }, []); - const updateShouldShowSuggestionMenuAfterScrolling = useCallback(() => { - suggestionEmojiRef.current?.updateShouldShowSuggestionMenuAfterScrolling(); - suggestionMentionRef.current?.updateShouldShowSuggestionMenuAfterScrolling(); - }, []); - const setShouldBlockSuggestionCalc = useCallback((shouldBlock: boolean) => { suggestionEmojiRef.current?.setShouldBlockSuggestionCalc(shouldBlock); suggestionMentionRef.current?.setShouldBlockSuggestionCalc(shouldBlock); @@ -146,17 +141,8 @@ function Suggestions( updateShouldShowSuggestionMenuToFalse, setShouldBlockSuggestionCalc, getSuggestions, - updateShouldShowSuggestionMenuAfterScrolling, }), - [ - onSelectionChange, - resetSuggestions, - setShouldBlockSuggestionCalc, - triggerHotkeyActions, - updateShouldShowSuggestionMenuToFalse, - getSuggestions, - updateShouldShowSuggestionMenuAfterScrolling, - ], + [onSelectionChange, resetSuggestions, setShouldBlockSuggestionCalc, triggerHotkeyActions, updateShouldShowSuggestionMenuToFalse, getSuggestions], ); useEffect(() => { diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index efb2d8ba73fb..c07b693001e0 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -631,7 +631,6 @@ function ReportActionItem({ action={action} draftMessage={draftMessage} reportID={report.reportID} - isGroupPolicyReport={ReportUtils.isGroupPolicy(report)} index={index} ref={textInputRef} // Avoid defining within component due to an existing Onyx bug diff --git a/src/pages/home/report/ReportActionItemMessageEdit.tsx b/src/pages/home/report/ReportActionItemMessageEdit.tsx index aeb870406adf..fc3c92434fc4 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.tsx +++ b/src/pages/home/report/ReportActionItemMessageEdit.tsx @@ -1,12 +1,12 @@ -import {PortalHost} from '@gorhom/portal'; import ExpensiMark from 'expensify-common/lib/ExpensiMark'; import lodashDebounce from 'lodash/debounce'; import type {ForwardedRef} from 'react'; import React, {forwardRef, useCallback, useEffect, useMemo, useRef, useState} from 'react'; -import {DeviceEventEmitter, findNodeHandle, Keyboard, NativeModules, View} from 'react-native'; -import type {MeasureInWindowOnSuccessCallback, NativeSyntheticEvent, TextInput, TextInputFocusEventData, TextInputKeyPressEventData} from 'react-native'; +import {Keyboard, View} from 'react-native'; +import type {NativeSyntheticEvent, TextInput, TextInputFocusEventData, TextInputKeyPressEventData} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import type {Emoji} from '@assets/emojis/types'; +import Composer from '@components/Composer'; import EmojiPickerButton from '@components/EmojiPicker/EmojiPickerButton'; import ExceededCommentLength from '@components/ExceededCommentLength'; import Icon from '@components/Icon'; @@ -42,13 +42,8 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; import * as ReportActionContextMenu from './ContextMenu/ReportActionContextMenu'; -import ComposerWithSuggestionsEdit from './ReportActionCompose/ComposerWithSuggestionsEdit/ComposerWithSuggestionsEdit'; -import {useSuggestionsContext} from './ReportActionCompose/ComposerWithSuggestionsEdit/SuggestionsContext'; -import type {SuggestionsRef} from './ReportActionCompose/ReportActionCompose'; import shouldUseEmojiPickerSelection from './shouldUseEmojiPickerSelection'; -const {RNTextInputReset} = NativeModules; - type ReportActionItemMessageEditProps = { /** All the data of the action */ action: OnyxTypes.ReportAction; @@ -59,9 +54,6 @@ type ReportActionItemMessageEditProps = { /** ReportID that holds the comment we're editing */ reportID: string; - /** If current composer is connected with report from group policy */ - isGroupPolicyReport: boolean; - /** Position index of the report action in the overall report FlatList view */ index: number; @@ -80,7 +72,7 @@ const isMobileSafari = Browser.isMobileSafari(); const shouldUseForcedSelectionRange = shouldUseEmojiPickerSelection(); function ReportActionItemMessageEdit( - {action, draftMessage, reportID, isGroupPolicyReport, index, shouldDisableEmojiPicker = false, preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE}: ReportActionItemMessageEditProps, + {action, draftMessage, reportID, index, shouldDisableEmojiPicker = false, preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE}: ReportActionItemMessageEditProps, forwardedRef: ForwardedRef<(TextInput & HTMLTextAreaElement) | undefined>, ) { const theme = useTheme(); @@ -90,7 +82,6 @@ function ReportActionItemMessageEdit( const {translate, preferredLocale} = useLocalize(); const {isKeyboardShown} = useKeyboardState(); const {isSmallScreenWidth} = useWindowDimensions(); - const {updateCurrentActiveSuggestionsRef, clearActiveSuggestionsRef, isActiveSuggestions} = useSuggestionsContext(); const prevDraftMessage = usePrevious(draftMessage); const getInitialSelection = () => { @@ -121,8 +112,6 @@ function ReportActionItemMessageEdit( const isFocusedRef = useRef(false); const insertedEmojis = useRef([]); const draftRef = useRef(draft); - const containerRef = useRef(null); - const suggestionsRef = useRef(null); const emojiPickerSelectionRef = useRef(undefined); // The ref to check whether the comment saving is in progress const isCommentPendingSaved = useRef(false); @@ -186,7 +175,7 @@ function ReportActionItemMessageEdit( // and subsequent programmatic focus shifts (e.g., modal focus trap) to show the blue frame (:focus-visible style), // so we need to ensure that it is only updated after focus. if (isMobileSafari) { - setDraft((prevDraft: string) => { + setDraft((prevDraft) => { setSelection({ start: prevDraft.length, end: prevDraft.length, @@ -215,9 +204,6 @@ function ReportActionItemMessageEdit( if (ReportActionContextMenu.isActiveReportAction(action.reportActionID)) { ReportActionContextMenu.clearActiveReportAction(); } - if (isActiveSuggestions(action.reportActionID)) { - clearActiveSuggestionsRef(); - } // Show the main composer when the focused message is deleted from another client // to prevent the main composer stays hidden until we swtich to another chat. @@ -276,7 +262,6 @@ function ReportActionItemMessageEdit( if (emojis?.length > 0) { const newEmojis = EmojiUtils.getAddedEmojis(emojis, emojisPresentBefore.current); if (newEmojis?.length > 0) { - suggestionsRef.current?.resetSuggestions(); insertedEmojis.current = [...insertedEmojis.current, ...newEmojis]; debouncedUpdateFrequentlyUsedEmojis(); } @@ -374,10 +359,6 @@ function ReportActionItemMessageEdit( */ const triggerSaveOrCancel = useCallback( (e: NativeSyntheticEvent | KeyboardEvent) => { - if (suggestionsRef.current?.triggerHotkeyActions(e as KeyboardEvent)) { - return; - } - if (!e || ComposerUtils.canSkipTriggerHotkeys(isSmallScreenWidth, isKeyboardShown)) { return; } @@ -393,20 +374,6 @@ function ReportActionItemMessageEdit( [deleteDraft, isKeyboardShown, isSmallScreenWidth, publishDraft], ); - const resetKeyboardInput = useCallback(() => { - if (!RNTextInputReset) { - return; - } - RNTextInputReset.resetKeyboardInput(findNodeHandle(textInputRef.current)); - }, [textInputRef]); - - const measureContainer = useCallback((callback: MeasureInWindowOnSuccessCallback) => { - if (!containerRef.current) { - return; - } - containerRef.current.measureInWindow(callback); - }, []); - /** * Focus the composer text input */ @@ -416,34 +383,9 @@ function ReportActionItemMessageEdit( validateCommentMaxLength(draft); }, [draft, validateCommentMaxLength]); - /** - * Listen scrolling event - */ - useEffect(() => { - if (!isFocused || !suggestionsRef.current) { - return () => {}; - } - const scrollingListener = DeviceEventEmitter.addListener(CONST.EVENTS.SCROLLING, (scrolling) => { - if (scrolling) { - suggestionsRef?.current?.resetSuggestions(); - return; - } - // Reopen the suggestion after scroll has end - suggestionsRef?.current?.updateShouldShowSuggestionMenuAfterScrolling(); - }); - - return () => { - scrollingListener.remove(); - }; - }, [isFocused]); - return ( <> - - + - { textInputRef.current = el; @@ -503,33 +445,18 @@ function ReportActionItemMessageEdit( if (!ReportActionContextMenu.isActiveReportAction(action.reportActionID)) { ReportActionContextMenu.clearActiveReportAction(); } - - updateCurrentActiveSuggestionsRef(suggestionsRef.current, action.reportActionID); }} onBlur={(event: NativeSyntheticEvent) => { setIsFocused(false); // @ts-expect-error TODO: TextInputFocusEventData doesn't contain relatedTarget. const relatedTargetId = event.nativeEvent?.relatedTarget?.id; - suggestionsRef.current?.resetSuggestions(); - clearActiveSuggestionsRef(); if (relatedTargetId && [messageEditInput, emojiButtonID].includes(relatedTargetId)) { return; } setShouldShowComposeInputKeyboardAware(true); }} selection={selection} - onSelectionChange={(e) => { - suggestionsRef.current?.onSelectionChange?.(e); - setSelection(e.nativeEvent.selection); - }} - setValue={setDraft} - setSelection={setSelection} - isComposerFocused={!!textInputRef.current && textInputRef.current.isFocused()} - resetKeyboardInput={resetKeyboardInput} - suggestionsRef={suggestionsRef} - updateDraft={updateDraft} - measureParentContainer={measureContainer} - isGroupPolicyReport={isGroupPolicyReport} + onSelectionChange={(e) => setSelection(e.nativeEvent.selection)} /> diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index 8549af3ee483..3c6038697c67 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -33,7 +33,6 @@ import type {EmptyObject} from '@src/types/utils/EmptyObject'; import FloatingMessageCounter from './FloatingMessageCounter'; import getInitialNumToRender from './getInitialNumReportActionsToRender'; import ListBoundaryLoader from './ListBoundaryLoader'; -import {useSuggestionsContext} from './ReportActionCompose/ComposerWithSuggestionsEdit/SuggestionsContext'; import ReportActionsListItemRenderer from './ReportActionsListItemRenderer'; type LoadNewerChats = DebouncedFunc<(params: {distanceFromStart: number}) => void>; @@ -164,7 +163,6 @@ function ReportActionsList({ const reportScrollManager = useReportScrollManager(); const userActiveSince = useRef(null); const lastMessageTime = useRef(null); - const {currentActiveSuggestionsRef} = useSuggestionsContext(); const [isVisible, setIsVisible] = useState(false); const isFocused = useIsFocused(); @@ -650,18 +648,6 @@ function ReportActionsList({ onScrollToIndexFailed={onScrollToIndexFailed} extraData={extraData} key={listID} - onScrollBeginDrag={() => { - if (!currentActiveSuggestionsRef.current) { - return; - } - currentActiveSuggestionsRef.current.resetSuggestions(); - }} - onScrollEndDrag={() => { - if (!currentActiveSuggestionsRef.current) { - return; - } - currentActiveSuggestionsRef.current.updateShouldShowSuggestionMenuAfterScrolling(); - }} shouldEnableAutoScrollToTopThreshold={shouldEnableAutoScrollToTopThreshold} /> diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index e5e21be100e2..401187b91cd2 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -862,18 +862,17 @@ const shouldPreventScroll = shouldPreventScrollOnAutoCompleteSuggestion(); /** * Gets the correct position for auto complete suggestion container */ -function getAutoCompleteSuggestionContainerStyle(itemsHeight: number, shouldBeDisplayedBelowParentContainer: boolean, isEditComposer: boolean): ViewStyle { +function getAutoCompleteSuggestionContainerStyle(itemsHeight: number): ViewStyle { 'worklet'; const borderWidth = 2; const height = itemsHeight + 2 * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTER_INNER_PADDING + (shouldPreventScroll ? borderWidth : 0); - const suggestionsPadding = isEditComposer ? CONST.AUTO_COMPLETE_SUGGESTER.EDIT_SUGGESTER_PADDING : CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTER_PADDING; // The suggester is positioned absolutely within the component that includes the input and RecipientLocalTime view (for non-expanded mode only). To position it correctly, // we need to shift it by the suggester's height plus its padding and, if applicable, the height of the RecipientLocalTime view. return { overflow: 'hidden', - top: -(height + (shouldBeDisplayedBelowParentContainer ? -2 : 1) * (suggestionsPadding + (shouldPreventScroll ? 0 : borderWidth))), + top: -(height + CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTER_PADDING + (shouldPreventScroll ? 0 : borderWidth)), height, minHeight: CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT, }; From ba00db216f29996bfc084ff13d04073dbdf9abda Mon Sep 17 00:00:00 2001 From: OSBotify Date: Wed, 24 Apr 2024 12:03:46 +0000 Subject: [PATCH 03/14] Update version to 1.4.65-2 (cherry picked from commit 390bf24ff2b98059b2527e40931fdac3f595d31e) --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- ios/NotificationServiceExtension/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 0cb1ea6d7383..bb508ea9f4de 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -106,8 +106,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001046501 - versionName "1.4.65-1" + versionCode 1001046502 + versionName "1.4.65-2" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 7826883e1ab7..51b184b60b09 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.65.1 + 1.4.65.2 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 422f2c8e696e..bf439777846f 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.4.65.1 + 1.4.65.2 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 1d9557d9da7f..3ae43d0bc946 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 1.4.65 CFBundleVersion - 1.4.65.1 + 1.4.65.2 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index 07f406f4607e..054b669e1926 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.65-1", + "version": "1.4.65-2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.65-1", + "version": "1.4.65-2", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index e66cd9ca6cbd..9a9ed16f6234 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.65-1", + "version": "1.4.65-2", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From 357364f3c064f800d099b706b21eb3ab134ebb7e Mon Sep 17 00:00:00 2001 From: Vit Horacek <36083550+mountiny@users.noreply.github.com> Date: Wed, 24 Apr 2024 14:01:01 +0200 Subject: [PATCH 04/14] Merge pull request #40807 from Expensify/dsilva_changingPropertyBeingUsedByFullstory [CP Staging] Changing properties being used by Fullstory (cherry picked from commit 51372ab1171059ff514ffed1dc1511452aee6a57) --- src/ONYXKEYS.ts | 4 ++ src/libs/actions/Session/index.ts | 2 +- src/libs/fullstory/index.native.ts | 33 +++++++--------- src/libs/fullstory/index.ts | 61 ++++++++++++++++-------------- src/libs/fullstory/types.ts | 7 +--- src/types/onyx/UserMetadata.ts | 8 ++++ src/types/onyx/index.ts | 2 + 7 files changed, 62 insertions(+), 55 deletions(-) create mode 100644 src/types/onyx/UserMetadata.ts diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 45c2d152542d..c17c911d18b3 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -188,6 +188,9 @@ const ONYXKEYS = { /** User's Expensify Wallet */ USER_WALLET: 'userWallet', + /** User's metadata that will be used to segmentation */ + USER_METADATA: 'userMetadata', + /** Object containing Onfido SDK Token + applicantID */ WALLET_ONFIDO: 'walletOnfido', @@ -597,6 +600,7 @@ type OnyxValuesMapping = { [ONYXKEYS.USER_LOCATION]: OnyxTypes.UserLocation; [ONYXKEYS.LOGIN_LIST]: OnyxTypes.LoginList; [ONYXKEYS.SESSION]: OnyxTypes.Session; + [ONYXKEYS.USER_METADATA]: OnyxTypes.UserMetadata; [ONYXKEYS.STASHED_SESSION]: OnyxTypes.Session; [ONYXKEYS.BETAS]: OnyxTypes.Beta[]; [ONYXKEYS.NVP_PRIORITY_MODE]: ValueOf; diff --git a/src/libs/actions/Session/index.ts b/src/libs/actions/Session/index.ts index cef66487a438..aa83d14a1675 100644 --- a/src/libs/actions/Session/index.ts +++ b/src/libs/actions/Session/index.ts @@ -66,7 +66,7 @@ Onyx.connect({ }); Onyx.connect({ - key: ONYXKEYS.SESSION, + key: ONYXKEYS.USER_METADATA, callback: Fullstory.consentAndIdentify, }); diff --git a/src/libs/fullstory/index.native.ts b/src/libs/fullstory/index.native.ts index 548598bf60bc..67d7c7f2fe90 100644 --- a/src/libs/fullstory/index.native.ts +++ b/src/libs/fullstory/index.native.ts @@ -1,7 +1,6 @@ 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'; +import type {UserMetadata} from '@src/types/onyx'; /** * Fullstory React-Native lib adapter @@ -19,37 +18,31 @@ const FS = { consent: (c: boolean) => FullStory.consent(c), /** - * Initializes the FullStory session with the provided session information. + * Initializes the FullStory metadata with the provided metadata information. */ - consentAndIdentify: (value: OnyxEntry) => { + consentAndIdentify: (value: OnyxEntry) => { try { - const session: UserSession = { - email: value?.email, - accountID: value?.accountID, - }; - // set consent + // We only use FullStory in production environment FullStory.consent(true); - FS.fsIdentify(session); + FS.fsIdentify(value); } 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. + * 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: (session: UserSession) => { - if (!session || session.email === 'undefined') { - // anonymize FullStory user identity session + fsIdentify: (metadata: UserMetadata | null) => { + if (!metadata?.accountID) { + // anonymize FullStory user identity metadata FullStory.anonymize(); } else { // define FullStory user identity - FullStory.identify(String(session.accountID), { - properties: { - accountID: session.accountID, - }, + FullStory.identify(String(metadata.accountID), { + properties: metadata, }); } }, diff --git a/src/libs/fullstory/index.ts b/src/libs/fullstory/index.ts index 0cb849983c80..5b60f20f9ddf 100644 --- a/src/libs/fullstory/index.ts +++ b/src/libs/fullstory/index.ts @@ -1,7 +1,9 @@ import {FullStory, init, isInitialized} from '@fullstory/browser'; import type {OnyxEntry} from 'react-native-onyx'; -import type Session from '@src/types/onyx/Session'; -import type {NavigationProperties, UserSession} from './types'; +import CONST from '@src/CONST'; +import * as Environment from '@src/libs/Environment/Environment'; +import type {UserMetadata} from '@src/types/onyx'; +import type NavigationProperties from './types'; // Placeholder Browser API does not support Manual Page definition class FSPage { @@ -27,12 +29,17 @@ const FS = { */ onReady: () => new Promise((resolve) => { - // Initialised via HEAD snippet - if (isInitialized()) { - init({orgId: ''}, resolve); - } else { - FullStory('observe', {type: 'start', callback: 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}); + } + }); }), /** @@ -46,18 +53,18 @@ const FS = { consent: (c: boolean) => FullStory('setIdentity', {consent: c}), /** - * Initializes the FullStory session with the provided session information. + * Initializes the FullStory metadata with the provided metadata information. */ - consentAndIdentify: (value: OnyxEntry) => { + consentAndIdentify: (value: OnyxEntry) => { try { - FS.onReady().then(() => { - const session: UserSession = { - email: value?.email, - accountID: value?.accountID, - }; - // set consent - FS.consent(true); - FS.fsIdentify(session); + Environment.getEnvironment().then((envName: string) => { + if (CONST.ENVIRONMENT.PRODUCTION !== envName) { + return; + } + FS.onReady().then(() => { + FS.consent(true); + FS.fsIdentify(value); + }); }); } catch (e) { // error handler @@ -65,21 +72,19 @@ const FS = { }, /** - * Sets the FullStory user identity based on the provided session information. - * If the session does not contain an email, the user identity is anonymized. - * If the session contains an email, the user identity is defined with the email and account ID. + * Sets the FullStory user identity based on the provided metadata information. + * 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: (session: UserSession) => { - if (typeof session.email === 'undefined') { - // anonymize FullStory user identity session + fsIdentify: (metadata: UserMetadata | null) => { + if (!metadata?.accountID) { + // anonymize FullStory user identity metadata FS.anonymize(); } else { // define FullStory user identity FullStory('setIdentity', { - uid: String(session.accountID), - properties: { - accountID: session.accountID, - }, + uid: String(metadata.accountID), + properties: metadata, }); } }, diff --git a/src/libs/fullstory/types.ts b/src/libs/fullstory/types.ts index 386e35536d97..24878fd0fbd3 100644 --- a/src/libs/fullstory/types.ts +++ b/src/libs/fullstory/types.ts @@ -1,10 +1,5 @@ -type UserSession = { - email: string | undefined; - accountID: number | undefined; -}; - type NavigationProperties = { path: string; }; -export type {UserSession, NavigationProperties}; +export default NavigationProperties; diff --git a/src/types/onyx/UserMetadata.ts b/src/types/onyx/UserMetadata.ts new file mode 100644 index 000000000000..fc6490264087 --- /dev/null +++ b/src/types/onyx/UserMetadata.ts @@ -0,0 +1,8 @@ +type UserMetadata = { + planType?: string; + role?: string; + freeTrial?: boolean; + accountID?: number; +}; + +export default UserMetadata; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index ea0870a7b8c6..84a8d5c85c40 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -72,6 +72,7 @@ import type {TransactionViolation, ViolationName} from './TransactionViolation'; import type TransactionViolations from './TransactionViolation'; import type User from './User'; import type UserLocation from './UserLocation'; +import type UserMetadata from './UserMetadata'; import type UserWallet from './UserWallet'; import type WalletAdditionalDetails from './WalletAdditionalDetails'; import type {WalletAdditionalQuestionDetails} from './WalletAdditionalDetails'; @@ -155,6 +156,7 @@ export type { TransactionViolations, User, UserLocation, + UserMetadata, UserWallet, ViolationName, WalletAdditionalDetails, From 9e727d760e3b03f3ac07b12b923c6d3348205c23 Mon Sep 17 00:00:00 2001 From: Oleksii Vagin Date: Wed, 8 May 2024 16:07:35 +0100 Subject: [PATCH 05/14] Changes: - IOs FullStory pod updated to version "1.47.0" - babel-plugin-react-native version updated to version "1.2.1" - Video preview, Attachment Preview fs-exclude class added --- android/app/build.gradle | 1 + android/build.gradle | 2 +- babel.config.js | 10 +---- ios/NewExpensify.xcodeproj/project.pbxproj | 19 ++++++++ ios/Podfile | 2 + ios/Podfile.lock | 17 ++++--- package-lock.json | 7 +-- package.json | 1 + .../AttachmentViewVideo/index.tsx | 2 + .../Attachments/AttachmentView/index.tsx | 45 +++++++++++++------ .../HTMLRenderers/VideoRenderer.tsx | 1 + .../VideoPlayer/BaseVideoPlayer.tsx | 14 ++++-- src/components/VideoPlayer/index.native.tsx | 1 + src/components/VideoPlayer/index.tsx | 1 + src/components/VideoPlayer/types.ts | 1 + src/components/VideoPlayerPreview/index.tsx | 10 ++++- 16 files changed, 98 insertions(+), 36 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index bb508ea9f4de..45d0f103c36e 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -14,6 +14,7 @@ apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.grad fullstory { org 'o-1WN56P-na1' enabledVariants 'all' + logcatLevel 'debug' } react { diff --git a/android/build.gradle b/android/build.gradle index 7ecd482b38f0..52c998998ba0 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -29,7 +29,7 @@ buildscript { 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") + classpath ("com.fullstory:gradle-plugin-local:1.47.0") // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/babel.config.js b/babel.config.js index 0660cdb452fb..5f987dd9d273 100644 --- a/babel.config.js +++ b/babel.config.js @@ -15,14 +15,8 @@ const defaultPlugins = [ 'transform-class-properties', /* Fullstory */ - [ - '@fullstory/react-native', - { - version: '1.4.0', - org: 'o-1WN56P-na1', - enabledVariants: 'all', - }, - ], + '@fullstory/react-native', + [ '@fullstory/babel-plugin-annotate-react', { diff --git a/ios/NewExpensify.xcodeproj/project.pbxproj b/ios/NewExpensify.xcodeproj/project.pbxproj index e7ce320f65d6..304313a873c0 100644 --- a/ios/NewExpensify.xcodeproj/project.pbxproj +++ b/ios/NewExpensify.xcodeproj/project.pbxproj @@ -416,6 +416,7 @@ F6E16E41F88F567A8CDD037C /* [CP] Copy Pods Resources */, 04A2B3BE14CFE4961BE987E8 /* [CP-User] [RNFB] Core Configuration */, 2D8F47B51A8E72FBA2BA4874 /* [CP-User] [RNFB] Crashlytics Configuration */, + 498A08C52BDA6A4C003A0F83 /* Run FullStory Asset Uploader */, ); buildRules = ( ); @@ -617,6 +618,24 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + 498A08C52BDA6A4C003A0F83 /* Run FullStory Asset Uploader */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Run FullStory Asset Uploader"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/FullStory/tools/FullStoryCommandLine\" \"${CONFIGURATION_BUILD_DIR}/${WRAPPER_NAME}\"\n"; + }; 5CC6761AF98472E1C710DB80 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; diff --git a/ios/Podfile b/ios/Podfile index 4f00eb2adfdd..2f7a3869edbd 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -117,3 +117,5 @@ end target 'NotificationServiceExtension' do pod 'AirshipServiceExtension' end + +pod 'FullStory', :http => 'https://ios-releases.fullstory.com/fullstory-1.47.0-xcframework.tar.gz' \ No newline at end of file diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 6ef495a65bd3..729316dbf335 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -138,7 +138,7 @@ PODS: - GoogleUtilities/Environment (~> 7.7) - "GoogleUtilities/NSData+zlib (~> 7.7)" - fmt (6.2.1) - - FullStory (1.43.1) + - FullStory (1.47.0) - fullstory_react-native (1.4.2): - FullStory (~> 1.14) - glog @@ -2083,6 +2083,8 @@ DEPENDENCIES: - ExpoImageManipulator (from `../node_modules/expo-image-manipulator/ios`) - ExpoModulesCore (from `../node_modules/expo-modules-core`) - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) + - "FullStory (from `{:http=>\"https://ios-releases.fullstory.com/fullstory-1.47.0-xcframework.tar.gz\"}`)" + - "fullstory_react-native (from `../node_modules/@fullstory/react-native`)" - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) - hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) - libevent (~> 2.1.12) @@ -2197,7 +2199,6 @@ SPEC REPOS: - FirebasePerformance - FirebaseRemoteConfig - fmt - - FullStory - GoogleAppMeasurement - GoogleDataTransport - GoogleSignIn @@ -2246,10 +2247,10 @@ EXTERNAL SOURCES: :path: "../node_modules/expo-modules-core" FBLazyVector: :path: "../node_modules/react-native/Libraries/FBLazyVector" + FullStory: + :http: https://ios-releases.fullstory.com/fullstory-1.47.0-xcframework.tar.gz 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: @@ -2440,6 +2441,10 @@ EXTERNAL SOURCES: Yoga: :path: "../node_modules/react-native/ReactCommon/yoga" +CHECKOUT OPTIONS: + FullStory: + :http: https://ios-releases.fullstory.com/fullstory-1.47.0-xcframework.tar.gz + SPEC CHECKSUMS: Airship: 5a6d3f8a982398940b0d48423bb9b8736717c123 AirshipFrameworkProxy: 7255f4ed9836dc2920f2f1ea5657ced4cee8a35c @@ -2465,7 +2470,7 @@ SPEC CHECKSUMS: FirebasePerformance: 0c01a7a496657d7cea86d40c0b1725259d164c6c FirebaseRemoteConfig: 2d6e2cfdb49af79535c8af8a80a4a5009038ec2b fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 - FullStory: e035758fef275fb59c6471f61b179652aeca452b + FullStory: c1233361b8e3c599e14609b4c516c5a8d323c0ea fullstory_react-native: a56e2bb52753b69f01aab3ae876087db08488034 glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2 GoogleAppMeasurement: 5ba1164e3c844ba84272555e916d0a6d3d977e91 @@ -2588,6 +2593,6 @@ SPEC CHECKSUMS: VisionCamera: 3033e0dd5272d46e97bcb406adea4ae0e6907abf Yoga: 64cd2a583ead952b0315d5135bf39e053ae9be70 -PODFILE CHECKSUM: a25a81f2b50270f0c0bd0aff2e2ebe4d0b4ec06d +PODFILE CHECKSUM: abcf5707e90d79fab2fdfe8879a819e9f1108c9a COCOAPODS: 1.13.0 diff --git a/package-lock.json b/package-lock.json index 054b669e1926..8361513802cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@formatjs/intl-locale": "^3.3.0", "@formatjs/intl-numberformat": "^8.5.0", "@formatjs/intl-pluralrules": "^5.2.2", + "@fullstory/babel-plugin-react-native": "^1.2.1", "@fullstory/browser": "^2.0.3", "@fullstory/react-native": "^1.4.0", "@gorhom/portal": "^1.0.14", @@ -5596,9 +5597,9 @@ "integrity": "sha512-gYLUL6Tu0exbvTIhK9nSCaztmqBlQAm07Fvtl/nKTc+lxwFkcX9vR8RrdTbyjJZKbPaA5EMlExQ6GeLCXkfm5g==" }, "node_modules/@fullstory/babel-plugin-react-native": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@fullstory/babel-plugin-react-native/-/babel-plugin-react-native-1.1.0.tgz", - "integrity": "sha512-BqfSUdyrrYrZM286GzdHd3qCdbitxUAIM0Z+HpoOTGWVTLDpkFNNaRw5juq8YhYbcPm6BAtK0RMGY7CvcMNarA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@fullstory/babel-plugin-react-native/-/babel-plugin-react-native-1.2.1.tgz", + "integrity": "sha512-EMAgoPOo+31eppHxQf05oAGhKKTem7rw8GHDdbNJF0c5dQWzBGNVgF72TPjcxES91UI6hbss2eqoVOhUttLEoQ==", "dependencies": { "@babel/parser": "^7.0.0", "@babel/types": "^7.0.0" diff --git a/package.json b/package.json index 9a9ed16f6234..bad6fecc44dd 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,7 @@ "@formatjs/intl-locale": "^3.3.0", "@formatjs/intl-numberformat": "^8.5.0", "@formatjs/intl-pluralrules": "^5.2.2", + "@fullstory/babel-plugin-react-native": "^1.2.1", "@fullstory/browser": "^2.0.3", "@fullstory/react-native": "^1.4.0", "@gorhom/portal": "^1.0.14", diff --git a/src/components/Attachments/AttachmentView/AttachmentViewVideo/index.tsx b/src/components/Attachments/AttachmentView/AttachmentViewVideo/index.tsx index 9e91bfd64fed..2665ea44385e 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewVideo/index.tsx +++ b/src/components/Attachments/AttachmentView/AttachmentViewVideo/index.tsx @@ -8,6 +8,8 @@ type AttachmentViewVideoProps = Pick + + fsClass="fs-exclude" + source={source} + shouldUseSharedVideoElement={isUsedInCarousel} + isHovered={isHovered} + duration={duration} + /> ); } return ( - - + + - {file?.name} + {file?.name} {!shouldShowLoadingSpinnerIcon && shouldShowDownloadIcon && ( - - + + )} {shouldShowLoadingSpinnerIcon && ( - - + + diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/VideoRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/VideoRenderer.tsx index 6802a4518ba1..6fa1977a0c8b 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/VideoRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/VideoRenderer.tsx @@ -27,6 +27,7 @@ function VideoRenderer({tnode, key}: VideoRendererProps) { {({report}) => ( {(isHovered) => ( - + } style={[styles.flex1]} /> {/* We are adding transparent absolute View between appended video component and control buttons to enable catching onMouse events from Attachment Carousel. Due to late appending React doesn't handle element's events properly. */} - + ) : ( { if (!el) { @@ -325,6 +330,7 @@ function BaseVideoPlayer({ }} > diff --git a/src/components/VideoPlayer/types.ts b/src/components/VideoPlayer/types.ts index d27b1dcdbcdd..49cb19c98631 100644 --- a/src/components/VideoPlayer/types.ts +++ b/src/components/VideoPlayer/types.ts @@ -8,6 +8,7 @@ import type CONST from '@src/CONST'; type VideoWithOnFullScreenUpdate = Video & {_onFullscreenUpdate: (event: VideoFullscreenUpdateEvent) => void}; type VideoPlayerProps = { + fsClass: string; url: string; onVideoLoaded?: (event: VideoReadyForDisplayEvent) => void; resizeMode?: string; diff --git a/src/components/VideoPlayerPreview/index.tsx b/src/components/VideoPlayerPreview/index.tsx index 414e95b0ff32..b581fb9b504a 100644 --- a/src/components/VideoPlayerPreview/index.tsx +++ b/src/components/VideoPlayerPreview/index.tsx @@ -18,6 +18,7 @@ type VideoDimensions = { }; type VideoPlayerPreviewProps = { + fsClass: string; /** Url to a video. */ videoUrl: string; @@ -70,7 +71,9 @@ function VideoPlayerPreview({videoUrl, thumbnailUrl, reportID, fileName, videoDi }, [currentlyPlayingURL, currentlyPlayingURLReportID, updateCurrentlyPlayingURL, videoUrl, reportID]); return ( - + {isSmallScreenWidth || isThumbnail ? ( void} videoDuration={videoDuration} shouldUseSmallVideoControls style={[styles.w100, styles.h100]} /> - + Date: Wed, 8 May 2024 21:48:43 +0100 Subject: [PATCH 06/14] Changes: - Latest fixes --- babel.config.js | 22 +++++++++---------- ios/NewExpensify.xcodeproj/project.pbxproj | 22 ++++--------------- ios/Podfile.lock | 7 +++--- .../VideoPlayer/BaseVideoPlayer.tsx | 5 +---- src/components/VideoPlayerPreview/index.tsx | 4 +++- 5 files changed, 22 insertions(+), 38 deletions(-) diff --git a/babel.config.js b/babel.config.js index 5f987dd9d273..3023d37df7e0 100644 --- a/babel.config.js +++ b/babel.config.js @@ -14,17 +14,6 @@ const defaultPlugins = [ // source code transformation as we do not use class property assignment. 'transform-class-properties', - /* Fullstory */ - '@fullstory/react-native', - - [ - '@fullstory/babel-plugin-annotate-react', - { - native: true, - setFSTagName: true, - }, - ], - // Keep it last 'react-native-reanimated/plugin', ]; @@ -46,6 +35,17 @@ const metro = { ['@babel/plugin-proposal-private-property-in-object', {loose: true}], // The reanimated babel plugin needs to be last, as stated here: https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/installation 'react-native-reanimated/plugin', + + /* Fullstory */ + '@fullstory/react-native', + [ + '@fullstory/babel-plugin-annotate-react', + { + native: true, + setFSTagName: true, + }, + ], + // Import alias for native devices [ 'module-resolver', diff --git a/ios/NewExpensify.xcodeproj/project.pbxproj b/ios/NewExpensify.xcodeproj/project.pbxproj index 7f5d88819b0e..9e4fcaef55dd 100644 --- a/ios/NewExpensify.xcodeproj/project.pbxproj +++ b/ios/NewExpensify.xcodeproj/project.pbxproj @@ -584,24 +584,6 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 498A08C52BDA6A4C003A0F83 /* Run FullStory Asset Uploader */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Run FullStory Asset Uploader"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/FullStory/tools/FullStoryCommandLine\" \"${CONFIGURATION_BUILD_DIR}/${WRAPPER_NAME}\"\n"; - }; 47017CF8C1CFE59999D45CDC /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -666,6 +648,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", @@ -677,6 +660,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", @@ -698,6 +682,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", @@ -709,6 +694,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", diff --git a/ios/Podfile.lock b/ios/Podfile.lock index b3912be7bf18..36140864afaf 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -2214,7 +2214,6 @@ SPEC REPOS: - FirebasePerformance - FirebaseRemoteConfig - fmt - - FullStory - GoogleAppMeasurement - GoogleDataTransport - GoogleSignIn @@ -2487,7 +2486,7 @@ SPEC CHECKSUMS: FirebaseRemoteConfig: 2d6e2cfdb49af79535c8af8a80a4a5009038ec2b fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 FullStory: c1233361b8e3c599e14609b4c516c5a8d323c0ea - fullstory_react-native: a56e2bb52753b69f01aab3ae876087db08488034 + fullstory_react-native: 6cba8a2c054374a24a44dc4310407d9435459cae glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2 GoogleAppMeasurement: 5ba1164e3c844ba84272555e916d0a6d3d977e91 GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a @@ -2607,8 +2606,8 @@ SPEC CHECKSUMS: SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Turf: 13d1a92d969ca0311bbc26e8356cca178ce95da2 VisionCamera: 1394a316c7add37e619c48d7aa40b38b954bf055 - Yoga: 1b901a6d6eeba4e8a2e8f308f708691cdb5db312 + Yoga: 64cd2a583ead952b0315d5135bf39e053ae9be70 -PODFILE CHECKSUM: a25a81f2b50270f0c0bd0aff2e2ebe4d0b4ec06d +PODFILE CHECKSUM: abcf5707e90d79fab2fdfe8879a819e9f1108c9a COCOAPODS: 1.13.0 diff --git a/src/components/VideoPlayer/BaseVideoPlayer.tsx b/src/components/VideoPlayer/BaseVideoPlayer.tsx index 07fbbd8a76c1..0cb4a70da673 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.tsx +++ b/src/components/VideoPlayer/BaseVideoPlayer.tsx @@ -292,7 +292,6 @@ function BaseVideoPlayer({ {(isHovered) => ( } style={[styles.flex1]} /> {/* We are adding transparent absolute View between appended video component and control buttons to enable catching onMouse events from Attachment Carousel. Due to late appending React doesn't handle element's events properly. */} - + ) : ( diff --git a/src/components/VideoPlayer/types.ts b/src/components/VideoPlayer/types.ts index 49cb19c98631..d27b1dcdbcdd 100644 --- a/src/components/VideoPlayer/types.ts +++ b/src/components/VideoPlayer/types.ts @@ -8,7 +8,6 @@ import type CONST from '@src/CONST'; type VideoWithOnFullScreenUpdate = Video & {_onFullscreenUpdate: (event: VideoFullscreenUpdateEvent) => void}; type VideoPlayerProps = { - fsClass: string; url: string; onVideoLoaded?: (event: VideoReadyForDisplayEvent) => void; resizeMode?: string; diff --git a/src/components/VideoPlayerPreview/index.tsx b/src/components/VideoPlayerPreview/index.tsx index d1014845c885..2db6862c0317 100644 --- a/src/components/VideoPlayerPreview/index.tsx +++ b/src/components/VideoPlayerPreview/index.tsx @@ -18,7 +18,6 @@ type VideoDimensions = { }; type VideoPlayerPreviewProps = { - fsClass: string; /** Url to a video. */ videoUrl: string; @@ -72,7 +71,6 @@ function VideoPlayerPreview({videoUrl, thumbnailUrl, reportID, fileName, videoDi return ( {isSmallScreenWidth || isThumbnail ? ( ) : ( void} videoDuration={videoDuration} @@ -94,7 +90,6 @@ function VideoPlayerPreview({videoUrl, thumbnailUrl, reportID, fileName, videoDi videoPlayerStyle={styles.videoPlayerPreview} /> Date: Mon, 13 May 2024 16:43:26 +0100 Subject: [PATCH 08/14] Changes: - Cleanup eslint/prettier - Added Fullstory lib to TSX (View Properties extend attributes) --- src/App.tsx | 1 + .../Attachments/AttachmentView/index.tsx | 35 +++++++------------ .../VideoPlayer/BaseVideoPlayer.tsx | 3 +- src/components/VideoPlayerPreview/index.tsx | 9 ++--- 4 files changed, 18 insertions(+), 30 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 6316fa80fba1..6bd1a2da40f4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,3 +1,4 @@ +import fullstory from '@fullstory/react-native'; import {PortalProvider} from '@gorhom/portal'; import React from 'react'; import {LogBox} from 'react-native'; diff --git a/src/components/Attachments/AttachmentView/index.tsx b/src/components/Attachments/AttachmentView/index.tsx index c64a7dd37882..2685a5cef407 100644 --- a/src/components/Attachments/AttachmentView/index.tsx +++ b/src/components/Attachments/AttachmentView/index.tsx @@ -132,8 +132,7 @@ function AttachmentView({ if (TransactionUtils.hasEReceipt(transaction) && transaction) { return ( - + + source={source} + shouldUseSharedVideoElement={isUsedInCarousel} + isHovered={isHovered} + duration={duration} + /> ); } return ( - - + + - {file?.name} + {file?.name} {!shouldShowLoadingSpinnerIcon && shouldShowDownloadIcon && ( - - + + )} {shouldShowLoadingSpinnerIcon && ( - - + + {(isHovered) => ( - + + {isSmallScreenWidth || isThumbnail ? ( ) : ( - + void} @@ -89,8 +87,7 @@ function VideoPlayerPreview({videoUrl, thumbnailUrl, reportID, fileName, videoDi style={[styles.w100, styles.h100]} videoPlayerStyle={styles.videoPlayerPreview} /> - + Date: Mon, 13 May 2024 20:05:27 +0100 Subject: [PATCH 09/14] Changes: - Cleanup eslint/prettier - Merge conflicts rollback deleted folder/files - Added Fullstory lib to TSX (View Properties extend attributes) --- jest/setup.ts | 2 + jest/setupMockFullstoryLib.ts | 24 +++++++ src/App.tsx | 3 + src/libs/Fullstory/index.native.ts | 60 +++++++++++++++++ src/libs/Fullstory/index.ts | 90 ++++++++++++++++++++++++++ src/libs/Fullstory/types.ts | 10 +++ src/libs/Navigation/NavigationRoot.tsx | 7 ++ src/libs/actions/Session/index.ts | 6 ++ 8 files changed, 202 insertions(+) create mode 100644 jest/setupMockFullstoryLib.ts create mode 100644 src/libs/Fullstory/index.native.ts create mode 100644 src/libs/Fullstory/index.ts create mode 100644 src/libs/Fullstory/types.ts diff --git a/jest/setup.ts b/jest/setup.ts index 488e3e36a1d3..b024d687b054 100644 --- a/jest/setup.ts +++ b/jest/setup.ts @@ -3,8 +3,10 @@ import 'react-native-gesture-handler/jestSetup'; import mockStorage from 'react-native-onyx/dist/storage/__mocks__'; import 'setimmediate'; import setupMockImages from './setupMockImages'; +import mockFSLibrary from './setupMockFullstoryLib'; setupMockImages(); +mockFSLibrary(); // This mock is required as per setup instructions for react-navigation testing // https://reactnavigation.org/docs/testing/#mocking-native-modules diff --git a/jest/setupMockFullstoryLib.ts b/jest/setupMockFullstoryLib.ts new file mode 100644 index 000000000000..9edfccab9441 --- /dev/null +++ b/jest/setupMockFullstoryLib.ts @@ -0,0 +1,24 @@ +type FSPageInterface = { + start: jest.Mock; +}; + +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, + }; + }); +} diff --git a/src/App.tsx b/src/App.tsx index 6bd1a2da40f4..cde31f0d61a1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -101,4 +101,7 @@ function App({url}: AppProps) { App.displayName = 'App'; +// TSX View Component properties extended +fullstory?.LogLevel?.Log; + export default App; diff --git a/src/libs/Fullstory/index.native.ts b/src/libs/Fullstory/index.native.ts new file mode 100644 index 000000000000..e1781e6fed2d --- /dev/null +++ b/src/libs/Fullstory/index.native.ts @@ -0,0 +1,60 @@ +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) => { + 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: { + displayName: session.email, + email: session.email, + }, + }); + } + }, +}; + +export default FS; +export {FSPage}; diff --git a/src/libs/Fullstory/index.ts b/src/libs/Fullstory/index.ts new file mode 100644 index 000000000000..3f42005e859b --- /dev/null +++ b/src/libs/Fullstory/index.ts @@ -0,0 +1,90 @@ +import {FullStory, init, isInitialized} from '@fullstory/browser'; +import type {OnyxEntry} from 'react-native-onyx'; +import type Session from '@src/types/onyx/Session'; +import type {NavigationProperties, UserSession} from './types'; + +// Placeholder Browser API does not support Manual Page definition +class FSPage { + private pageName; + + private properties; + + constructor(name: string, properties: NavigationProperties) { + this.pageName = name; + this.properties = properties; + } + + start() {} +} + +/** + * Web does not use Fullstory React-Native lib + * Proxy function calls to Browser Snippet instance + * */ +const FS = { + /** + * Executes a function when the FullStory library is ready, either by initialization or by observing the start event. + */ + onReady: () => + new Promise((resolve) => { + // Initialised via HEAD snippet + if (isInitialized()) { + init({orgId: ''}, resolve); + } else { + FullStory('observe', {type: 'start', callback: resolve}); + } + }), + + /** + * Sets the identity as anonymous using the FullStory library. + */ + anonymize: () => FullStory('setIdentity', {anonymous: true}), + + /** + * Sets the identity consent status using the FullStory library. + */ + consent: (c: boolean) => FullStory('setIdentity', {consent: c}), + + /** + * Initializes the FullStory session with the provided session information. + */ + consentAndIdentify: (value: OnyxEntry) => { + try { + FS.onReady().then(() => { + const session: UserSession = { + email: value?.email, + accountID: value?.accountID, + }; + // set consent + FS.consent(true); + FS.fsIdentify(session); + }); + } catch (e) { + // error handler + } + }, + + /** + * Sets the FullStory user identity based on the provided session information. + * If the session does not contain an email, 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 (typeof session.email === 'undefined') { + // anonymize FullStory user identity session + FS.anonymize(); + } else { + // define FullStory user identity + FullStory('setIdentity', { + uid: String(session.accountID), + properties: { + displayName: session.email, + email: session.email, + }, + }); + } + }, +}; + +export default FS; +export {FSPage}; diff --git a/src/libs/Fullstory/types.ts b/src/libs/Fullstory/types.ts new file mode 100644 index 000000000000..386e35536d97 --- /dev/null +++ b/src/libs/Fullstory/types.ts @@ -0,0 +1,10 @@ +type UserSession = { + email: string | undefined; + accountID: number | undefined; +}; + +type NavigationProperties = { + path: string; +}; + +export type {UserSession, NavigationProperties}; diff --git a/src/libs/Navigation/NavigationRoot.tsx b/src/libs/Navigation/NavigationRoot.tsx index 506eae2bdfd2..e06e4da57da3 100644 --- a/src/libs/Navigation/NavigationRoot.tsx +++ b/src/libs/Navigation/NavigationRoot.tsx @@ -1,3 +1,4 @@ +import {FSPage} from '@libs/Fullstory'; import type {NavigationState} from '@react-navigation/native'; import {DefaultTheme, findFocusedRoute, NavigationContainer} from '@react-navigation/native'; import React, {useContext, useEffect, useMemo, useRef} from 'react'; @@ -57,6 +58,12 @@ function parseAndLogRoute(state: NavigationState) { } Navigation.setIsNavigationReady(); + + // Fullstory Page navigation tracking + const focusedRouteName = focusedRoute?.name; + if(focusedRouteName){ + new FSPage(focusedRoute?.name ?? '', {path: currentPath}).start(); + } } function NavigationRoot({authenticated, lastVisitedPath, initialUrl, onReady}: NavigationRootProps) { diff --git a/src/libs/actions/Session/index.ts b/src/libs/actions/Session/index.ts index 9fc1485cd6e5..5dc66fcac43a 100644 --- a/src/libs/actions/Session/index.ts +++ b/src/libs/actions/Session/index.ts @@ -3,6 +3,7 @@ import type {ChannelAuthorizationData} from 'pusher-js/types/src/core/auth/optio import type {ChannelAuthorizationCallback} from 'pusher-js/with-encryption'; import {InteractionManager, Linking, NativeModules} from 'react-native'; import type {OnyxEntry, OnyxUpdate} from 'react-native-onyx'; +import Fullstory from '@libs/Fullstory'; import Onyx from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import * as PersistedRequests from '@libs/actions/PersistedRequests'; @@ -64,6 +65,11 @@ Onyx.connect({ }, }); +Onyx.connect({ + key: ONYXKEYS.SESSION, + callback: Fullstory.consentAndIdentify, +}); + let stashedSession: Session = {}; Onyx.connect({ key: ONYXKEYS.STASHED_SESSION, From f55fb9918197d2ae1092c30c136e120a7823f665 Mon Sep 17 00:00:00 2001 From: Oleksii Vagin Date: Tue, 14 May 2024 09:06:45 +0100 Subject: [PATCH 10/14] Changes: - Cleanup eslint/prettier - Added TSX fullstory file to trigger types overrides --- src/App.tsx | 4 ---- src/components/FullstoryNative.tsx | 3 +++ src/libs/Navigation/NavigationRoot.tsx | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) create mode 100644 src/components/FullstoryNative.tsx diff --git a/src/App.tsx b/src/App.tsx index cde31f0d61a1..6316fa80fba1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,3 @@ -import fullstory from '@fullstory/react-native'; import {PortalProvider} from '@gorhom/portal'; import React from 'react'; import {LogBox} from 'react-native'; @@ -101,7 +100,4 @@ function App({url}: AppProps) { App.displayName = 'App'; -// TSX View Component properties extended -fullstory?.LogLevel?.Log; - export default App; diff --git a/src/components/FullstoryNative.tsx b/src/components/FullstoryNative.tsx new file mode 100644 index 000000000000..f218f366c072 --- /dev/null +++ b/src/components/FullstoryNative.tsx @@ -0,0 +1,3 @@ +import FS from '@fullstory/react-native'; + +export default FS; \ No newline at end of file diff --git a/src/libs/Navigation/NavigationRoot.tsx b/src/libs/Navigation/NavigationRoot.tsx index e06e4da57da3..ffaa13a4e2af 100644 --- a/src/libs/Navigation/NavigationRoot.tsx +++ b/src/libs/Navigation/NavigationRoot.tsx @@ -62,7 +62,7 @@ function parseAndLogRoute(state: NavigationState) { // Fullstory Page navigation tracking const focusedRouteName = focusedRoute?.name; if(focusedRouteName){ - new FSPage(focusedRoute?.name ?? '', {path: currentPath}).start(); + new FSPage(focusedRouteName, {path: currentPath}).start(); } } From 2f3586966561c5c8670224e0c71e664d7632bddc Mon Sep 17 00:00:00 2001 From: Oleksii Vagin Date: Fri, 17 May 2024 17:33:41 +0100 Subject: [PATCH 11/14] Changes: - Fixes eslint/prettier --- jest/setup.ts | 2 +- src/components/FullstoryNative.tsx | 2 +- src/libs/Navigation/NavigationRoot.tsx | 4 ++-- src/libs/actions/Session/index.ts | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/jest/setup.ts b/jest/setup.ts index b024d687b054..174e59a7e493 100644 --- a/jest/setup.ts +++ b/jest/setup.ts @@ -2,8 +2,8 @@ import '@shopify/flash-list/jestSetup'; import 'react-native-gesture-handler/jestSetup'; import mockStorage from 'react-native-onyx/dist/storage/__mocks__'; import 'setimmediate'; -import setupMockImages from './setupMockImages'; import mockFSLibrary from './setupMockFullstoryLib'; +import setupMockImages from './setupMockImages'; setupMockImages(); mockFSLibrary(); diff --git a/src/components/FullstoryNative.tsx b/src/components/FullstoryNative.tsx index f218f366c072..63e6217a464e 100644 --- a/src/components/FullstoryNative.tsx +++ b/src/components/FullstoryNative.tsx @@ -1,3 +1,3 @@ import FS from '@fullstory/react-native'; -export default FS; \ No newline at end of file +export default FS; diff --git a/src/libs/Navigation/NavigationRoot.tsx b/src/libs/Navigation/NavigationRoot.tsx index ffaa13a4e2af..06a3dce8d59a 100644 --- a/src/libs/Navigation/NavigationRoot.tsx +++ b/src/libs/Navigation/NavigationRoot.tsx @@ -1,4 +1,3 @@ -import {FSPage} from '@libs/Fullstory'; import type {NavigationState} from '@react-navigation/native'; import {DefaultTheme, findFocusedRoute, NavigationContainer} from '@react-navigation/native'; import React, {useContext, useEffect, useMemo, useRef} from 'react'; @@ -7,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'; @@ -61,7 +61,7 @@ function parseAndLogRoute(state: NavigationState) { // Fullstory Page navigation tracking const focusedRouteName = focusedRoute?.name; - if(focusedRouteName){ + if (focusedRouteName) { new FSPage(focusedRouteName, {path: currentPath}).start(); } } diff --git a/src/libs/actions/Session/index.ts b/src/libs/actions/Session/index.ts index 5dc66fcac43a..6141dba190bf 100644 --- a/src/libs/actions/Session/index.ts +++ b/src/libs/actions/Session/index.ts @@ -3,7 +3,6 @@ import type {ChannelAuthorizationData} from 'pusher-js/types/src/core/auth/optio import type {ChannelAuthorizationCallback} from 'pusher-js/with-encryption'; import {InteractionManager, Linking, NativeModules} from 'react-native'; import type {OnyxEntry, OnyxUpdate} from 'react-native-onyx'; -import Fullstory from '@libs/Fullstory'; import Onyx from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import * as PersistedRequests from '@libs/actions/PersistedRequests'; @@ -24,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'; From dd22a4c69bcef7d59bff471bd1eb1de3eccade9d Mon Sep 17 00:00:00 2001 From: Vit Horacek <36083550+mountiny@users.noreply.github.com> Date: Wed, 24 Apr 2024 13:01:01 +0100 Subject: [PATCH 12/14] Merge pull request #40807 from Expensify/dsilva_changingPropertyBeingUsedByFullstory [CP Staging] Changing properties being used by Fullstory (cherry picked from commit 51372ab1171059ff514ffed1dc1511452aee6a57) --- src/ONYXKEYS.ts | 4 ++ src/libs/Fullstory/index.native.ts | 34 +++++++--------- src/libs/Fullstory/index.ts | 62 ++++++++++++++++-------------- src/libs/Fullstory/types.ts | 7 +--- src/libs/actions/Session/index.ts | 2 +- src/types/onyx/UserMetadata.ts | 8 ++++ src/types/onyx/index.ts | 2 + 7 files changed, 62 insertions(+), 57 deletions(-) create mode 100644 src/types/onyx/UserMetadata.ts diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index ddf37fba2354..e669d4740f98 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -188,6 +188,9 @@ const ONYXKEYS = { /** User's Expensify Wallet */ USER_WALLET: 'userWallet', + /** User's metadata that will be used to segmentation */ + USER_METADATA: 'userMetadata', + /** Object containing Onfido SDK Token + applicantID */ WALLET_ONFIDO: 'walletOnfido', @@ -610,6 +613,7 @@ type OnyxValuesMapping = { [ONYXKEYS.USER_LOCATION]: OnyxTypes.UserLocation; [ONYXKEYS.LOGIN_LIST]: OnyxTypes.LoginList; [ONYXKEYS.SESSION]: OnyxTypes.Session; + [ONYXKEYS.USER_METADATA]: OnyxTypes.UserMetadata; [ONYXKEYS.STASHED_SESSION]: OnyxTypes.Session; [ONYXKEYS.BETAS]: OnyxTypes.Beta[]; [ONYXKEYS.NVP_PRIORITY_MODE]: ValueOf; diff --git a/src/libs/Fullstory/index.native.ts b/src/libs/Fullstory/index.native.ts index e1781e6fed2d..67d7c7f2fe90 100644 --- a/src/libs/Fullstory/index.native.ts +++ b/src/libs/Fullstory/index.native.ts @@ -1,7 +1,6 @@ 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'; +import type {UserMetadata} from '@src/types/onyx'; /** * Fullstory React-Native lib adapter @@ -19,38 +18,31 @@ const FS = { consent: (c: boolean) => FullStory.consent(c), /** - * Initializes the FullStory session with the provided session information. + * Initializes the FullStory metadata with the provided metadata information. */ - consentAndIdentify: (value: OnyxEntry) => { + consentAndIdentify: (value: OnyxEntry) => { try { - const session: UserSession = { - email: value?.email, - accountID: value?.accountID, - }; - // set consent + // We only use FullStory in production environment FullStory.consent(true); - FS.fsIdentify(session); + FS.fsIdentify(value); } 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. + * 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: (session: UserSession) => { - if (!session || session.email === 'undefined') { - // anonymize FullStory user identity session + fsIdentify: (metadata: UserMetadata | null) => { + if (!metadata?.accountID) { + // anonymize FullStory user identity metadata FullStory.anonymize(); } else { // define FullStory user identity - FullStory.identify(String(session.accountID), { - properties: { - displayName: session.email, - email: session.email, - }, + FullStory.identify(String(metadata.accountID), { + properties: metadata, }); } }, diff --git a/src/libs/Fullstory/index.ts b/src/libs/Fullstory/index.ts index 3f42005e859b..5b60f20f9ddf 100644 --- a/src/libs/Fullstory/index.ts +++ b/src/libs/Fullstory/index.ts @@ -1,7 +1,9 @@ import {FullStory, init, isInitialized} from '@fullstory/browser'; import type {OnyxEntry} from 'react-native-onyx'; -import type Session from '@src/types/onyx/Session'; -import type {NavigationProperties, UserSession} from './types'; +import CONST from '@src/CONST'; +import * as Environment from '@src/libs/Environment/Environment'; +import type {UserMetadata} from '@src/types/onyx'; +import type NavigationProperties from './types'; // Placeholder Browser API does not support Manual Page definition class FSPage { @@ -27,12 +29,17 @@ const FS = { */ onReady: () => new Promise((resolve) => { - // Initialised via HEAD snippet - if (isInitialized()) { - init({orgId: ''}, resolve); - } else { - FullStory('observe', {type: 'start', callback: 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}); + } + }); }), /** @@ -46,18 +53,18 @@ const FS = { consent: (c: boolean) => FullStory('setIdentity', {consent: c}), /** - * Initializes the FullStory session with the provided session information. + * Initializes the FullStory metadata with the provided metadata information. */ - consentAndIdentify: (value: OnyxEntry) => { + consentAndIdentify: (value: OnyxEntry) => { try { - FS.onReady().then(() => { - const session: UserSession = { - email: value?.email, - accountID: value?.accountID, - }; - // set consent - FS.consent(true); - FS.fsIdentify(session); + Environment.getEnvironment().then((envName: string) => { + if (CONST.ENVIRONMENT.PRODUCTION !== envName) { + return; + } + FS.onReady().then(() => { + FS.consent(true); + FS.fsIdentify(value); + }); }); } catch (e) { // error handler @@ -65,22 +72,19 @@ const FS = { }, /** - * Sets the FullStory user identity based on the provided session information. - * If the session does not contain an email, the user identity is anonymized. - * If the session contains an email, the user identity is defined with the email and account ID. + * Sets the FullStory user identity based on the provided metadata information. + * 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: (session: UserSession) => { - if (typeof session.email === 'undefined') { - // anonymize FullStory user identity session + fsIdentify: (metadata: UserMetadata | null) => { + if (!metadata?.accountID) { + // anonymize FullStory user identity metadata FS.anonymize(); } else { // define FullStory user identity FullStory('setIdentity', { - uid: String(session.accountID), - properties: { - displayName: session.email, - email: session.email, - }, + uid: String(metadata.accountID), + properties: metadata, }); } }, diff --git a/src/libs/Fullstory/types.ts b/src/libs/Fullstory/types.ts index 386e35536d97..24878fd0fbd3 100644 --- a/src/libs/Fullstory/types.ts +++ b/src/libs/Fullstory/types.ts @@ -1,10 +1,5 @@ -type UserSession = { - email: string | undefined; - accountID: number | undefined; -}; - type NavigationProperties = { path: string; }; -export type {UserSession, NavigationProperties}; +export default NavigationProperties; diff --git a/src/libs/actions/Session/index.ts b/src/libs/actions/Session/index.ts index 6141dba190bf..303517558206 100644 --- a/src/libs/actions/Session/index.ts +++ b/src/libs/actions/Session/index.ts @@ -66,7 +66,7 @@ Onyx.connect({ }); Onyx.connect({ - key: ONYXKEYS.SESSION, + key: ONYXKEYS.USER_METADATA, callback: Fullstory.consentAndIdentify, }); diff --git a/src/types/onyx/UserMetadata.ts b/src/types/onyx/UserMetadata.ts new file mode 100644 index 000000000000..fc6490264087 --- /dev/null +++ b/src/types/onyx/UserMetadata.ts @@ -0,0 +1,8 @@ +type UserMetadata = { + planType?: string; + role?: string; + freeTrial?: boolean; + accountID?: number; +}; + +export default UserMetadata; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index eb3ef2eefc8d..d0fcae44ef31 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -74,6 +74,7 @@ import type {TransactionViolation, ViolationName} from './TransactionViolation'; import type TransactionViolations from './TransactionViolation'; import type User from './User'; import type UserLocation from './UserLocation'; +import type UserMetadata from './UserMetadata'; import type UserWallet from './UserWallet'; import type WalletAdditionalDetails from './WalletAdditionalDetails'; import type {WalletAdditionalQuestionDetails} from './WalletAdditionalDetails'; @@ -158,6 +159,7 @@ export type { TransactionViolations, User, UserLocation, + UserMetadata, UserWallet, ViolationName, WalletAdditionalDetails, From 0bba42c4b700ffcecf58e1198da801c4d1af9090 Mon Sep 17 00:00:00 2001 From: Daniel Silva Date: Wed, 22 May 2024 01:13:07 +0200 Subject: [PATCH 13/14] adding further changes --- config/webpack/webpack.common.ts | 2 +- web/gtm.js | 10 -- web/index.html | 6 +- web/thirdPartyScripts.js | 155 +++++++++++++++++++++++++++++++ 4 files changed, 159 insertions(+), 14 deletions(-) delete mode 100644 web/gtm.js create mode 100644 web/thirdPartyScripts.js diff --git a/config/webpack/webpack.common.ts b/config/webpack/webpack.common.ts index 7cafafca9973..9d397b9557a3 100644 --- a/config/webpack/webpack.common.ts +++ b/config/webpack/webpack.common.ts @@ -98,7 +98,7 @@ const getCommonConfiguration = ({file = '.env', platform = 'web'}: Environment): {from: 'web/apple-touch-icon.png'}, {from: 'assets/images/expensify-app-icon.svg'}, {from: 'web/manifest.json'}, - {from: 'web/gtm.js'}, + {from: 'web/thirdPartyScripts.js'}, {from: 'assets/css', to: 'css'}, {from: 'assets/fonts/web', to: 'fonts'}, {from: 'assets/sounds', to: 'sounds'}, diff --git a/web/gtm.js b/web/gtm.js deleted file mode 100644 index 6194d4ca2453..000000000000 --- a/web/gtm.js +++ /dev/null @@ -1,10 +0,0 @@ -(function (w, d, s, l, i) { - w[l] = w[l] || []; - w[l].push({'gtm.start': new Date().getTime(), event: 'gtm.js'}); - const f = d.getElementsByTagName(s)[0]; - const j = d.createElement(s); - const dl = l !== 'dataLayer' ? '&l=' + l : ''; - j.async = true; - j.src = 'https://www.googletagmanager.com/gtm.js?id=' + i + dl; - f.parentNode.insertBefore(j, f); -})(window, document, 'script', 'dataLayer', 'GTM-N4M3FLJZ'); diff --git a/web/index.html b/web/index.html index 115803573bbd..be6396190d25 100644 --- a/web/index.html +++ b/web/index.html @@ -122,9 +122,9 @@ <% if (htmlWebpackPlugin.options.isProduction) { %> - - - + + + <% } %> <% } %> diff --git a/web/thirdPartyScripts.js b/web/thirdPartyScripts.js new file mode 100644 index 000000000000..ca57b1c45b9a --- /dev/null +++ b/web/thirdPartyScripts.js @@ -0,0 +1,155 @@ +/* eslint-disable */ +// Google Tag Manager +(function (w, d, s, l, i) { + w[l] = w[l] || []; + w[l].push({'gtm.start': new Date().getTime(), event: 'gtm.js'}); + const f = d.getElementsByTagName(s)[0]; + const j = d.createElement(s); + const dl = l !== 'dataLayer' ? '&l=' + l : ''; + j.async = true; + j.src = 'https://www.googletagmanager.com/gtm.js?id=' + i + dl; + f.parentNode.insertBefore(j, f); +})(window, document, 'script', 'dataLayer', 'GTM-N4M3FLJZ'); + +// FullStory +window['_fs_host'] = 'fullstory.com'; +window['_fs_script'] = 'edge.fullstory.com/s/fs.js'; +window['_fs_org'] = 'o-1WN56P-na1'; +window['_fs_namespace'] = 'FS'; +!(function (m, n, e, t, l, o, g, y) { + var s, + f, + a = (function (h) { + return !(h in m) || (m.console && m.console.log && m.console.log('FullStory namespace conflict. Please set window["_fs_namespace"].'), !1); + })(e); + function p(b) { + var h, + d = []; + function j() { + h && + (d.forEach(function (b) { + var d; + try { + d = b[h[0]] && b[h[0]](h[1]); + } catch (h) { + return void (b[3] && b[3](h)); + } + d && d.then ? d.then(b[2], b[3]) : b[2] && b[2](d); + }), + (d.length = 0)); + } + function r(b) { + return function (d) { + h || ((h = [b, d]), j()); + }; + } + return ( + b(r(0), r(1)), + { + then: function (b, h) { + return p(function (r, i) { + d.push([b, h, r, i]), j(); + }); + }, + } + ); + } + a && + ((g = m[e] = + (function () { + var b = function (b, d, j, r) { + function i(i, c) { + h(b, d, j, i, c, r); + } + r = r || 2; + var c, + u = /Async$/; + return u.test(b) ? ((b = b.replace(u, '')), 'function' == typeof Promise ? new Promise(i) : p(i)) : h(b, d, j, c, c, r); + }; + function h(h, d, j, r, i, c) { + return b._api ? b._api(h, d, j, r, i, c) : (b.q && b.q.push([h, d, j, r, i, c]), null); + } + return (b.q = []), b; + })()), + (y = function (b) { + function h(h) { + 'function' == typeof h[4] && h[4](new Error(b)); + } + var d = g.q; + if (d) { + for (var j = 0; j < d.length; j++) h(d[j]); + (d.length = 0), (d.push = h); + } + }), + (function () { + ((o = n.createElement(t)).async = !0), + (o.crossOrigin = 'anonymous'), + (o.src = 'https://' + l), + (o.onerror = function () { + y('Error loading ' + l); + }); + var b = n.getElementsByTagName(t)[0]; + b && b.parentNode ? b.parentNode.insertBefore(o, b) : n.head.appendChild(o); + })(), + (function () { + function b() {} + function h(b, h, d) { + g(b, h, d, 1); + } + function d(b, d, j) { + h('setProperties', {type: b, properties: d}, j); + } + function j(b, h) { + d('user', b, h); + } + function r(b, h, d) { + j( + { + uid: b, + }, + d, + ), + h && j(h, d); + } + (g.identify = r), + (g.setUserVars = j), + (g.identifyAccount = b), + (g.clearUserCookie = b), + (g.setVars = d), + (g.event = function (b, d, j) { + h( + 'trackEvent', + { + name: b, + properties: d, + }, + j, + ); + }), + (g.anonymize = function () { + r(!1); + }), + (g.shutdown = function () { + h('shutdown'); + }), + (g.restart = function () { + h('restart'); + }), + (g.log = function (b, d) { + h('log', {level: b, msg: d}); + }), + (g.consent = function (b) { + h('setIdentity', {consent: !arguments.length || b}); + }); + })(), + (s = 'fetch'), + (f = 'XMLHttpRequest'), + (g._w = {}), + (g._w[f] = m[f]), + (g._w[s] = m[s]), + m[s] && + (m[s] = function () { + return g._w[s].apply(this, arguments); + }), + (g._v = '2.0.0')); +})(window, document, window._fs_namespace, 'script', window._fs_script); \ No newline at end of file From e7c2e65e7cb400dba7068a25b7a1cd53cc40534f Mon Sep 17 00:00:00 2001 From: Daniel Silva Date: Wed, 22 May 2024 01:31:25 +0200 Subject: [PATCH 14/14] prettier --- web/thirdPartyScripts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/thirdPartyScripts.js b/web/thirdPartyScripts.js index ca57b1c45b9a..15e77dbd490e 100644 --- a/web/thirdPartyScripts.js +++ b/web/thirdPartyScripts.js @@ -152,4 +152,4 @@ window['_fs_namespace'] = 'FS'; return g._w[s].apply(this, arguments); }), (g._v = '2.0.0')); -})(window, document, window._fs_namespace, 'script', window._fs_script); \ No newline at end of file +})(window, document, window._fs_namespace, 'script', window._fs_script);