Skip to content

fix: avoid duplicated /SearchForReport calls #60810

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
a9ab1fe
fix: avoid duplicated /SearchForReport calls
martasudol Apr 24, 2025
0b5ea99
Merge branch 'main' into avoid-duplicated-search-for-reports-calls
martasudol Apr 24, 2025
d1f5510
fix: avoid duplicated /SearchForReport calls
martasudol Apr 24, 2025
52b835f
fix: avoid duplicated /SearchForReport calls
martasudol Apr 24, 2025
2e9d962
fix: avoid duplicated /SearchForReport calls
martasudol Apr 24, 2025
712ca22
fix: avoid duplicated /SearchForReport calls
martasudol Apr 24, 2025
fdf27e4
fix: avoid duplicated /SearchForReport calls
martasudol Apr 24, 2025
214c235
fix: avoid duplicated /SearchForReport calls
martasudol Apr 24, 2025
c2849bb
fix: avoid duplicated /SearchForReport calls
martasudol Apr 25, 2025
6d7b55e
Fix bug with /SearchForReports calls on input focus
martasudol Apr 28, 2025
a0c27f6
Moved Search feature documentation to contributingGuides
martasudol Apr 28, 2025
69fe490
Revert docs/_data_.routes.yml to match main
martasudol Apr 28, 2025
fda4689
Fix prettier issues
martasudol Apr 28, 2025
b4a006c
Fix tests
martasudol Apr 28, 2025
65a1c8d
Fix prettier
martasudol Apr 28, 2025
26ad8a4
Fix prettier
martasudol Apr 28, 2025
3c97469
Use getQueryWithoutFilters to rely on query comparison to handle sear…
martasudol Apr 29, 2025
cc05bde
prettier & eslint fixes
martasudol Apr 29, 2025
a75af9c
prettier & eslint fixes
martasudol Apr 29, 2025
2d89229
prettier & eslint fixes
martasudol Apr 29, 2025
9420b43
Refactoring
martasudol Apr 30, 2025
43c5bb1
Prettier
martasudol Apr 30, 2025
641f18c
Merge branch 'main' into avoid-duplicated-search-for-reports-calls
martasudol May 12, 2025
6f94129
build search query optimization
martasudol May 12, 2025
5997f36
PR comments addressed
martasudol May 13, 2025
aa28dc9
prettier fixes
martasudol May 13, 2025
68cd35b
Merge branch 'main' into avoid-duplicated-search-for-reports-calls
martasudol May 14, 2025
83d600e
fix: `/SearchForReports` call on autocomplete result item click
martasudol May 14, 2025
2f7f97f
fix: `/SearchForReports` call missing for narrow resolutions
martasudol May 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 14 additions & 10 deletions docs/_data/_routes.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ platforms:
url: www.expensify.com
description: "Your account settings look like this:"
image: /assets/images/settings-old-dot.svg

# Hubs are comprised of subcategories and articles. Subcategories contain multiple related articles, but there can be standalone articles as well
hubs:
- href: getting-started
Expand All @@ -38,7 +38,7 @@ platforms:
title: Domains
icon: /assets/images/domains.svg
description: Claim and verify your company’s domain to access additional management and security features.

- href: bank-accounts-and-payments
title: Bank Accounts & Payments
icon: /assets/images/send-money.svg
Expand Down Expand Up @@ -88,16 +88,16 @@ platforms:
title: Travel
icon: /assets/images/plane.svg
description: Manage all your corporate travel needs with Expensify Travel.


- href: new-expensify
title: New Expensify
hub-title: New Expensify - Help & Resources
hub-description: Questions? Find the answers by clicking a Category or using the search bar.
url: new.expensify.com
description: "Your account settings look like this:"
image: /assets/images/settings-new-dot.svg

hubs:
- href: getting-started
title: Getting Started
Expand All @@ -113,7 +113,7 @@ platforms:
title: Workspaces
icon: /assets/images/shield.svg
description: Configure rules, settings, and limits for your company’s spending.

- href: reports-and-expenses
title: Reports & Expenses
icon: /assets/images/envelope-receipt.svg
Expand All @@ -128,12 +128,12 @@ platforms:
title: Connect Credit Cards
icon: /assets/images/bank-card.svg
description: Track credit card transactions and reconcile company cards.

- href: expensify-card
title: Expensify Card
icon: /assets/images/hand-card.svg
description: Explore the perks and benefits of the Expensify Card.

- href: travel
title: Travel
icon: /assets/images/plane.svg
Expand All @@ -148,9 +148,13 @@ platforms:
title: Settings
icon: /assets/images/gears.svg
description: Manage profile settings and notifications.

- href: billing-and-subscriptions
title: Expensify Billing & Subscriptions
icon: /assets/images/subscription-annual.svg
description: Review Expensify's subscription options, plan types, and payment methods.


- href: search-overview
title: Search Functionality in New Expensify
icon: /assets/images/magnifying-glass.svg
description: Search feature overview.
54 changes: 54 additions & 0 deletions docs/articles/new-expensify/search/Search-overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
title: Search Functionality in New Expensify
description: Learn how to effectively use the powerful search feature in New Expensify to find and filter your financial data quickly and efficiently.
---
<div id="expensify-classic" markdown="1">

The Search functionality in New Expensify provides a powerful way to locate and filter financial data.
With advanced autocomplete suggestions and predefined filters, you can quickly find specific expenses, reports,
invoices, and more across the platform.

## Main Uses

- **Locate specific transactions** - Quickly find expenses, invoices, or other financial data using keywords or filters.
- **Filter by status** - Easily view items based on their current state (Drafts, Outstanding, Approved, etc.).
- **Save frequent searches** - Store commonly used search parameters for quick access in the future.

## Search Components
The search functionality in Expensify consists of several key components:

- **Search input** - Located at the top of the Reports view for entering search terms.
- **Left-hand navigation (LHN)** - Allows switching between different data types and saved searches.
- **Predefined filters** - Quick-access filters at the top of each list view.
- **Autocomplete modal** - Suggestions that appear as you type in the search input.
- **Results list** - The formatted list of search results displayed below the filters, showing matching items based on
your search criteria and selected filters.

# How Search Works
## Basic Navigation
1. When you select a data type from the LHN (Expenses, Expense Reports, Chats, Invoices, or Trips), the system calls the `/Search` endpoint to display results.
2. Selecting a predefined filter (All, Drafts, Outstanding, etc.) also triggers the `/Search` endpoint with the appropriate parameters.

## Using Predefined Filters
Each data type offers specific predefined filters for quick access:

1. Navigate to the desired data type view (e.g., Expenses) via the LHN.
2. At the top of the list, select one of the predefined filters:
- **All** - Shows all items (default selection)
- **Drafts** - Items not yet submitted
- **Outstanding** - Items awaiting action
- **Approved** - Items that have received approval
- **Done** - Completed items
- **Paid** - Items that have been reimbursed or paid

Note: Available filters may vary depending on the data type selected.

## Using the Search Input
The search input provides powerful functionality:
1. Click in the search field at the top of the Reports view.
2. Begin typing your search term.
3. The autocomplete modal appears with suggestions:
- The first option always shows your exact search text with a magnifying glass icon
- Additional suggestions based on your input from `/SearchForReports` and Onyx data
4. Select first suggestion (magnifying glass icon with search text) or press Enter to execute the search (`/Search` endpoint call).
5. Clicking on any other suggestion (retrieved from either `/SearchForReports` or Onyx data) directly opens the selected report by calling the `/OpenReport` endpoint.
12 changes: 10 additions & 2 deletions src/components/Search/SearchAutocompleteList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import usePermissions from '@hooks/usePermissions';
import usePolicy from '@hooks/usePolicy';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';
import {searchInServer} from '@libs/actions/Report';
import {getCardFeedKey, getCardFeedNamesWithType} from '@libs/CardFeedUtils';
import {getCardDescription, isCard, isCardHiddenFromSearch, mergeCardListWithWorkspaceFeeds} from '@libs/CardUtils';
import memoize from '@libs/memoize';
Expand Down Expand Up @@ -57,6 +56,9 @@ type SearchAutocompleteListProps = {
/** Value of TextInput */
autocompleteQueryValue: string;

/** Callback to trigger search action * */
handleSearch?: (value: string) => void;

/** An optional item to always display on the top of the router list */
searchQueryItem?: SearchQueryItem;

Expand Down Expand Up @@ -130,6 +132,7 @@ function SearchRouterItem(props: UserListItemProps<OptionData> | SearchQueryList
function SearchAutocompleteList(
{
autocompleteQueryValue,
handleSearch,
searchQueryItem,
getAdditionalSections,
onListItemPress,
Expand Down Expand Up @@ -501,7 +504,12 @@ function SearchAutocompleteList(
}, [autocompleteQueryValue, filterOptions, searchOptions]);

useEffect(() => {
searchInServer(autocompleteQueryValue.trim());
if (!handleSearch) {
return;
}
handleSearch(autocompleteQueryValue);
// eslint-disable-next-line react-compiler/react-compiler
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [autocompleteQueryValue]);

/* Sections generation */
Expand Down
18 changes: 10 additions & 8 deletions src/components/Search/SearchPageHeader/SearchPageHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,24 +28,25 @@ type SearchPageHeaderProps = {
hideSearchRouterList?: () => void;
onSearchRouterFocus?: () => void;
headerButtonsOptions: Array<DropdownOption<SearchHeaderOptionValue>>;
handleSearch?: (value: string) => void;
};

type SearchHeaderOptionValue = DeepValueOf<typeof CONST.SEARCH.BULK_ACTION_TYPES> | undefined;

function SearchPageHeader({queryJSON, searchName, searchRouterListVisible, hideSearchRouterList, onSearchRouterFocus, headerButtonsOptions}: SearchPageHeaderProps) {
function SearchPageHeader({queryJSON, searchName, searchRouterListVisible, hideSearchRouterList, onSearchRouterFocus, headerButtonsOptions, handleSearch}: SearchPageHeaderProps) {
const styles = useThemeStyles();
const {shouldUseNarrowLayout} = useResponsiveLayout();
const {selectedTransactions} = useSearchContext();
const [selectionMode] = useOnyx(ONYXKEYS.MOBILE_SELECTION_MODE);
const [selectionMode] = useOnyx(ONYXKEYS.MOBILE_SELECTION_MODE, {canBeMissing: true});
const personalDetails = usePersonalDetails();
const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT);
const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: true});
const taxRates = getAllTaxRates();
const [userCardList] = useOnyx(ONYXKEYS.CARD_LIST);
const [workspaceCardFeeds] = useOnyx(ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST);
const [userCardList] = useOnyx(ONYXKEYS.CARD_LIST, {canBeMissing: true});
const [workspaceCardFeeds] = useOnyx(ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST, {canBeMissing: true});
const allCards = useMemo(() => mergeCardListWithWorkspaceFeeds(workspaceCardFeeds ?? CONST.EMPTY_OBJECT, userCardList), [userCardList, workspaceCardFeeds]);
const [currencyList = {}] = useOnyx(ONYXKEYS.CURRENCY_LIST);
const [policyCategories] = useOnyx(ONYXKEYS.COLLECTION.POLICY_CATEGORIES);
const [policyTagsLists] = useOnyx(ONYXKEYS.COLLECTION.POLICY_TAGS);
const [currencyList = {}] = useOnyx(ONYXKEYS.CURRENCY_LIST, {canBeMissing: true});
const [policyCategories] = useOnyx(ONYXKEYS.COLLECTION.POLICY_CATEGORIES, {canBeMissing: true});
const [policyTagsLists] = useOnyx(ONYXKEYS.COLLECTION.POLICY_TAGS, {canBeMissing: true});

const selectedTransactionsKeys = Object.keys(selectedTransactions ?? {});

Expand Down Expand Up @@ -85,6 +86,7 @@ function SearchPageHeader({queryJSON, searchName, searchRouterListVisible, hideS
searchName={searchName}
hideSearchRouterList={hideSearchRouterList}
inputRightComponent={InputRightComponent}
handleSearch={handleSearch}
/>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,10 @@ type SearchPageHeaderInputProps = {
onSearchRouterFocus?: () => void;
searchName?: string;
inputRightComponent: React.ReactNode;
handleSearch?: (value: string) => void;
};

function SearchPageHeaderInput({queryJSON, searchRouterListVisible, hideSearchRouterList, onSearchRouterFocus, searchName, inputRightComponent}: SearchPageHeaderInputProps) {
function SearchPageHeaderInput({queryJSON, searchRouterListVisible, hideSearchRouterList, onSearchRouterFocus, searchName, inputRightComponent, handleSearch}: SearchPageHeaderInputProps) {
const {translate} = useLocalize();
const {canUseLeftHandBar} = usePermissions();
const [showPopupButton, setShowPopupButton] = useState(true);
Expand Down Expand Up @@ -118,7 +119,6 @@ function SearchPageHeaderInput({queryJSON, searchRouterListVisible, hideSearchRo

useEffect(() => {
setTextInputValue(isDefaultQuery ? '' : queryText);
setAutocompleteQueryValue(isDefaultQuery ? '' : queryText);
}, [isDefaultQuery, queryText]);

useEffect(() => {
Expand All @@ -144,6 +144,16 @@ function SearchPageHeaderInput({queryJSON, searchRouterListVisible, hideSearchRo
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

const handleSearchAction = useCallback(
(value: string) => {
if (!isAutocompleteListVisible || !handleSearch) {
return;
}
handleSearch(value);
},
[handleSearch, isAutocompleteListVisible],
);

const onSearchQueryChange = useCallback(
(userQuery: string) => {
const singleLineUserQuery = StringUtils.lineBreaksToSpaces(userQuery, true);
Expand Down Expand Up @@ -290,6 +300,7 @@ function SearchPageHeaderInput({queryJSON, searchRouterListVisible, hideSearchRo
<View style={[styles.flex1]}>
<SearchAutocompleteList
autocompleteQueryValue={autocompleteQueryValue}
handleSearch={handleSearchAction}
searchQueryItem={searchQueryItem}
onListItemPress={onListItemPress}
setTextQuery={setTextInputValue}
Expand Down Expand Up @@ -356,6 +367,7 @@ function SearchPageHeaderInput({queryJSON, searchRouterListVisible, hideSearchRo
<View style={[styles.mh65vh, !isAutocompleteListVisible && styles.dNone]}>
<SearchAutocompleteList
autocompleteQueryValue={autocompleteQueryValue}
handleSearch={handleSearchAction}
searchQueryItem={searchQueryItem}
onListItemPress={onListItemPress}
setTextQuery={setTextAndUpdateSelection}
Expand Down
7 changes: 6 additions & 1 deletion src/components/Search/SearchRouter/SearchRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import StringUtils from '@libs/StringUtils';
import Navigation from '@navigation/Navigation';
import type {ReportsSplitNavigatorParamList} from '@navigation/types';
import variables from '@styles/variables';
import {navigateToAndOpenReport} from '@userActions/Report';
import {navigateToAndOpenReport, searchInServer} from '@userActions/Report';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
Expand Down Expand Up @@ -239,6 +239,10 @@ function SearchRouter({onRouterClose, shouldHideInputCaret, isSearchRouterDispla
[setSelection, setTextInputValue],
);

const handleSearchAction = (value: string) => {
searchInServer(value);
};

const onListItemPress = useCallback(
(item: OptionData | SearchQueryItem) => {
if (isSearchQueryItem(item)) {
Expand Down Expand Up @@ -347,6 +351,7 @@ function SearchRouter({onRouterClose, shouldHideInputCaret, isSearchRouterDispla
/>
<SearchAutocompleteList
autocompleteQueryValue={autocompleteQueryValue || textInputValue}
handleSearch={handleSearchAction}
searchQueryItem={searchQueryItem}
getAdditionalSections={getAdditionalSections}
onListItemPress={onListItemPress}
Expand Down
15 changes: 10 additions & 5 deletions src/components/Search/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useSearchHighlightAndScroll from '@hooks/useSearchHighlightAndScroll';
import useThemeStyles from '@hooks/useThemeStyles';
import {turnOffMobileSelectionMode, turnOnMobileSelectionMode} from '@libs/actions/MobileSelectionMode';
import {search, updateSearchResultsWithTransactionThreadReportID} from '@libs/actions/Search';
import {updateSearchResultsWithTransactionThreadReportID} from '@libs/actions/Search';
import {canUseTouchScreen} from '@libs/DeviceCapabilities';
import Log from '@libs/Log';
import isSearchTopmostFullScreenRoute from '@libs/Navigation/helpers/isSearchTopmostFullScreenRoute';
Expand Down Expand Up @@ -47,14 +47,15 @@ import type SearchResults from '@src/types/onyx/SearchResults';
import {useSearchContext} from './SearchContext';
import SearchList from './SearchList';
import SearchScopeProvider from './SearchScopeProvider';
import type {SearchColumnType, SearchQueryJSON, SelectedTransactionInfo, SelectedTransactions, SortOrder} from './types';
import type {SearchColumnType, SearchParams, SearchQueryJSON, SelectedTransactionInfo, SelectedTransactions, SortOrder} from './types';

type SearchProps = {
queryJSON: SearchQueryJSON;
onSearchListScroll?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
contentContainerStyle?: StyleProp<ViewStyle>;
currentSearchResults?: SearchResults;
lastNonEmptySearchResults?: SearchResults;
handleSearch?: (value: SearchParams) => void;
};

function mapTransactionItemToSelectedEntry(item: TransactionListItemType): [string, SelectedTransactionInfo] {
Expand Down Expand Up @@ -124,7 +125,7 @@ function prepareTransactionsList(item: TransactionListItemType, selectedTransact
};
}

function Search({queryJSON, currentSearchResults, lastNonEmptySearchResults, onSearchListScroll, contentContainerStyle}: SearchProps) {
function Search({queryJSON, currentSearchResults, lastNonEmptySearchResults, onSearchListScroll, contentContainerStyle, handleSearch}: SearchProps) {
const {isOffline} = useNetwork();
const {shouldUseNarrowLayout} = useResponsiveLayout();
const styles = useThemeStyles();
Expand Down Expand Up @@ -197,8 +198,12 @@ function Search({queryJSON, currentSearchResults, lastNonEmptySearchResults, onS
return;
}

search({queryJSON, offset});
}, [isOffline, offset, queryJSON]);
if (!handleSearch) {
return;
}

handleSearch({queryJSON, offset});
}, [handleSearch, isOffline, offset, queryJSON]);

const {newSearchResultKey, handleSelectionListScroll} = useSearchHighlightAndScroll({
searchResults,
Expand Down
6 changes: 6 additions & 0 deletions src/components/Search/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,11 @@ type SearchAutocompleteQueryRange = {
value: string;
};

type SearchParams = {
queryJSON: SearchQueryJSON;
offset: number;
};

export type {
SelectedTransactionInfo,
SelectedTransactions,
Expand All @@ -170,6 +175,7 @@ export type {
SearchAutocompleteResult,
PaymentData,
SearchAutocompleteQueryRange,
SearchParams,
TableColumnSize,
SearchGroupBy,
};
Loading
Loading