diff --git a/src/hooks/useAutoCreateSubmitWorkspace.ts b/src/hooks/useAutoCreateSubmitWorkspace.ts index 5c86630fcd1d..a35d71c41eda 100644 --- a/src/hooks/useAutoCreateSubmitWorkspace.ts +++ b/src/hooks/useAutoCreateSubmitWorkspace.ts @@ -44,6 +44,8 @@ function useAutoCreateSubmitWorkspace() { [], ); const [hasEditableGroupPolicy] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {selector: groupPolicySelector}); + const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID); + const [conciergeChat] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${conciergeReportID}`); const autoCreateSubmitWorkspace = useCallback( async (firstName: string, lastName: string) => { @@ -82,6 +84,7 @@ function useAutoCreateSubmitWorkspace() { onboardingPolicyID: newPolicyID, introSelected, isSelfTourViewed, + conciergeChat, }); } catch (error) { Log.warn('[useAutoCreateSubmitWorkspace] Error completing onboarding', {error}); @@ -110,6 +113,7 @@ function useAutoCreateSubmitWorkspace() { betas, hasActiveAdminPolicies, shouldUseNarrowLayout, + conciergeChat, ], ); diff --git a/src/hooks/useAutoCreateTrackWorkspace.ts b/src/hooks/useAutoCreateTrackWorkspace.ts index e53fb71b7e4a..cb3e6439c8f8 100644 --- a/src/hooks/useAutoCreateTrackWorkspace.ts +++ b/src/hooks/useAutoCreateTrackWorkspace.ts @@ -50,6 +50,9 @@ function useAutoCreateTrackWorkspace() { const [hasPaidGroupAdminPolicy] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {selector: paidGroupPolicySelector}); const [conciergeChatReportID = ''] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID); + const [conciergeChat] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${conciergeChatReportID}`); + const [selfDMReportID] = useOnyx(ONYXKEYS.SELF_DM_REPORT_ID); + const [selfDMReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReportID}`); const [onboardingValues] = useOnyx(ONYXKEYS.NVP_ONBOARDING); const [reportNameValuePairs] = useOnyx(ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS); const {isBetaEnabled} = usePermissions(); @@ -104,6 +107,8 @@ function useAutoCreateTrackWorkspace() { personalTrackGoal: onboardingPurposeSelected === CONST.ONBOARDING_CHOICES.TRACK_PERSONAL && !!personalTrackGoal ? personalTrackGoal : undefined, introSelected, isSelfTourViewed, + conciergeChat, + selfDMReport, }); if (isSidePanelReportSupported) { @@ -156,6 +161,8 @@ function useAutoCreateTrackWorkspace() { conciergeChatReportID, reportNameValuePairs, mergedAccountConciergeReportID, + conciergeChat, + selfDMReport, ], ); diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index cd1e4f062cee..194e3aa8b380 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -11554,6 +11554,12 @@ type PrepareOnboardingOnyxDataParams = { onboardingPurposeSelected?: OnboardingPurpose; // TODO: isSelfTourViewed will be required eventually. Refactor issue: https://github.com/Expensify/App/issues/66424 isSelfTourViewed?: boolean; + /** The concierge chat report, looked up by conciergeReportID. Falls back to getChatByParticipants using the deprecated module-level Onyx data while the refactor is in progress. */ + conciergeChat?: OnyxEntry; + /** The admins chat report, looked up by adminsChatReportID. Falls back to the deprecated module-level Onyx data while the refactor is in progress. */ + adminsChatReport?: OnyxEntry; + /** The self-DM report, looked up by ONYXKEYS.SELF_DM_REPORT_ID. Falls back to the deprecated module-level Onyx data while the refactor is in progress. */ + selfDMReport?: OnyxEntry; }; function prepareOnboardingOnyxData({ @@ -11568,6 +11574,9 @@ function prepareOnboardingOnyxData({ isInvitedAccountant, onboardingPurposeSelected, isSelfTourViewed, + conciergeChat: conciergeChatParam, + adminsChatReport: adminsChatReportParam, + selfDMReport: selfDMReportParam, }: PrepareOnboardingOnyxDataParams) { if (engagementChoice === CONST.ONBOARDING_CHOICES.PERSONAL_SPEND) { // eslint-disable-next-line no-param-reassign @@ -11582,8 +11591,9 @@ function prepareOnboardingOnyxData({ const shouldPostTasksInAdminsRoom = isPostingTasksInAdminsRoom(engagementChoice); // Server picks the inboxAdminsBespoke variant at response time, so optimistic writes here would be stale. const shouldDeferOptimisticTasks = engagementChoice === CONST.ONBOARDING_CHOICES.MANAGE_TEAM; - const adminsChatReport = deprecatedAllReports?.[`${ONYXKEYS.COLLECTION.REPORT}${adminsChatReportID}`]; + const adminsChatReport = adminsChatReportParam ?? deprecatedAllReports?.[`${ONYXKEYS.COLLECTION.REPORT}${adminsChatReportID}`]; const conciergeChat = + conciergeChatParam ?? getChatByParticipants([CONST.ACCOUNT_ID.CONCIERGE, deprecatedCurrentUserAccountID ?? CONST.DEFAULT_NUMBER_ID], deprecatedAllReports, false) ?? (conciergeReportIDOnyxConnect ? {reportID: conciergeReportIDOnyxConnect} : undefined); const targetChatReport = shouldPostTasksInAdminsRoom @@ -11977,7 +11987,8 @@ function prepareOnboardingOnyxData({ lastVisibleActionCreated: '', hasOutstandingChildTask: false, }; - const report = deprecatedAllReports?.[`${ONYXKEYS.COLLECTION.REPORT}${targetChatReportID}`]; + // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- targetChatReport may be a stub with only reportID/policyID/chatType; the consumers below handle missing fields gracefully. + const report = targetChatReport as OnyxEntry; const canUserPerformWriteActionVariable = canUserPerformWriteAction(report, false); const {lastMessageText = ''} = getLastVisibleMessageActionUtils(targetChatReportID, canUserPerformWriteActionVariable); if (lastMessageText) { @@ -12088,7 +12099,7 @@ function prepareOnboardingOnyxData({ (!onboardingPurposeSelected || onboardingPurposeSelected === CONST.ONBOARDING_CHOICES.PERSONAL_SPEND || onboardingPurposeSelected === CONST.ONBOARDING_CHOICES.TRACK_PERSONAL)) ) { const selfDMReportID = findSelfDMReportID(); - let selfDMReport = deprecatedAllReports?.[`${ONYXKEYS.COLLECTION.REPORT}${selfDMReportID}`]; + let selfDMReport = selfDMReportParam ?? deprecatedAllReports?.[`${ONYXKEYS.COLLECTION.REPORT}${selfDMReportID}`]; let createdAction: ReportAction; if (!selfDMReport) { const currentTime = DateUtils.getDBTime(); diff --git a/src/libs/actions/Report/index.ts b/src/libs/actions/Report/index.ts index d76d52390bf3..381edf84addf 100644 --- a/src/libs/actions/Report/index.ts +++ b/src/libs/actions/Report/index.ts @@ -5438,6 +5438,12 @@ type CompleteOnboardingProps = { shouldWaitForRHPVariantInitialization?: boolean; introSelected: OnyxEntry; isSelfTourViewed: boolean | undefined; + /** The concierge chat report, looked up by ONYXKEYS.CONCIERGE_REPORT_ID. */ + conciergeChat?: OnyxEntry; + /** The admins chat report, looked up by ONYXKEYS.ONBOARDING_ADMINS_CHAT_REPORT_ID. */ + adminsChatReport?: OnyxEntry; + /** The self-DM report, looked up by ONYXKEYS.SELF_DM_REPORT_ID. */ + selfDMReport?: OnyxEntry; }; async function completeOnboarding({ @@ -5458,6 +5464,9 @@ async function completeOnboarding({ shouldWaitForRHPVariantInitialization = false, introSelected, isSelfTourViewed, + conciergeChat, + adminsChatReport, + selfDMReport, }: CompleteOnboardingProps) { const onboardingData = prepareOnboardingOnyxData({ introSelected, @@ -5471,6 +5480,9 @@ async function completeOnboarding({ isInvitedAccountant, onboardingPurposeSelected, isSelfTourViewed, + conciergeChat, + adminsChatReport, + selfDMReport, }); if (!onboardingData) { return; diff --git a/src/pages/OnboardingInterestedFeatures/BaseOnboardingInterestedFeatures.tsx b/src/pages/OnboardingInterestedFeatures/BaseOnboardingInterestedFeatures.tsx index a4d39dd623a2..a458eddb85f4 100644 --- a/src/pages/OnboardingInterestedFeatures/BaseOnboardingInterestedFeatures.tsx +++ b/src/pages/OnboardingInterestedFeatures/BaseOnboardingInterestedFeatures.tsx @@ -64,6 +64,8 @@ function BaseOnboardingInterestedFeatures({shouldUseNativeStyles}: BaseOnboardin const {isBetaEnabled} = usePermissions(); const [session] = useOnyx(ONYXKEYS.SESSION); const [conciergeReportID = ''] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID); + const [conciergeChat] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${conciergeReportID}`); + const [adminsChatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${onboardingAdminsChatReportID}`); const [reportNameValuePairs] = useOnyx(ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS); const activePolicy = useActivePolicy(); const hasActiveAdminPolicies = useHasActiveAdminPolicies(); @@ -235,6 +237,8 @@ function BaseOnboardingInterestedFeatures({shouldUseNativeStyles}: BaseOnboardin shouldWaitForRHPVariantInitialization: isSidePanelReportSupported, introSelected, isSelfTourViewed, + conciergeChat, + adminsChatReport, }); const rhpVariant = isSidePanelReportSupported ? extractRHPVariantFromResponse(response) : undefined; @@ -292,6 +296,8 @@ function BaseOnboardingInterestedFeatures({shouldUseNativeStyles}: BaseOnboardin hasActiveAdminPolicies, lastWorkspaceNumber, translate, + conciergeChat, + adminsChatReport, ]); // Create items for enabled features diff --git a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx index ca8f2c26af6b..399244ae32fd 100644 --- a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx +++ b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx @@ -50,6 +50,7 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat const [loginList] = useOnyx(ONYXKEYS.LOGINS, {selector: expensifyLoginsSelector}); const [onboardingValues] = useOnyx(ONYXKEYS.NVP_ONBOARDING); const [conciergeChatReportID = ''] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID); + const [conciergeChat] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${conciergeChatReportID}`); const {onboardingMessages} = useOnboardingMessages(); const [session] = useOnyx(ONYXKEYS.SESSION); const [onboardingPersonalDetailsForm] = useOnyx(ONYXKEYS.FORMS.ONBOARDING_PERSONAL_DETAILS_FORM); @@ -97,6 +98,7 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat onboardingPolicyID, introSelected, isSelfTourViewed, + conciergeChat, }); setOnboardingAdminsChatReportID(); @@ -130,6 +132,7 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat conciergeChatReportID, introSelected, isSelfTourViewed, + conciergeChat, ], ); diff --git a/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx b/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx index 014c386d2e2f..df28d4b7f595 100644 --- a/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx +++ b/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx @@ -64,6 +64,8 @@ function BaseOnboardingPurpose({shouldUseNativeStyles, shouldEnableMaxHeight, ro const {onboardingIsMediumOrLargerScreenWidth} = useResponsiveLayout(); const onboardingStep = useOnboardingStepCounter(SCREENS.ONBOARDING.PURPOSE); const [account] = useOnyx(ONYXKEYS.ACCOUNT); + const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID); + const [conciergeChat] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${conciergeReportID}`); const {onboardingMessages} = useOnboardingMessages(); const isPrivateDomainAndHasAccessiblePolicies = !account?.isFromPublicDomain && !!account?.hasAccessibleDomainPolicies; @@ -72,6 +74,7 @@ function BaseOnboardingPurpose({shouldUseNativeStyles, shouldEnableMaxHeight, ro const [onboardingErrorMessage, onboardingErrorMessageResult] = useOnyx(ONYXKEYS.ONBOARDING_ERROR_MESSAGE_TRANSLATION_KEY); const [onboardingPolicyID] = useOnyx(ONYXKEYS.ONBOARDING_POLICY_ID); const [onboardingAdminsChatReportID] = useOnyx(ONYXKEYS.ONBOARDING_ADMINS_CHAT_REPORT_ID); + const [adminsChatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${onboardingAdminsChatReportID}`); const [personalDetailsForm] = useOnyx(ONYXKEYS.FORMS.ONBOARDING_PERSONAL_DETAILS_FORM); const [onboardingCompanySize] = useOnyx(ONYXKEYS.ONBOARDING_COMPANY_SIZE); const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED); @@ -137,6 +140,8 @@ function BaseOnboardingPurpose({shouldUseNativeStyles, shouldEnableMaxHeight, ro companySize: onboardingCompanySize, introSelected, isSelfTourViewed, + conciergeChat, + adminsChatReport, }); return; diff --git a/src/pages/OnboardingWorkspaces/BaseOnboardingWorkspaces.tsx b/src/pages/OnboardingWorkspaces/BaseOnboardingWorkspaces.tsx index 6c395fc4ae78..2840384a4a41 100644 --- a/src/pages/OnboardingWorkspaces/BaseOnboardingWorkspaces.tsx +++ b/src/pages/OnboardingWorkspaces/BaseOnboardingWorkspaces.tsx @@ -64,6 +64,7 @@ function BaseOnboardingWorkspaces({route, shouldUseNativeStyles}: BaseOnboarding const {isBetaEnabled} = usePermissions(); const [conciergeReportID = ''] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID); + const [conciergeChat] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${conciergeReportID}`); const [onboardingValues] = useOnyx(ONYXKEYS.NVP_ONBOARDING); const isVsb = onboardingValues?.signupQualifier === CONST.ONBOARDING_SIGNUP_QUALIFIERS.VSB; @@ -93,6 +94,7 @@ function BaseOnboardingWorkspaces({route, shouldUseNativeStyles}: BaseOnboarding companySize: onboardingCompanySize, introSelected, isSelfTourViewed, + conciergeChat, }); setOnboardingAdminsChatReportID(); setOnboardingPolicyID(policy.policyID); diff --git a/tests/actions/ReportTest.ts b/tests/actions/ReportTest.ts index eae8e6264470..d750f358144d 100644 --- a/tests/actions/ReportTest.ts +++ b/tests/actions/ReportTest.ts @@ -2898,6 +2898,121 @@ describe('actions/Report', () => { const formEntries = Object.fromEntries(body as FormData); expect(formEntries.selectedInterestedFeatures).toBe(JSON.stringify(selectedInterestedFeatures)); }); + + it('should post onboarding tasks to the existing Concierge chat', async () => { + await Onyx.set(ONYXKEYS.SESSION, {email: TEST_USER_LOGIN, accountID: TEST_USER_ACCOUNT_ID}); + await waitForBatchedUpdates(); + + // An existing 1:1 Concierge chat the onboarding tasks should be posted to + const conciergeChatReportID = '9988776655'; + const conciergeChat: OnyxTypes.Report = { + reportID: conciergeChatReportID, + type: CONST.REPORT.TYPE.CHAT, + participants: { + [CONST.ACCOUNT_ID.CONCIERGE]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + [TEST_USER_ACCOUNT_ID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + }, + }; + // LOOKING_AROUND posts the onboarding tasks to the Concierge chat (not the #admins room) + const engagementChoice = CONST.ONBOARDING_CHOICES.LOOKING_AROUND; + const {onboardingMessages} = getOnboardingMessages(); + + const onboardingData = ReportUtils.prepareOnboardingOnyxData({ + engagementChoice, + onboardingMessage: onboardingMessages[engagementChoice], + companySize: CONST.ONBOARDING_COMPANY_SIZE.MICRO, + userReportedIntegration: null, + introSelected: {choice: engagementChoice}, + isSelfTourViewed: false, + conciergeChat, + }); + + // The onboarding optimistic data should target the existing Concierge chat + expect(onboardingData).toBeTruthy(); + const targetsConciergeChat = onboardingData?.optimisticData.some((update) => update.key.includes(conciergeChatReportID)); + expect(targetsConciergeChat).toBe(true); + }); + + it('should reuse the existing self-DM for a personal spend onboarding', async () => { + await Onyx.set(ONYXKEYS.SESSION, {email: TEST_USER_LOGIN, accountID: TEST_USER_ACCOUNT_ID}); + await waitForBatchedUpdates(); + + // An existing Concierge chat (the onboarding target) and an existing self-DM the personal spend + // onboarding should reuse instead of creating a new one + const conciergeChatReportID = '6677889900'; + const conciergeChat: OnyxTypes.Report = { + reportID: conciergeChatReportID, + type: CONST.REPORT.TYPE.CHAT, + participants: { + [CONST.ACCOUNT_ID.CONCIERGE]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + [TEST_USER_ACCOUNT_ID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + }, + }; + const selfDMReportID = '5544332211'; + const selfDMReport: OnyxTypes.Report = { + reportID: selfDMReportID, + type: CONST.REPORT.TYPE.CHAT, + chatType: CONST.REPORT.CHAT_TYPE.SELF_DM, + participants: { + [TEST_USER_ACCOUNT_ID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + }, + }; + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReportID}`, selfDMReport); + await waitForBatchedUpdates(); + + // PERSONAL_SPEND routes the onboarding to the user's self-DM + const engagementChoice = CONST.ONBOARDING_CHOICES.PERSONAL_SPEND; + const {onboardingMessages} = getOnboardingMessages(); + + const onboardingData = ReportUtils.prepareOnboardingOnyxData({ + engagementChoice, + onboardingMessage: onboardingMessages[engagementChoice], + companySize: CONST.ONBOARDING_COMPANY_SIZE.MICRO, + userReportedIntegration: null, + introSelected: {choice: engagementChoice}, + isSelfTourViewed: false, + conciergeChat, + }); + + // The existing self-DM should be reused, so no new self-DM is created + expect(onboardingData).toBeTruthy(); + expect(onboardingData?.selfDMParameters?.reportID).toBeUndefined(); + }); + + it('should optimistically create a self-DM for a personal spend onboarding when none exists', async () => { + await Onyx.set(ONYXKEYS.SESSION, {email: TEST_USER_LOGIN, accountID: TEST_USER_ACCOUNT_ID}); + await waitForBatchedUpdates(); + + // Only a Concierge chat exists (the onboarding target); there is no self-DM yet + const conciergeChatReportID = '1122334455'; + const conciergeChat: OnyxTypes.Report = { + reportID: conciergeChatReportID, + type: CONST.REPORT.TYPE.CHAT, + participants: { + [CONST.ACCOUNT_ID.CONCIERGE]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + [TEST_USER_ACCOUNT_ID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + }, + }; + const engagementChoice = CONST.ONBOARDING_CHOICES.PERSONAL_SPEND; + const {onboardingMessages} = getOnboardingMessages(); + + const onboardingData = ReportUtils.prepareOnboardingOnyxData({ + engagementChoice, + onboardingMessage: onboardingMessages[engagementChoice], + companySize: CONST.ONBOARDING_COMPANY_SIZE.MICRO, + userReportedIntegration: null, + introSelected: {choice: engagementChoice}, + isSelfTourViewed: false, + conciergeChat, + }); + + // A new self-DM is created and added to the optimistic data + expect(onboardingData).toBeTruthy(); + const newSelfDMReportID = onboardingData?.selfDMParameters?.reportID; + expect(newSelfDMReportID).toBeTruthy(); + const createsSelfDM = onboardingData?.optimisticData.some((update) => update.key === `${ONYXKEYS.COLLECTION.REPORT}${newSelfDMReportID}`); + expect(createsSelfDM).toBe(true); + }); }); describe('markAllMessagesAsRead', () => {