Skip to content

Commit 42e9c77

Browse files
authored
Merge pull request Expensify#90404 from dukenv0307/fix/66424-part-17
refactor createAndOpenSearchTransactionThread to use isSelfTourViewed and hasCompletedGuidedSetupFlow from useOnyx
2 parents 1c966a1 + 3b00979 commit 42e9c77

5 files changed

Lines changed: 133 additions & 81 deletions

File tree

src/components/Search/SearchList/ListItem/TransactionGroupListExpanded.tsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import CONST from '@src/CONST';
3333
import ONYXKEYS from '@src/ONYXKEYS';
3434
import ROUTES from '@src/ROUTES';
3535
import {columnsSelector} from '@src/selectors/AdvancedSearchFiltersForm';
36+
import {hasCompletedGuidedSetupFlowSelector, hasSeenTourSelector} from '@src/selectors/Onboarding';
3637
import type * as OnyxTypes from '@src/types/onyx';
3738
import type {TransactionGroupListExpandedProps, TransactionListItemType} from './types';
3839

@@ -69,6 +70,8 @@ function TransactionGroupListExpanded<TItem extends ListItem>({
6970
const [isMobileSelectionModeEnabled] = useOnyx(ONYXKEYS.RAM_ONLY_MOBILE_SELECTION_MODE);
7071
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
7172
const [betas] = useOnyx(ONYXKEYS.BETAS);
73+
const [isSelfTourViewed] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasSeenTourSelector});
74+
const [hasCompletedGuidedSetupFlow] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasCompletedGuidedSetupFlowSelector});
7275
const [visibleColumns] = useOnyx(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM, {selector: columnsSelector});
7376
const [allTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION);
7477

@@ -159,15 +162,17 @@ function TransactionGroupListExpanded<TItem extends ListItem>({
159162

160163
const navigateToTransactionThread = () => {
161164
if (!transactionItem?.reportAction?.childReportID) {
162-
createAndOpenSearchTransactionThread(
163-
transactionItem,
165+
createAndOpenSearchTransactionThread({
166+
item: transactionItem,
164167
introSelected,
165168
backTo,
166-
currentUserDetails.email ?? '',
167-
currentUserDetails.accountID,
169+
currentUserLogin: currentUserDetails.email ?? '',
170+
currentUserAccountID: currentUserDetails.accountID,
168171
betas,
169-
transactionItem?.reportAction?.childReportID,
170-
);
172+
isSelfTourViewed,
173+
hasCompletedGuidedSetupFlow,
174+
IOUTransactionID: transactionItem?.reportAction?.childReportID,
175+
});
171176
return;
172177
}
173178
markReportIDAsExpense(reportID);

src/components/Search/SearchStaticList.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import StatusBadge from '@components/StatusBadge';
2424
import TransactionItemRow from '@components/TransactionItemRow';
2525
import {useCurrencyListActions} from '@hooks/useCurrencyList';
2626
import useLocalize from '@hooks/useLocalize';
27+
import useOnyx from '@hooks/useOnyx';
2728
import useStyleUtils from '@hooks/useStyleUtils';
2829
import useTheme from '@hooks/useTheme';
2930
import useThemeStyles from '@hooks/useThemeStyles';
@@ -32,7 +33,9 @@ import Navigation from '@libs/Navigation/Navigation';
3233
import {getReportStatusColorStyle, getReportStatusTranslation, isOneTransactionReport} from '@libs/ReportUtils';
3334
import {createAndOpenSearchTransactionThread, getSections, getSortedSections, getValidGroupBy} from '@libs/SearchUIUtils';
3435
import CONST from '@src/CONST';
36+
import ONYXKEYS from '@src/ONYXKEYS';
3537
import ROUTES from '@src/ROUTES';
38+
import {hasCompletedGuidedSetupFlowSelector, hasSeenTourSelector} from '@src/selectors/Onboarding';
3639
import type {SearchResults} from '@src/types/onyx';
3740
import type {TransactionListItemType} from './SearchList/ListItem/types';
3841
import UserInfoCellsWithArrow from './SearchList/ListItem/UserInfoCellsWithArrow';
@@ -73,6 +76,8 @@ function SearchStaticList({
7376
const session = useSession();
7477
const accountID = session?.accountID ?? CONST.DEFAULT_NUMBER_ID;
7578
const email = session?.email;
79+
const [isSelfTourViewed] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasSeenTourSelector});
80+
const [hasCompletedGuidedSetupFlow] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasCompletedGuidedSetupFlowSelector});
7681

7782
const [showPendingExpensePlaceholder, setShowPendingExpensePlaceholder] = useState(
7883
() => hasDeferredWrite(CONST.DEFERRED_LAYOUT_WRITE_KEYS.SEARCH) || Navigation.getIsFullscreenPreInsertedUnderRHP(),
@@ -128,7 +133,18 @@ function SearchStaticList({
128133
// betas and introSelected are passed as undefined to avoid extra Onyx subscriptions in this lightweight placeholder.
129134
// They're only used for guided-setup onboarding data, which is gated behind introSelected/onboarding checks
130135
// that won't apply here - the user has already completed onboarding if they're submitting expenses.
131-
createAndOpenSearchTransactionThread(item, undefined, backTo, email ?? '', accountID, undefined, item.reportAction?.childReportID, undefined, shouldOpenTransactionThread);
136+
createAndOpenSearchTransactionThread({
137+
item,
138+
introSelected: undefined,
139+
backTo,
140+
currentUserLogin: email ?? '',
141+
currentUserAccountID: accountID,
142+
betas: undefined,
143+
isSelfTourViewed,
144+
hasCompletedGuidedSetupFlow,
145+
IOUTransactionID: item.reportAction?.childReportID,
146+
shouldNavigate: shouldOpenTransactionThread,
147+
});
132148
if (shouldOpenTransactionThread) {
133149
return;
134150
}

src/components/Search/index.tsx

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ import ONYXKEYS from '@src/ONYXKEYS';
7676
import ROUTES from '@src/ROUTES';
7777
import SCREENS from '@src/SCREENS';
7878
import {columnsSelector} from '@src/selectors/AdvancedSearchFiltersForm';
79+
import {hasCompletedGuidedSetupFlowSelector, hasSeenTourSelector} from '@src/selectors/Onboarding';
7980
import type {OutstandingReportsByPolicyIDDerivedValue, SaveSearch, Transaction} from '@src/types/onyx';
8081
import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage';
8182
import type SearchResults from '@src/types/onyx/SearchResults';
@@ -258,6 +259,8 @@ function Search({
258259
const [transactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION);
259260
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
260261
const [betas] = useOnyx(ONYXKEYS.BETAS);
262+
const [isSelfTourViewed] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasSeenTourSelector});
263+
const [hasCompletedGuidedSetupFlow] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasCompletedGuidedSetupFlowSelector});
261264
const previousTransactions = usePrevious(transactions);
262265
const [reportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS);
263266
const [outstandingReportsByPolicyID] = useOnyx(ONYXKEYS.DERIVED.OUTSTANDING_REPORTS_BY_POLICY_ID);
@@ -1094,7 +1097,18 @@ function Search({
10941097
if (isTransactionItem && !item?.reportAction?.childReportID) {
10951098
// If the report is unreported (self DM), we want to open the track expense thread instead of a report with an ID of 0
10961099
const shouldOpenTransactionThread = !isOneTransactionReport(item.report) || item.reportID === CONST.REPORT.UNREPORTED_REPORT_ID;
1097-
createAndOpenSearchTransactionThread(item, introSelected, backTo, email ?? '', accountID, betas, item?.reportAction?.childReportID, undefined, shouldOpenTransactionThread);
1100+
createAndOpenSearchTransactionThread({
1101+
item,
1102+
introSelected,
1103+
backTo,
1104+
currentUserLogin: email ?? '',
1105+
currentUserAccountID: accountID,
1106+
betas,
1107+
isSelfTourViewed,
1108+
hasCompletedGuidedSetupFlow,
1109+
IOUTransactionID: item?.reportAction?.childReportID,
1110+
shouldNavigate: shouldOpenTransactionThread,
1111+
});
10981112
if (shouldOpenTransactionThread) {
10991113
return;
11001114
}
@@ -1141,17 +1155,19 @@ function Search({
11411155
const firstTransaction = item.transactions.at(0);
11421156
if (item.isOneTransactionReport && firstTransaction && transactionPreviewData) {
11431157
if (!firstTransaction?.reportAction?.childReportID) {
1144-
createAndOpenSearchTransactionThread(
1145-
firstTransaction,
1158+
createAndOpenSearchTransactionThread({
1159+
item: firstTransaction,
11461160
introSelected,
11471161
backTo,
1148-
email ?? '',
1149-
accountID,
1162+
currentUserLogin: email ?? '',
1163+
currentUserAccountID: accountID,
11501164
betas,
1151-
firstTransaction?.reportAction?.childReportID,
1165+
isSelfTourViewed,
1166+
hasCompletedGuidedSetupFlow,
1167+
IOUTransactionID: firstTransaction?.reportAction?.childReportID,
11521168
transactionPreviewData,
1153-
false,
1154-
);
1169+
shouldNavigate: false,
1170+
});
11551171
} else {
11561172
setOptimisticDataForTransactionThreadPreview(firstTransaction, transactionPreviewData, firstTransaction?.reportAction?.childReportID);
11571173
}
@@ -1208,6 +1224,8 @@ function Search({
12081224
unmarkReportIDAsMultiTransactionExpense,
12091225
introSelected,
12101226
betas,
1227+
isSelfTourViewed,
1228+
hasCompletedGuidedSetupFlow,
12111229
email,
12121230
accountID,
12131231
queryJSON,

src/libs/SearchUIUtils.ts

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2490,18 +2490,55 @@ function getTaskSections(
24902490
return [tasks, tasks.length];
24912491
}
24922492

2493+
type CreateAndOpenSearchTransactionThreadParams = {
2494+
/** The transaction list item being opened */
2495+
item: TransactionListItemType;
2496+
2497+
/** The intro selected by the user */
2498+
introSelected: OnyxEntry<OnyxTypes.IntroSelected>;
2499+
2500+
/** The route to go back to after navigation */
2501+
backTo: string;
2502+
2503+
/** The current user's login */
2504+
currentUserLogin: string;
2505+
2506+
/** The current user's account ID */
2507+
currentUserAccountID: number;
2508+
2509+
/** Beta features list */
2510+
betas: OnyxEntry<OnyxTypes.Beta[]>;
2511+
2512+
/** Whether the user has seen the self tour */
2513+
isSelfTourViewed: boolean | undefined;
2514+
2515+
/** Whether the user has completed the guided setup flow */
2516+
hasCompletedGuidedSetupFlow: boolean | undefined;
2517+
2518+
/** Existing transaction thread report ID (childReportID), if any */
2519+
IOUTransactionID?: string;
2520+
2521+
/** Preview data used to set optimistic data for the transaction thread preview */
2522+
transactionPreviewData?: TransactionPreviewData;
2523+
2524+
/** Whether to navigate to the transaction thread after creating it */
2525+
shouldNavigate?: boolean;
2526+
};
2527+
24932528
/** Creates transaction thread report and navigates to it from the search page */
2494-
function createAndOpenSearchTransactionThread(
2495-
item: TransactionListItemType,
2496-
introSelected: OnyxEntry<OnyxTypes.IntroSelected>,
2497-
backTo: string,
2498-
currentUserLogin: string,
2499-
currentUserAccountID: number,
2500-
betas: OnyxEntry<OnyxTypes.Beta[]>,
2501-
IOUTransactionID?: string,
2502-
transactionPreviewData?: TransactionPreviewData,
2529+
function createAndOpenSearchTransactionThread({
2530+
item,
2531+
introSelected,
2532+
backTo,
2533+
currentUserLogin,
2534+
currentUserAccountID,
2535+
betas,
2536+
isSelfTourViewed,
2537+
hasCompletedGuidedSetupFlow,
2538+
IOUTransactionID,
2539+
transactionPreviewData,
25032540
shouldNavigate = true,
2504-
) {
2541+
}: CreateAndOpenSearchTransactionThreadParams) {
25052542
const isFromSelfDM = item.reportID === CONST.REPORT.UNREPORTED_REPORT_ID;
25062543
const isDeleted = isDeletedTransaction(item);
25072544
const iouReportAction = getIOUActionForReportID(isFromSelfDM ? findSelfDMReportID() : item.reportID, item.transactionID);
@@ -2537,6 +2574,8 @@ function createAndOpenSearchTransactionThread(
25372574
iouReportAction: reportActionToPass,
25382575
transaction,
25392576
transactionViolations,
2577+
isSelfTourViewed,
2578+
hasCompletedGuidedSetupFlow,
25402579
});
25412580
}
25422581

tests/unit/Search/SearchUIUtilsTest.ts

Lines changed: 30 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -8651,24 +8651,26 @@ describe('SearchUIUtils', () => {
86518651
const introSelectedData: OnyxTypes.IntroSelected = {choice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM};
86528652
const currentUserLogin = 'test@example.com';
86538653
const currentUserAccountID = 1;
8654+
const baseParams = {
8655+
item: transactionListItem,
8656+
introSelected: introSelectedData,
8657+
backTo,
8658+
currentUserLogin,
8659+
currentUserAccountID,
8660+
betas: undefined,
8661+
isSelfTourViewed: false,
8662+
hasCompletedGuidedSetupFlow: true,
8663+
IOUTransactionID: threadReportID,
8664+
};
8665+
86548666
beforeEach(() => {
86558667
jest.clearAllMocks();
86568668
});
86578669

86588670
test('Should create transaction thread report and set optimistic data necessary for its preview', () => {
86598671
(createTransactionThreadReport as jest.Mock).mockReturnValue(threadReport);
86608672

8661-
SearchUIUtils.createAndOpenSearchTransactionThread(
8662-
transactionListItem,
8663-
introSelectedData,
8664-
backTo,
8665-
currentUserLogin,
8666-
currentUserAccountID,
8667-
undefined,
8668-
threadReportID,
8669-
undefined,
8670-
false,
8671-
);
8673+
SearchUIUtils.createAndOpenSearchTransactionThread({...baseParams, shouldNavigate: false});
86728674

86738675
expect(setOptimisticDataForTransactionThreadPreview).toHaveBeenCalled();
86748676
// The full reportAction is passed to preserve originalMessage.type for proper expense type detection
@@ -8681,41 +8683,28 @@ describe('SearchUIUtils', () => {
86818683
iouReportAction: reportAction1,
86828684
transaction: undefined,
86838685
transactionViolations: undefined,
8686+
isSelfTourViewed: false,
8687+
hasCompletedGuidedSetupFlow: true,
86848688
});
86858689
});
86868690

86878691
test('Should not navigate if shouldNavigate = false', () => {
8688-
SearchUIUtils.createAndOpenSearchTransactionThread(
8689-
transactionListItem,
8690-
introSelectedData,
8691-
backTo,
8692-
currentUserLogin,
8693-
currentUserAccountID,
8694-
undefined,
8695-
threadReportID,
8696-
undefined,
8697-
false,
8698-
);
8692+
SearchUIUtils.createAndOpenSearchTransactionThread({...baseParams, shouldNavigate: false});
86998693
expect(Navigation.navigate).not.toHaveBeenCalled();
87008694
});
87018695

87028696
test('Should handle navigation if shouldNavigate = true', () => {
8703-
SearchUIUtils.createAndOpenSearchTransactionThread(
8704-
transactionListItem,
8705-
introSelectedData,
8706-
backTo,
8707-
currentUserLogin,
8708-
currentUserAccountID,
8709-
undefined,
8710-
threadReportID,
8711-
undefined,
8712-
true,
8713-
);
8697+
SearchUIUtils.createAndOpenSearchTransactionThread({...baseParams, shouldNavigate: true});
87148698
// For one-transaction reports (isOneTransactionReport = true), navigation goes to the parent report (item.reportID)
87158699
// instead of the transaction thread report
87168700
expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.SEARCH_REPORT.getRoute({reportID: transactionListItem.reportID, backTo}));
87178701
});
87188702

8703+
test('Should default shouldNavigate to true when not provided', () => {
8704+
SearchUIUtils.createAndOpenSearchTransactionThread(baseParams);
8705+
expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.SEARCH_REPORT.getRoute({reportID: transactionListItem.reportID, backTo}));
8706+
});
8707+
87198708
test('Should fallback to childReportID from IOU action when transaction thread report is not in Onyx', async () => {
87208709
(createTransactionThreadReport as jest.Mock).mockReset();
87218710
const childReportID = 'child-thread-456';
@@ -8731,17 +8720,12 @@ describe('SearchUIUtils', () => {
87318720
});
87328721
await waitForBatchedUpdates();
87338722

8734-
SearchUIUtils.createAndOpenSearchTransactionThread(
8735-
multiTransactionItem,
8736-
introSelectedData,
8737-
backTo,
8738-
currentUserLogin,
8739-
currentUserAccountID,
8740-
undefined,
8741-
undefined,
8742-
undefined,
8743-
true,
8744-
);
8723+
SearchUIUtils.createAndOpenSearchTransactionThread({
8724+
...baseParams,
8725+
item: multiTransactionItem,
8726+
IOUTransactionID: undefined,
8727+
shouldNavigate: true,
8728+
});
87458729

87468730
expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.SEARCH_REPORT.getRoute({reportID: childReportID, backTo}));
87478731
});
@@ -8750,25 +8734,15 @@ describe('SearchUIUtils', () => {
87508734
(createTransactionThreadReport as jest.Mock).mockReturnValue(threadReport);
87518735
const customIntroSelected: OnyxTypes.IntroSelected = {choice: CONST.ONBOARDING_CHOICES.PERSONAL_SPEND};
87528736

8753-
SearchUIUtils.createAndOpenSearchTransactionThread(
8754-
transactionListItem,
8755-
customIntroSelected,
8756-
backTo,
8757-
currentUserLogin,
8758-
currentUserAccountID,
8759-
undefined,
8760-
threadReportID,
8761-
undefined,
8762-
false,
8763-
);
8737+
SearchUIUtils.createAndOpenSearchTransactionThread({...baseParams, introSelected: customIntroSelected, shouldNavigate: false});
87648738

87658739
expect(jest.mocked(createTransactionThreadReport).mock.calls.at(0)?.at(0)?.introSelected).toEqual(customIntroSelected);
87668740
});
87678741

87688742
test('Should pass undefined introSelected without bypassing with empty values', () => {
87698743
(createTransactionThreadReport as jest.Mock).mockReturnValue(threadReport);
87708744

8771-
SearchUIUtils.createAndOpenSearchTransactionThread(transactionListItem, undefined, backTo, currentUserLogin, currentUserAccountID, undefined, threadReportID, undefined, false);
8745+
SearchUIUtils.createAndOpenSearchTransactionThread({...baseParams, introSelected: undefined, shouldNavigate: false});
87728746

87738747
expect(jest.mocked(createTransactionThreadReport).mock.calls.at(0)?.at(0)?.introSelected).toBeUndefined();
87748748
});

0 commit comments

Comments
 (0)