From e60b0c201ce1fb604eb6ab638fcd4d1e7606288e Mon Sep 17 00:00:00 2001 From: Nabi Date: Thu, 4 Jun 2026 12:03:20 +0430 Subject: [PATCH 1/3] track FullStory user vars and page views --- src/Expensify.tsx | 2 + src/FullstoryUserContextHandler.tsx | 67 +++++++ src/libs/Fullstory/index.native.ts | 4 +- src/libs/Fullstory/index.ts | 4 +- src/libs/Fullstory/types.ts | 136 +++++++++++++- src/libs/Fullstory/utils.ts | 237 +++++++++++++++++++++++++ src/libs/Navigation/NavigationRoot.tsx | 2 + tests/unit/FullstoryUtilsTest.ts | 77 ++++++++ 8 files changed, 522 insertions(+), 7 deletions(-) create mode 100644 src/FullstoryUserContextHandler.tsx create mode 100644 src/libs/Fullstory/utils.ts create mode 100644 tests/unit/FullstoryUtilsTest.ts diff --git a/src/Expensify.tsx b/src/Expensify.tsx index a7dd389611b6..8bec964d0907 100644 --- a/src/Expensify.tsx +++ b/src/Expensify.tsx @@ -12,6 +12,7 @@ import CONST from './CONST'; import DeepLinkHandler from './DeepLinkHandler'; import DelegateAccessHandler from './DelegateAccessHandler'; import FullstoryInitHandler from './FullstoryInitHandler'; +import FullstoryUserContextHandler from './FullstoryUserContextHandler'; import GlobalModals from './GlobalModals'; import useDebugShortcut from './hooks/useDebugShortcut'; import useIsAuthenticated from './hooks/useIsAuthenticated'; @@ -285,6 +286,7 @@ function Expensify() { + {/* Wait for the initial URL to resolve before mounting NavigationRoot, because its initialState diff --git a/src/FullstoryUserContextHandler.tsx b/src/FullstoryUserContextHandler.tsx new file mode 100644 index 000000000000..acc95da9cecd --- /dev/null +++ b/src/FullstoryUserContextHandler.tsx @@ -0,0 +1,67 @@ +import {useEffect, useMemo, useRef} from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; +import useOnyx from './hooks/useOnyx'; +import FS from './libs/Fullstory'; +import type {FullstoryUserVars} from './libs/Fullstory/types'; +import {buildFullstoryUserVars} from './libs/Fullstory/utils'; +import {shallowCompare} from './libs/ObjectUtils'; +import ONYXKEYS from './ONYXKEYS'; + +function FullstoryUserContextHandler() { + const [account] = useOnyx(ONYXKEYS.ACCOUNT); + const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID); + const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED); + const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST); + const [onboarding] = useOnyx(ONYXKEYS.NVP_ONBOARDING); + const [onboardingCompanySize] = useOnyx(ONYXKEYS.ONBOARDING_COMPANY_SIZE); + const [onboardingLastVisitedPath] = useOnyx(ONYXKEYS.ONBOARDING_LAST_VISITED_PATH); + const [onboardingPurposeSelected] = useOnyx(ONYXKEYS.ONBOARDING_PURPOSE_SELECTED); + const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY); + const [session] = useOnyx(ONYXKEYS.SESSION); + const [userMetadata] = useOnyx(ONYXKEYS.USER_METADATA); + + const activePolicy = useMemo(() => { + if (!activePolicyID) { + return; + } + + return policies?.[`${ONYXKEYS.COLLECTION.POLICY}${activePolicyID}`]; + }, [activePolicyID, policies]); + + const userVars = useMemo( + () => + buildFullstoryUserVars({ + account, + activePolicy, + introSelected, + loginList, + onboarding, + onboardingCompanySize, + onboardingLastVisitedPath, + onboardingPurposeSelected, + policies, + session, + userMetadata, + }), + [account, activePolicy, introSelected, loginList, onboarding, onboardingCompanySize, onboardingLastVisitedPath, onboardingPurposeSelected, policies, session, userMetadata], + ); + + const previousUserVars = useRef>(undefined); + + useEffect(() => { + if (!userMetadata?.accountID) { + return; + } + + if (shallowCompare(previousUserVars.current, userVars)) { + return; + } + + previousUserVars.current = userVars; + FS.setUserVars(userVars); + }, [userMetadata?.accountID, userVars]); + + return null; +} + +export default FullstoryUserContextHandler; diff --git a/src/libs/Fullstory/index.native.ts b/src/libs/Fullstory/index.native.ts index 114d03215321..c54689be14bf 100644 --- a/src/libs/Fullstory/index.native.ts +++ b/src/libs/Fullstory/index.native.ts @@ -57,8 +57,8 @@ const FS: Fullstory = { return FullStory.getCurrentSessionURL(); }, - event: (eventName, eventProperties = {}) => { - FullStory.event(eventName, eventProperties); + event: (eventName, eventProperties) => { + FullStory.event(eventName, eventProperties ?? {}); }, log: (level, message) => { diff --git a/src/libs/Fullstory/index.ts b/src/libs/Fullstory/index.ts index d357c97c0e91..ba3a8f0fa922 100644 --- a/src/libs/Fullstory/index.ts +++ b/src/libs/Fullstory/index.ts @@ -102,11 +102,11 @@ const FS: Fullstory = { return FullStory('getSessionAsync', {format: 'url'}); }, - event: (eventName, eventProperties = {}) => { + event: (eventName, eventProperties) => { if (!isInitialized()) { return; } - FullStory(CONST.FULLSTORY.OPERATION.TRACK_EVENT, {name: eventName, properties: eventProperties}); + FullStory(CONST.FULLSTORY.OPERATION.TRACK_EVENT, {name: eventName, properties: eventProperties ?? {}}); }, log: (level, message) => { diff --git a/src/libs/Fullstory/types.ts b/src/libs/Fullstory/types.ts index 1a6146d8cf77..e606bb2b48e7 100644 --- a/src/libs/Fullstory/types.ts +++ b/src/libs/Fullstory/types.ts @@ -7,6 +7,136 @@ type FSClass = ValueOf; type PropertiesWithoutPageName = Record & {pageName?: never}; +type FullstoryUserVars = { + user_type_path?: string; + account_type?: 'personal' | 'business'; + user_status?: 'new' | 'returning'; + has_completed_onboarding?: boolean; + onb_step?: 'registration' | 'accounting' | 'completed'; + user_role?: 'admin' | 'auditor' | 'member'; + workspace_state?: 'has_workspaces' | 'no_workspaces'; + workspace_count?: number; + workspace_member_count?: number; + free_trial_end_date?: string; + days_till_trial_end?: number; + free_trial_status?: 'active' | 'expiring_soon' | 'expired' | 'expired_last30days'; + plan_type?: 'collect' | 'control'; + paid_member?: boolean; + auth_method?: 'email' | 'google' | 'apple'; + reg_method?: 'Google signup' | 'email/phone signup'; + login_status?: 'success' | 'failure'; +}; + +type FullstoryEventPropertiesMap = { + Page_viewed: { + screen_name: string; + entry_point?: string; + onb_step?: FullstoryUserVars['onb_step']; + }; + Component_viewed: { + screen_name: string; + location?: string; + component_name?: string; + onb_step?: FullstoryUserVars['onb_step']; + }; + Component_closed: { + screen_name: string; + location?: string; + component_name?: string; + onb_step?: FullstoryUserVars['onb_step']; + }; + clickable_action: { + screen_name?: string; + location?: string; + component_name?: string; + onb_step?: FullstoryUserVars['onb_step']; + element_label?: string; + checked_box?: boolean; + toggle_swith_on?: boolean; + result_type?: string; + action_status?: string; + position?: number; + }; + Input_field: { + screen_name?: string; + location?: string; + component_name?: string; + onb_step?: FullstoryUserVars['onb_step']; + input_field_name?: string; + input_field_type?: string; + input_field_status?: string; + }; + Error_message: { + screen_name?: string; + location?: string; + component_name?: string; + onb_step?: FullstoryUserVars['onb_step']; + error_location?: string; + error_code?: string; + error_message?: string; + error_type?: string; + }; + Search_submitted: { + screen_name?: string; + result_type?: string; + search_results_count?: number; + search_type?: string; + }; + Chat_opened: { + screen_name?: string; + chat_type?: 'Full-page chat' | 'side-panel chat'; + }; + Login_submitted: { + action_status?: string; + }; + sign_up: { + entry_point?: string; + action_status?: string; + }; + File_upload_started: { + upload_method?: string; + }; + File_upload_completed: { + upload_success?: boolean; + }; + Concierge_message_sent: { + has_attachment?: boolean; + attachment_count?: number; + attachment_types?: string; + upload_method?: string; + }; + Chatbot_response_received: { + error_type?: string; + }; + Expense_created: { + expense_type?: string; + expense_creation_method?: string; + amount_range?: string; + }; + Report_created: { + expense_count?: number; + report_type?: string; + }; + Report_submitted: { + expense_count?: number; + report_type?: string; + approver_count?: number; + }; + Bank_account_added: { + bank_account_type?: string; + card_connection_method?: string; + bank_region?: string; + }; + Card_added: { + card_connection_method?: string; + card_type?: string; + card_provider?: string; + card_country?: string; + }; +}; + +type FullstoryEventName = keyof FullstoryEventPropertiesMap; + /** * Represents the common FSPage class signature that will be used in both platform implementations. */ @@ -87,7 +217,7 @@ type Fullstory = { /** * Sends a custom event to FullStory. */ - event: (eventName: string, eventProperties?: Record) => void; + event: (eventName: TEventName, eventProperties?: FullstoryEventPropertiesMap[TEventName]) => void; /** * Sends a log message to FullStory with the specified log level. @@ -97,7 +227,7 @@ type Fullstory = { /** * Updates user properties without re-identifying. */ - setUserVars: (userVars: Record) => void; + setUserVars: (userVars: FullstoryUserVars) => void; /** * Resets the idle timer to prevent session timeout. @@ -148,4 +278,4 @@ type ForwardedFSClassProps = { forwardedFSClass?: FSClass; }; -export type {FSPageLike, Fullstory, GetChatFSClass, ForwardedFSClassProps, ShouldInitialize}; +export type {FSPageLike, Fullstory, FullstoryEventName, FullstoryEventPropertiesMap, FullstoryUserVars, GetChatFSClass, ForwardedFSClassProps, ShouldInitialize}; diff --git a/src/libs/Fullstory/utils.ts b/src/libs/Fullstory/utils.ts new file mode 100644 index 000000000000..2b7e40d43ca1 --- /dev/null +++ b/src/libs/Fullstory/utils.ts @@ -0,0 +1,237 @@ +import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; +import {filterObject} from '@libs/ObjectUtils'; +import {getActivePolicies, isControlPolicy} from '@libs/PolicyUtils'; +import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; +import type {Account, IntroSelected, LoginList, Onboarding, Policy, Session, UserMetadata} from '@src/types/onyx'; +import FS from '.'; +import type {FullstoryEventName, FullstoryEventPropertiesMap, FullstoryUserVars} from './types'; + +type BuildFullstoryUserVarsParams = { + account: OnyxEntry; + activePolicy: OnyxEntry; + introSelected: OnyxEntry; + loginList: OnyxEntry; + onboarding: OnyxEntry; + onboardingCompanySize: OnyxEntry; + onboardingLastVisitedPath: OnyxEntry; + onboardingPurposeSelected: OnyxEntry>; + policies: OnyxCollection | undefined; + session: OnyxEntry; + userMetadata: OnyxEntry; +}; + +function sanitizeSegment(value: string): string { + return value + .toLowerCase() + .replaceAll(/[^a-z0-9]+/g, '_') + .replaceAll(/^_+|_+$/g, ''); +} + +function getNormalizedOnboardingChoice(choice: OnyxEntry>): string | undefined { + if (!choice) { + return; + } + + const choiceMap: Partial, string>> = { + [CONST.ONBOARDING_CHOICES.MANAGE_TEAM]: 'team', + [CONST.ONBOARDING_CHOICES.TRACK_BUSINESS]: 'track_business', + [CONST.ONBOARDING_CHOICES.TRACK_PERSONAL]: 'track_personal', + [CONST.ONBOARDING_CHOICES.PERSONAL_SPEND]: 'personal_spend', + [CONST.ONBOARDING_CHOICES.LOOKING_AROUND]: 'looking_around', + [CONST.ONBOARDING_CHOICES.EMPLOYER]: 'employer', + [CONST.ONBOARDING_CHOICES.CHAT_SPLIT]: 'chat_split', + [CONST.ONBOARDING_CHOICES.ADMIN]: 'admin', + [CONST.ONBOARDING_CHOICES.SUBMIT]: 'submit', + [CONST.ONBOARDING_CHOICES.TEST_DRIVE_RECEIVER]: 'test_drive_receiver', + }; + + return choiceMap[choice] ?? sanitizeSegment(choice); +} + +function buildUserTypePath(choice: OnyxEntry>, companySize: OnyxEntry, isFromPublicDomain?: boolean): string | undefined { + const normalizedChoice = getNormalizedOnboardingChoice(choice); + const normalizedCompanySize = companySize ? sanitizeSegment(companySize) : undefined; + let domainType: string | undefined; + if (isFromPublicDomain !== undefined) { + domainType = isFromPublicDomain ? 'public' : 'private'; + } + + const segments = [normalizedChoice, normalizedCompanySize, domainType].filter(Boolean); + if (segments.length === 0) { + return; + } + + return segments.join('_'); +} + +function getDaysTillDate(dateString: string | undefined): number | undefined { + if (!dateString) { + return; + } + + const endDate = new Date(dateString); + if (Number.isNaN(endDate.getTime())) { + return; + } + + return Math.ceil((endDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24)); +} + +function getFreeTrialStatus(daysTillTrialEnd: number | undefined): FullstoryUserVars['free_trial_status'] { + if (daysTillTrialEnd === undefined) { + return; + } + if (daysTillTrialEnd < -30) { + return 'expired'; + } + if (daysTillTrialEnd < 0) { + return 'expired_last30days'; + } + if (daysTillTrialEnd <= 14) { + return 'expiring_soon'; + } + return 'active'; +} + +function getOnboardingStep(onboardingPath: string | undefined, hasCompletedOnboarding?: boolean): FullstoryUserVars['onb_step'] { + if (hasCompletedOnboarding) { + return 'completed'; + } + + if (!onboardingPath) { + return; + } + + if (onboardingPath.includes(ROUTES.ONBOARDING_ACCOUNTING.route)) { + return 'accounting'; + } + + const onboardingRoutes = [ + ROUTES.ONBOARDING_ROOT.route, + ROUTES.ONBOARDING_WORK_EMAIL.route, + ROUTES.ONBOARDING_WORK_EMAIL_VALIDATION.route, + ROUTES.ONBOARDING_PRIVATE_DOMAIN.route, + ROUTES.ONBOARDING_PERSONAL_DETAILS.route, + ROUTES.ONBOARDING_WORKSPACES.route, + ROUTES.ONBOARDING_PURPOSE.route, + ROUTES.ONBOARDING_EMPLOYEES.route, + ROUTES.ONBOARDING_INTERESTED_FEATURES.route, + ROUTES.ONBOARDING_WORKSPACE.route, + ROUTES.ONBOARDING_WORKSPACE_CONFIRMATION.route, + ROUTES.ONBOARDING_WORKSPACE_CURRENCY.route, + ROUTES.ONBOARDING_WORKSPACE_INVITE.route, + ]; + + if (onboardingRoutes.some((route) => onboardingPath.includes(route))) { + return 'registration'; + } +} + +function getUserRole(policies: OnyxCollection | undefined): FullstoryUserVars['user_role'] { + let userRole: FullstoryUserVars['user_role'] = 'member'; + + for (const policy of Object.values(policies ?? {})) { + if (policy?.role === CONST.POLICY.ROLE.ADMIN) { + return 'admin'; + } + if (policy?.role === CONST.POLICY.ROLE.AUDITOR) { + userRole = 'auditor'; + } + } + + return userRole; +} + +function getAuthMethod(loginList: OnyxEntry): FullstoryUserVars['auth_method'] { + const normalizedPartnerNames = Object.values(loginList ?? {}) + .map((login) => login?.partnerName?.toLowerCase()) + .filter(Boolean); + + if (normalizedPartnerNames.some((partnerName) => partnerName?.includes('google'))) { + return 'google'; + } + if (normalizedPartnerNames.some((partnerName) => partnerName?.includes('apple'))) { + return 'apple'; + } + + return 'email'; +} + +function getPlanType(policies: Policy[]): FullstoryUserVars['plan_type'] { + if (policies.some(isControlPolicy)) { + return 'control'; + } + if (policies.some((policy) => policy.type === CONST.POLICY.TYPE.TEAM)) { + return 'collect'; + } +} + +function buildFullstoryUserVars({ + account, + activePolicy, + introSelected, + loginList, + onboarding, + onboardingCompanySize, + onboardingLastVisitedPath, + onboardingPurposeSelected, + policies, + session, + userMetadata, +}: BuildFullstoryUserVarsParams): FullstoryUserVars { + const activePolicies = getActivePolicies(policies ?? null, session?.email); + const hasCompletedOnboarding = onboarding?.hasCompletedGuidedSetupFlow; + const currentOnboardingChoice = introSelected?.choice ?? onboardingPurposeSelected; + const companySize = introSelected?.companySize ?? onboardingCompanySize; + const daysTillTrialEnd = getDaysTillDate(userMetadata?.freeTrialEndDate); + let userStatus: FullstoryUserVars['user_status']; + + if (hasCompletedOnboarding !== undefined) { + userStatus = hasCompletedOnboarding ? 'returning' : 'new'; + } + + /* eslint-disable @typescript-eslint/naming-convention -- FullStory schema uses external snake_case keys. */ + return filterObject( + { + user_type_path: buildUserTypePath(currentOnboardingChoice, companySize, account?.isFromPublicDomain), + account_type: activePolicies.length > 0 ? 'business' : 'personal', + user_status: userStatus, + has_completed_onboarding: hasCompletedOnboarding, + onb_step: getOnboardingStep(onboardingLastVisitedPath, hasCompletedOnboarding), + user_role: getUserRole(policies), + workspace_state: activePolicies.length > 0 ? 'has_workspaces' : 'no_workspaces', + workspace_count: activePolicies.length, + workspace_member_count: activePolicy ? Object.keys(activePolicy.employeeList ?? {}).length : undefined, + free_trial_end_date: userMetadata?.freeTrialEndDate, + days_till_trial_end: daysTillTrialEnd, + free_trial_status: getFreeTrialStatus(daysTillTrialEnd), + plan_type: getPlanType(activePolicies), + paid_member: userMetadata?.paidMember, + auth_method: getAuthMethod(loginList), + } satisfies FullstoryUserVars, + (_key, value) => value !== undefined, + ); + /* eslint-enable @typescript-eslint/naming-convention */ +} + +function trackFullstoryEvent(eventName: TEventName, eventProperties: FullstoryEventPropertiesMap[TEventName]) { + FS.event( + eventName, + filterObject(eventProperties, (_key, value) => value !== undefined), + ); +} + +function buildPageViewedEvent(screenName: string, entryPoint: string): FullstoryEventPropertiesMap['Page_viewed'] { + /* eslint-disable @typescript-eslint/naming-convention -- FullStory schema uses external snake_case keys. */ + return { + screen_name: screenName, + entry_point: entryPoint, + onb_step: getOnboardingStep(entryPoint), + }; + /* eslint-enable @typescript-eslint/naming-convention */ +} + +export {buildFullstoryUserVars, buildPageViewedEvent, getOnboardingStep, trackFullstoryEvent}; +export type {BuildFullstoryUserVarsParams}; diff --git a/src/libs/Navigation/NavigationRoot.tsx b/src/libs/Navigation/NavigationRoot.tsx index 2338fda22531..a77c9ff441fa 100644 --- a/src/libs/Navigation/NavigationRoot.tsx +++ b/src/libs/Navigation/NavigationRoot.tsx @@ -11,6 +11,7 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemePreference from '@hooks/useThemePreference'; import FS from '@libs/Fullstory'; +import {buildPageViewedEvent, trackFullstoryEvent} from '@libs/Fullstory/utils'; import Log from '@libs/Log'; import {setupNavigationFocusReturn, teardownNavigationFocusReturn} from '@libs/NavigationFocusReturn'; import {sanitizeUrlForLogging} from '@libs/sanitizeLogParams'; @@ -98,6 +99,7 @@ function parseAndLogRoute(state: NavigationState) { const focusedRouteName = focusedRoute?.name; if (focusedRouteName) { new FS.Page(focusedRouteName, {path: currentPath}).start(); + trackFullstoryEvent('Page_viewed', buildPageViewedEvent(focusedRouteName, currentPath)); } } diff --git a/tests/unit/FullstoryUtilsTest.ts b/tests/unit/FullstoryUtilsTest.ts new file mode 100644 index 000000000000..07682c1e80b7 --- /dev/null +++ b/tests/unit/FullstoryUtilsTest.ts @@ -0,0 +1,77 @@ +import CONST from '@src/CONST'; +import {buildFullstoryUserVars, buildPageViewedEvent, getOnboardingStep} from '@src/libs/Fullstory/utils'; +import type {Policy} from '@src/types/onyx'; + +describe('FullstoryUtils', () => { + it('builds expected FullStory user vars from onboarding and workspace context', () => { + const policy = { + id: '1', + name: 'Test Workspace', + type: CONST.POLICY.TYPE.TEAM, + role: CONST.POLICY.ROLE.ADMIN, + employeeList: { + 1: {email: 'a@test.com'}, + 2: {email: 'b@test.com'}, + }, + } as unknown as Policy; + + const userVars = buildFullstoryUserVars({ + account: {isFromPublicDomain: true}, + activePolicy: policy, + introSelected: { + choice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM, + companySize: '1-10', + }, + loginList: { + test: { + partnerName: 'google.com', + }, + }, + onboarding: { + hasCompletedGuidedSetupFlow: false, + signupQualifier: CONST.ONBOARDING_SIGNUP_QUALIFIERS.SMB, + }, + onboardingCompanySize: '1-10', + onboardingLastVisitedPath: '/onboarding/workspace', + onboardingPurposeSelected: CONST.ONBOARDING_CHOICES.MANAGE_TEAM, + policies: { + [`policy_${policy.id}`]: policy, + }, + session: {email: 'test@test.com'}, + userMetadata: { + freeTrialEndDate: '2099-05-31 23:59:59', + paidMember: true, + }, + }); + + expect(userVars).toMatchObject({ + user_type_path: 'team_1_10_public', + account_type: 'business', + user_status: 'new', + has_completed_onboarding: false, + onb_step: 'registration', + user_role: 'admin', + workspace_state: 'has_workspaces', + workspace_count: 1, + workspace_member_count: 2, + free_trial_end_date: '2099-05-31 23:59:59', + free_trial_status: 'active', + plan_type: 'collect', + paid_member: true, + auth_method: 'google', + }); + expect(userVars.days_till_trial_end).toBeGreaterThan(0); + }); + + it('builds page viewed event metadata', () => { + expect(buildPageViewedEvent('OnboardingWorkspace', '/onboarding/workspace')).toEqual({ + screen_name: 'OnboardingWorkspace', + entry_point: '/onboarding/workspace', + onb_step: 'registration', + }); + }); + + it('returns completed onboarding step when flow is finished', () => { + expect(getOnboardingStep('/settings/workspaces', true)).toBe('completed'); + }); +}); From a7e7dac41d784d258e8b019399784a4969376252 Mon Sep 17 00:00:00 2001 From: Nabi Date: Thu, 4 Jun 2026 13:38:03 +0430 Subject: [PATCH 2/3] fix fullStory CI issues --- src/libs/Fullstory/types.ts | 3 +++ src/libs/Fullstory/utils.ts | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libs/Fullstory/types.ts b/src/libs/Fullstory/types.ts index e606bb2b48e7..108a4229486d 100644 --- a/src/libs/Fullstory/types.ts +++ b/src/libs/Fullstory/types.ts @@ -7,6 +7,7 @@ type FSClass = ValueOf; type PropertiesWithoutPageName = Record & {pageName?: never}; +/* eslint-disable @typescript-eslint/naming-convention -- FullStory schema uses external snake_case keys. */ type FullstoryUserVars = { user_type_path?: string; account_type?: 'personal' | 'business'; @@ -52,6 +53,7 @@ type FullstoryEventPropertiesMap = { onb_step?: FullstoryUserVars['onb_step']; element_label?: string; checked_box?: boolean; + // cspell:disable-next-line toggle_swith_on?: boolean; result_type?: string; action_status?: string; @@ -136,6 +138,7 @@ type FullstoryEventPropertiesMap = { }; type FullstoryEventName = keyof FullstoryEventPropertiesMap; +/* eslint-enable @typescript-eslint/naming-convention */ /** * Represents the common FSPage class signature that will be used in both platform implementations. diff --git a/src/libs/Fullstory/utils.ts b/src/libs/Fullstory/utils.ts index 2b7e40d43ca1..c7906cc77b5f 100644 --- a/src/libs/Fullstory/utils.ts +++ b/src/libs/Fullstory/utils.ts @@ -234,4 +234,3 @@ function buildPageViewedEvent(screenName: string, entryPoint: string): Fullstory } export {buildFullstoryUserVars, buildPageViewedEvent, getOnboardingStep, trackFullstoryEvent}; -export type {BuildFullstoryUserVarsParams}; From a0a92121d008064e77da45f4af152c99a74f0ab9 Mon Sep 17 00:00:00 2001 From: Nabi Date: Thu, 4 Jun 2026 14:06:25 +0430 Subject: [PATCH 3/3] fix lint --- tests/unit/FullstoryUtilsTest.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/FullstoryUtilsTest.ts b/tests/unit/FullstoryUtilsTest.ts index 07682c1e80b7..e8e554effec8 100644 --- a/tests/unit/FullstoryUtilsTest.ts +++ b/tests/unit/FullstoryUtilsTest.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/naming-convention */ import CONST from '@src/CONST'; import {buildFullstoryUserVars, buildPageViewedEvent, getOnboardingStep} from '@src/libs/Fullstory/utils'; import type {Policy} from '@src/types/onyx';