Skip to content

Commit 226cc79

Browse files
authored
Merge pull request #91950 from KJ21-ENG/KJ21-ENG/90933-admin-invite-onboarding-v3
Fix admin invite onboarding task
2 parents 7ebf80b + 9b3d0a2 commit 226cc79

4 files changed

Lines changed: 89 additions & 18 deletions

File tree

src/libs/OnboardingUtils.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
import CONST from '@src/CONST';
2-
import type {OnboardingPurpose} from '@src/types/onyx';
2+
import type {OnboardingInvite} from '@src/CONST';
3+
import type {IntroSelected, OnboardingPurpose} from '@src/types/onyx';
34

45
import type {OnyxEntry} from 'react-native-onyx';
56

7+
type SupportedInviteOnboardingChoice = typeof CONST.ONBOARDING_CHOICES.ADMIN | typeof CONST.ONBOARDING_CHOICES.SUBMIT | typeof CONST.ONBOARDING_CHOICES.CHAT_SPLIT;
8+
type SupportedPendingInviteIntroSelected = IntroSelected & {
9+
choice: SupportedInviteOnboardingChoice;
10+
inviteType: Exclude<OnboardingInvite, typeof CONST.ONBOARDING_INVITE_TYPES.IOU | typeof CONST.ONBOARDING_INVITE_TYPES.INVOICE>;
11+
};
12+
613
/**
714
* Returns true when the onboarding choice is one of the "track" variants
815
* (TRACK_BUSINESS/TRACK_WORKSPACE, TRACK_PERSONAL, or the legacy PERSONAL_SPEND).
@@ -14,4 +21,23 @@ function isTrackOnboardingChoice(choice: OnyxEntry<OnboardingPurpose>): choice i
1421
return choice === CONST.ONBOARDING_CHOICES.TRACK_BUSINESS || choice === CONST.ONBOARDING_CHOICES.TRACK_PERSONAL || choice === CONST.ONBOARDING_CHOICES.PERSONAL_SPEND;
1522
}
1623

24+
function isSupportedInviteOnboardingChoice(choice: OnyxEntry<OnboardingPurpose>): choice is SupportedInviteOnboardingChoice {
25+
return choice === CONST.ONBOARDING_CHOICES.ADMIN || choice === CONST.ONBOARDING_CHOICES.SUBMIT || choice === CONST.ONBOARDING_CHOICES.CHAT_SPLIT;
26+
}
27+
28+
function isSupportedPendingInviteOnboarding(introSelected: OnyxEntry<IntroSelected>): introSelected is SupportedPendingInviteIntroSelected {
29+
if (!introSelected) {
30+
return false;
31+
}
32+
33+
if (!introSelected.inviteType || introSelected.isInviteOnboardingComplete) {
34+
return false;
35+
}
36+
37+
const isInviteIOUorInvoice = introSelected.inviteType === CONST.ONBOARDING_INVITE_TYPES.IOU || introSelected.inviteType === CONST.ONBOARDING_INVITE_TYPES.INVOICE;
38+
39+
return isSupportedInviteOnboardingChoice(introSelected.choice) && !isInviteIOUorInvoice;
40+
}
41+
1742
export default isTrackOnboardingChoice;
43+
export {isSupportedInviteOnboardingChoice, isSupportedPendingInviteOnboarding};

src/libs/actions/Report/index.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ import {getDBTimeWithSkew, getIsOffline as isOfflineNetwork} from '@libs/Network
7878
import {buildNextStepNew, buildOptimisticNextStep} from '@libs/NextStepUtils';
7979
import LocalNotification from '@libs/Notification/LocalNotification';
8080
import {rand64} from '@libs/NumberUtils';
81+
import {isSupportedInviteOnboardingChoice, isSupportedPendingInviteOnboarding} from '@libs/OnboardingUtils';
8182
import capturePageHTML from '@libs/PageHTMLCapture';
8283
import {prunePagesToNewestWindow} from '@libs/PaginationUtils';
8384
import Parser from '@libs/Parser';
@@ -1402,23 +1403,22 @@ function getGuidedSetupDataForOpenReport(
14021403
): GuidedSetupDataForOpenReport | undefined {
14031404
const isInviteOnboardingComplete = introSelected?.isInviteOnboardingComplete ?? false;
14041405
const isOnboardingCompleted = hasCompletedGuidedSetupFlow ?? onboarding?.hasCompletedGuidedSetupFlow ?? false;
1406+
const isPendingInviteOnboarding = isSupportedPendingInviteOnboarding(introSelected);
14051407

14061408
// Some cases we can have two open report requests with guide setup data because isInviteOnboardingComplete is not updated completely.
14071409
// Then we need to check the list request and prevent the guided setup data from being duplicated.
14081410
const allPersistedRequests = getAll();
14091411
const hasOpenReportWithGuidedSetupData = allPersistedRequests.some((request) => request.command === WRITE_COMMANDS.OPEN_REPORT && request.data?.guidedSetupData);
14101412

1411-
// Prepare guided setup data only when nvp_introSelected is set and onboarding is not completed
1412-
// OldDot users will never have nvp_introSelected set, so they will not see guided setup messages
1413-
if (!introSelected || isOnboardingCompleted || isInviteOnboardingComplete || hasOpenReportWithGuidedSetupData) {
1413+
// Prepare guided setup data only when nvp_introSelected is set and regular onboarding is pending or a supported invite onboarding flow still needs to run.
1414+
// OldDot users will never have nvp_introSelected set, so they will not see guided setup messages.
1415+
if (!introSelected || isInviteOnboardingComplete || hasOpenReportWithGuidedSetupData || (isOnboardingCompleted && !isPendingInviteOnboarding)) {
14141416
return undefined;
14151417
}
14161418

14171419
const {choice, inviteType} = introSelected;
14181420
const isInviteIOUorInvoice = inviteType === CONST.ONBOARDING_INVITE_TYPES.IOU || inviteType === CONST.ONBOARDING_INVITE_TYPES.INVOICE;
1419-
const isInviteChoiceCorrect = choice === CONST.ONBOARDING_CHOICES.ADMIN || choice === CONST.ONBOARDING_CHOICES.SUBMIT || choice === CONST.ONBOARDING_CHOICES.CHAT_SPLIT;
1420-
1421-
if (!isInviteChoiceCorrect || isInviteIOUorInvoice) {
1421+
if (!isSupportedInviteOnboardingChoice(choice) || isInviteIOUorInvoice) {
14221422
return undefined;
14231423
}
14241424

@@ -1434,6 +1434,7 @@ function getGuidedSetupDataForOpenReport(
14341434
onboardingMessage,
14351435
companySize: introSelected?.companySize as OnboardingCompanySize,
14361436
isSelfTourViewed,
1437+
wasInvited: isPendingInviteOnboarding && isOnboardingCompleted,
14371438
});
14381439

14391440
if (!onboardingData) {

src/pages/inbox/ReportFetchHandler.tsx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {getAllNonDeletedTransactions} from '@libs/MoneyRequestReportUtils';
1515
import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types';
1616
import TransitionTracker from '@libs/Navigation/TransitionTracker';
1717
import type {CancelHandle} from '@libs/Navigation/TransitionTracker';
18+
import {isSupportedInviteOnboardingChoice, isSupportedPendingInviteOnboarding} from '@libs/OnboardingUtils';
1819
import {getFilteredReportActionsForReportView, getIOUActionForReportID, getOneTransactionThreadReportID, isCreatedAction} from '@libs/ReportActionsUtils';
1920
import {
2021
isChatThread,
@@ -134,6 +135,10 @@ function ReportFetchHandler() {
134135

135136
const isInviteOnboardingComplete = introSelected?.isInviteOnboardingComplete ?? false;
136137
const isOnboardingCompleted = onboarding?.hasCompletedGuidedSetupFlow ?? false;
138+
const isRegularOnboardingPending = !!introSelected && !introSelected.inviteType && isSupportedInviteOnboardingChoice(introSelected.choice) && !isOnboardingCompleted;
139+
const isPendingInviteOnboarding = isSupportedPendingInviteOnboarding(introSelected);
140+
const onboardingSignal = introSelected ? `${introSelected.choice ?? ''}:${introSelected.inviteType ?? ''}:${isInviteOnboardingComplete ? 'complete' : 'pending'}` : '';
141+
const shouldDeferGuidedSetupOpenReport = !!isLoadingApp && (isRegularOnboardingPending || isPendingInviteOnboarding);
137142

138143
const fetchReport = useEffectEvent(() => {
139144
if (reportMetadata.isOptimisticReport && report?.type === CONST.REPORT.TYPE.CHAT && !isPolicyExpenseChat(report)) {
@@ -144,18 +149,12 @@ function ReportFetchHandler() {
144149
return;
145150
}
146151

147-
// When a user goes through onboarding for the first time, various tasks are created for chatting with Concierge.
152+
// When a user goes through guided setup, various tasks are created for chatting with Concierge.
148153
// If this function is called too early (while the application is still loading), we will not have information about policies,
149154
// which means we will not be able to obtain the correct link for one of the tasks.
150155
// More information here: https://github.com/Expensify/App/issues/71742
151-
if (isLoadingApp && introSelected && !isOnboardingCompleted && !isInviteOnboardingComplete) {
152-
const {choice, inviteType} = introSelected;
153-
const isInviteIOUorInvoice = inviteType === CONST.ONBOARDING_INVITE_TYPES.IOU || inviteType === CONST.ONBOARDING_INVITE_TYPES.INVOICE;
154-
const isInviteChoiceCorrect = choice === CONST.ONBOARDING_CHOICES.ADMIN || choice === CONST.ONBOARDING_CHOICES.SUBMIT || choice === CONST.ONBOARDING_CHOICES.CHAT_SPLIT;
155-
156-
if (isInviteChoiceCorrect && !isInviteIOUorInvoice) {
157-
return;
158-
}
156+
if (shouldDeferGuidedSetupOpenReport) {
157+
return;
159158
}
160159

161160
openReport({reportID: reportIDFromRoute, introSelected, reportActionID: reportActionIDFromRoute, betas});
@@ -305,9 +304,10 @@ function ReportFetchHandler() {
305304
useEffect(() => {
306305
// This function is triggered when a user clicks on a link to navigate to a report.
307306
// For each link click, we retrieve the report data again, even though it may already be cached.
308-
// There should be only one openReport execution per page start or navigating
307+
// Usually this triggers one openReport execution per page start or navigation. If guided setup is deferred while app data loads,
308+
// rerun once the defer signal clears so openReport includes the loaded onboarding data.
309309
fetchReport();
310-
}, [route, isLinkedMessagePageReady, reportActionIDFromRoute]);
310+
}, [route, isLinkedMessagePageReady, reportActionIDFromRoute, shouldDeferGuidedSetupOpenReport, onboardingSignal]);
311311

312312
useEffect(() => {
313313
// This function is only triggered when a user is invited to a room after opening the link.

tests/actions/ReportTest.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7935,6 +7935,50 @@ describe('actions/Report', () => {
79357935
expect(result?.failureData).toBeDefined();
79367936
});
79377937

7938+
it('should return guided setup data for pending invite onboarding when regular guided setup is completed', async () => {
7939+
await setupUserWithConciergeChat();
7940+
await Onyx.merge(ONYXKEYS.NVP_ONBOARDING, {hasCompletedGuidedSetupFlow: true});
7941+
await waitForBatchedUpdates();
7942+
7943+
const introSelected: OnyxTypes.IntroSelected = {
7944+
choice: CONST.ONBOARDING_CHOICES.ADMIN,
7945+
inviteType: CONST.ONBOARDING_INVITE_TYPES.WORKSPACE,
7946+
isInviteOnboardingComplete: false,
7947+
};
7948+
const result = Report.getGuidedSetupDataForOpenReport(introSelected);
7949+
7950+
expect(result).toBeDefined();
7951+
expect(result?.guidedSetupData).toContain(CONST.ONBOARDING_TASK_TYPE.REVIEW_WORKSPACE_SETTINGS);
7952+
expect(result?.optimisticData.find((update) => update.key === ONYXKEYS.NVP_ONBOARDING)).toBeUndefined();
7953+
});
7954+
7955+
it('should complete regular onboarding for first-time pending invite onboarding', async () => {
7956+
await setupUserWithConciergeChat();
7957+
await Onyx.merge(ONYXKEYS.NVP_ONBOARDING, {hasCompletedGuidedSetupFlow: false});
7958+
await waitForBatchedUpdates();
7959+
7960+
const introSelected: OnyxTypes.IntroSelected = {
7961+
choice: CONST.ONBOARDING_CHOICES.ADMIN,
7962+
inviteType: CONST.ONBOARDING_INVITE_TYPES.WORKSPACE,
7963+
isInviteOnboardingComplete: false,
7964+
};
7965+
const result = Report.getGuidedSetupDataForOpenReport(introSelected);
7966+
7967+
expect(result).toBeDefined();
7968+
expect(result?.optimisticData.find((update) => update.key === ONYXKEYS.NVP_ONBOARDING)?.value).toEqual({hasCompletedGuidedSetupFlow: true});
7969+
});
7970+
7971+
it('should return undefined for completed regular onboarding when invite onboarding is not pending', async () => {
7972+
await setupUserWithConciergeChat();
7973+
await Onyx.merge(ONYXKEYS.NVP_ONBOARDING, {hasCompletedGuidedSetupFlow: true});
7974+
await waitForBatchedUpdates();
7975+
7976+
const introSelected: OnyxTypes.IntroSelected = {choice: CONST.ONBOARDING_CHOICES.ADMIN, isInviteOnboardingComplete: false};
7977+
const result = Report.getGuidedSetupDataForOpenReport(introSelected);
7978+
7979+
expect(result).toBeUndefined();
7980+
});
7981+
79387982
it.each<[boolean, string]>([
79397983
[true, 'should be defined'],
79407984
[false, 'should be undefined'],

0 commit comments

Comments
 (0)