diff --git a/src/CONST/index.ts b/src/CONST/index.ts index e8d303b1b8e18..feabc4cc8fbba 100755 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -1808,6 +1808,10 @@ const CONST = { SPAN_OD_ND_TRANSITION: 'ManualOdNdTransition', SPAN_OD_ND_TRANSITION_LOGGED_OUT: 'ManualOdNdTransitionLoggedOut', SPAN_OPEN_SEARCH_ROUTER: 'ManualOpenSearchRouter', + SPAN_SEARCH_ROUTER_MODAL_CLOSE_WAIT: 'SearchRouter.ModalCloseWait', + SPAN_SEARCH_ROUTER_OPTIONS_INIT: 'SearchRouter.OptionsInit', + SPAN_SEARCH_ROUTER_COMPUTE_OPTIONS: 'SearchRouter.ComputeOptions', + SPAN_SEARCH_ROUTER_LIST_RENDER: 'SearchRouter.ListRender', SPAN_OPEN_CREATE_EXPENSE: 'ManualOpenCreateExpense', SPAN_SUBMIT_EXPENSE: 'ManualCreateExpenseSubmit', SPAN_NAVIGATE_AFTER_EXPENSE_CREATE: 'ManualCreateExpenseNavigation', diff --git a/src/components/OptionListContextProvider.tsx b/src/components/OptionListContextProvider.tsx index 216df0141e3e7..61aff30e241b2 100644 --- a/src/components/OptionListContextProvider.tsx +++ b/src/components/OptionListContextProvider.tsx @@ -1,15 +1,17 @@ -import React, {createContext, useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react'; -import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; +import type { OnyxCollection, OnyxEntry } from 'react-native-onyx'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useOnyx from '@hooks/useOnyx'; import usePrevious from '@hooks/usePrevious'; import usePrivateIsArchivedMap from '@hooks/usePrivateIsArchivedMap'; -import {createOptionFromReport, createOptionList, processReport, shallowOptionsListCompare} from '@libs/OptionsListUtils'; -import type {OptionList, SearchOption} from '@libs/OptionsListUtils'; -import {isSelfDM} from '@libs/ReportUtils'; +import { createOptionFromReport, createOptionList, processReport, shallowOptionsListCompare } from '@libs/OptionsListUtils'; +import type { OptionList, SearchOption } from '@libs/OptionsListUtils'; +import { isSelfDM } from '@libs/ReportUtils'; +import { endSpan, getSpan, startSpan } from '@libs/telemetry/activeSpans'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {PersonalDetails, Report} from '@src/types/onyx'; -import {usePersonalDetails} from './OnyxListItemProvider'; +import type { PersonalDetails, Report } from '@src/types/onyx'; +import { usePersonalDetails } from './OnyxListItemProvider'; type OptionsListContextProps = { /** List of options for reports and personal details */ @@ -43,17 +45,17 @@ const isEqualPersonalDetail = (prevPersonalDetail: PersonalDetails, personalDeta prevPersonalDetail?.login === personalDetail?.login && prevPersonalDetail?.displayName === personalDetail?.displayName; -function OptionsListContextProvider({children}: OptionsListProviderProps) { +function OptionsListContextProvider({ children }: OptionsListProviderProps) { const areOptionsInitialized = useRef(false); const [options, setOptions] = useState({ reports: [], personalDetails: [], }); - const [reportAttributes] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {canBeMissing: true}); + const [reportAttributes] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, { canBeMissing: true }); const prevReportAttributesLocale = usePrevious(reportAttributes?.locale); - const [reports, {sourceValue: changedReports}] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: true}); + const [reports, { sourceValue: changedReports }] = useOnyx(ONYXKEYS.COLLECTION.REPORT, { canBeMissing: true }); const prevReports = usePrevious(reports); - const [, {sourceValue: changedReportActions}] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS, {canBeMissing: true}); + const [, { sourceValue: changedReportActions }] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS, { canBeMissing: true }); const personalDetails = usePersonalDetails(); const prevPersonalDetails = usePrevious(personalDetails); const privateIsArchivedMap = usePrivateIsArchivedMap(); @@ -123,7 +125,7 @@ function OptionsListContextProvider({children}: OptionsListProviderProps) { for (const reportKey of changedReportKeys) { const report = changedReportsEntries[reportKey]; const reportID = reportKey.replace(ONYXKEYS.COLLECTION.REPORT, ''); - const {reportOption} = processReport(report, personalDetails, currentUserAccountID, reportAttributes?.reports); + const { reportOption } = processReport(report, personalDetails, currentUserAccountID, reportAttributes?.reports); if (reportOption) { updatedReportsMap.set(reportID, reportOption); @@ -157,7 +159,7 @@ function OptionsListContextProvider({children}: OptionsListProviderProps) { } const reportID = key.replace(ONYXKEYS.COLLECTION.REPORT_ACTIONS, ''); - const {reportOption} = processReport(updatedReportsMap.get(reportID)?.item, personalDetails, currentUserAccountID, reportAttributes?.reports); + const { reportOption } = processReport(updatedReportsMap.get(reportID)?.item, personalDetails, currentUserAccountID, reportAttributes?.reports); if (reportOption) { updatedReportsMap.set(reportID, reportOption); @@ -187,7 +189,7 @@ function OptionsListContextProvider({children}: OptionsListProviderProps) { // Handle initial personal details load. This initialization is required here specifically to prevent // UI freezing that occurs when resetting the app from the troubleshooting page. if (!prevPersonalDetails) { - const {personalDetails: newPersonalDetailsOptions, reports: newReports} = createOptionList( + const { personalDetails: newPersonalDetailsOptions, reports: newReports } = createOptionList( personalDetails, currentUserAccountID, privateIsArchivedMap, @@ -227,7 +229,7 @@ function OptionsListContextProvider({children}: OptionsListProviderProps) { } const privateIsArchived = privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report.reportID}`]; - const newReportOption = createOptionFromReport(report, personalDetails, currentUserAccountID, privateIsArchived, reportAttributes?.reports, {showPersonalDetails: true}); + const newReportOption = createOptionFromReport(report, personalDetails, currentUserAccountID, privateIsArchived, reportAttributes?.reports, { showPersonalDetails: true }); const replaceIndex = options.reports.findIndex((option) => option.reportID === report.reportID); newReportOptions.push({ newReportOption, @@ -240,7 +242,7 @@ function OptionsListContextProvider({children}: OptionsListProviderProps) { const newPersonalDetailsOptions = createOptionList(personalDetails, currentUserAccountID, privateIsArchivedMap, reports, reportAttributes?.reports).personalDetails; setOptions((prevOptions) => { - const newOptions = {...prevOptions}; + const newOptions = { ...prevOptions }; newOptions.personalDetails = newPersonalDetailsOptions; for (const newReportOption of newReportOptions) { newOptions.reports[newReportOption.replaceIndex] = newReportOption.newReportOption; @@ -253,8 +255,18 @@ function OptionsListContextProvider({children}: OptionsListProviderProps) { }, [personalDetails]); const initializeOptions = useCallback(() => { + const isSearchRouterSpanActive = !!getSpan(CONST.TELEMETRY.SPAN_OPEN_SEARCH_ROUTER); + if (isSearchRouterSpanActive) { + startSpan(CONST.TELEMETRY.SPAN_SEARCH_ROUTER_OPTIONS_INIT, { + name: CONST.TELEMETRY.SPAN_SEARCH_ROUTER_OPTIONS_INIT, + op: 'function', + }); + } loadOptions(); areOptionsInitialized.current = true; + if (isSearchRouterSpanActive) { + endSpan(CONST.TELEMETRY.SPAN_SEARCH_ROUTER_OPTIONS_INIT); + } }, [loadOptions]); const resetOptions = useCallback(() => { @@ -271,7 +283,7 @@ function OptionsListContextProvider({children}: OptionsListProviderProps) { return ( ({options, initializeOptions, areOptionsInitialized: areOptionsInitialized.current, resetOptions}), [options, initializeOptions, resetOptions])} + value={useMemo(() => ({ options, initializeOptions, areOptionsInitialized: areOptionsInitialized.current, resetOptions }), [options, initializeOptions, resetOptions])} > {children} @@ -281,9 +293,9 @@ function OptionsListContextProvider({children}: OptionsListProviderProps) { const useOptionsListContext = () => useContext(OptionsListContext); // Hook to use the OptionsListContext with an initializer to load the options -const useOptionsList = (options?: {shouldInitialize: boolean}) => { - const {shouldInitialize = true} = options ?? {}; - const {initializeOptions, options: optionsList, areOptionsInitialized, resetOptions} = useOptionsListContext(); +const useOptionsList = (options?: { shouldInitialize: boolean }) => { + const { shouldInitialize = true } = options ?? {}; + const { initializeOptions, options: optionsList, areOptionsInitialized, resetOptions } = useOptionsListContext(); const [internalOptions, setInternalOptions] = useState(optionsList); const prevOptions = useRef(null); const [areInternalOptionsInitialized, setAreInternalOptionsInitialized] = useState(false); @@ -340,4 +352,4 @@ const useOptionsList = (options?: {shouldInitialize: boolean}) => { export default OptionsListContextProvider; -export {useOptionsList, OptionsListContext}; +export { useOptionsList, OptionsListContext }; diff --git a/src/components/Search/SearchAutocompleteList.tsx b/src/components/Search/SearchAutocompleteList.tsx index 9777e67ee7780..5a0c44e611ee3 100644 --- a/src/components/Search/SearchAutocompleteList.tsx +++ b/src/components/Search/SearchAutocompleteList.tsx @@ -1,39 +1,39 @@ // We use Date.now() and Math.random() for performance measurements /* eslint-disable react-hooks/purity */ -import type {ForwardedRef, RefObject} from 'react'; -import React, {useEffect, useRef, useState} from 'react'; -import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; -import {useOptionsList} from '@components/OptionListContextProvider'; -import type {AnimatedTextInputRef} from '@components/RNTextInput'; -import type {ListItem as NewListItem, UserListItemProps} from '@components/SelectionList/ListItem/types'; +import type { ForwardedRef, RefObject } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; +import type { OnyxCollection, OnyxEntry } from 'react-native-onyx'; +import { useOptionsList } from '@components/OptionListContextProvider'; +import type { AnimatedTextInputRef } from '@components/RNTextInput'; +import type { ListItem as NewListItem, UserListItemProps } from '@components/SelectionList/ListItem/types'; import UserListItem from '@components/SelectionList/ListItem/UserListItem'; import SelectionListWithSections from '@components/SelectionList/SelectionListWithSections'; -import type {Section, SelectionListWithSectionsHandle} from '@components/SelectionList/SelectionListWithSections/types'; +import type { Section, SelectionListWithSectionsHandle } from '@components/SelectionList/SelectionListWithSections/types'; // eslint-disable-next-line no-restricted-imports -import type {SearchQueryItem, SearchQueryListItemProps} from '@components/SelectionListWithSections/Search/SearchQueryListItem'; -import SearchQueryListItem, {isSearchQueryItem} from '@components/SelectionListWithSections/Search/SearchQueryListItem'; -import {useCurrencyListState} from '@hooks/useCurrencyList'; +import type { SearchQueryItem, SearchQueryListItemProps } from '@components/SelectionListWithSections/Search/SearchQueryListItem'; +import SearchQueryListItem, { isSearchQueryItem } from '@components/SelectionListWithSections/Search/SearchQueryListItem'; +import { useCurrencyListState } from '@hooks/useCurrencyList'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useFeedKeysWithAssignedCards from '@hooks/useFeedKeysWithAssignedCards'; import useDebounce from '@hooks/useDebounce'; -import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; +import { useMemoizedLazyExpensifyIcons } from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; -import {getCardFeedsForDisplay} from '@libs/CardFeedUtils'; -import {getCardDescription, isCard, isCardHiddenFromSearch} from '@libs/CardUtils'; -import {getDecodedCategoryName} from '@libs/CategoryUtils'; +import { getCardFeedsForDisplay } from '@libs/CardFeedUtils'; +import { getCardDescription, isCard, isCardHiddenFromSearch } from '@libs/CardUtils'; +import { getDecodedCategoryName } from '@libs/CategoryUtils'; import FS from '@libs/Fullstory'; import Log from '@libs/Log'; -import type {Options, SearchOption} from '@libs/OptionsListUtils'; -import {combineOrderingOfReportsAndPersonalDetails, getSearchOptions} from '@libs/OptionsListUtils'; +import type { Options, SearchOption } from '@libs/OptionsListUtils'; +import { combineOrderingOfReportsAndPersonalDetails, getSearchOptions } from '@libs/OptionsListUtils'; import Parser from '@libs/Parser'; import Performance from '@libs/Performance'; -import {getAllTaxRates, getCleanedTagName, shouldShowPolicy} from '@libs/PolicyUtils'; -import {getReportAction} from '@libs/ReportActionsUtils'; -import type {OptionData} from '@libs/ReportUtils'; -import {getReportOrDraftReport} from '@libs/ReportUtils'; +import { getAllTaxRates, getCleanedTagName, shouldShowPolicy } from '@libs/PolicyUtils'; +import { getReportAction } from '@libs/ReportActionsUtils'; +import type { OptionData } from '@libs/ReportUtils'; +import { getReportOrDraftReport } from '@libs/ReportUtils'; import { getAutocompleteCategories, getAutocompleteRecentCategories, @@ -42,17 +42,17 @@ import { getAutocompleteTaxList, parseForAutocomplete, } from '@libs/SearchAutocompleteUtils'; -import {buildSearchQueryJSON, buildUserReadableQueryString, getQueryWithoutFilters, getUserFriendlyKey, getUserFriendlyValue, shouldHighlight} from '@libs/SearchQueryUtils'; -import {getDatePresets, getHasOptions} from '@libs/SearchUIUtils'; +import { buildSearchQueryJSON, buildUserReadableQueryString, getQueryWithoutFilters, getUserFriendlyKey, getUserFriendlyValue, shouldHighlight } from '@libs/SearchQueryUtils'; +import { getDatePresets, getHasOptions } from '@libs/SearchUIUtils'; import StringUtils from '@libs/StringUtils'; -import {endSpan} from '@libs/telemetry/activeSpans'; -import CONST, {CONTINUATION_DETECTION_SEARCH_FILTER_KEYS} from '@src/CONST'; +import { endSpan, startSpan } from '@libs/telemetry/activeSpans'; +import CONST, { CONTINUATION_DETECTION_SEARCH_FILTER_KEYS } from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {CardFeeds, CardList, PersonalDetailsList, Policy, Report} from '@src/types/onyx'; -import type {SearchDataTypes} from '@src/types/onyx/SearchResults'; -import {getEmptyObject} from '@src/types/utils/EmptyObject'; -import {getSubstitutionMapKey} from './SearchRouter/getQueryWithSubstitutions'; -import type {SearchFilterKey, UserFriendlyKey} from './types'; +import type { CardFeeds, CardList, PersonalDetailsList, Policy, Report } from '@src/types/onyx'; +import type { SearchDataTypes } from '@src/types/onyx/SearchResults'; +import { getEmptyObject } from '@src/types/utils/EmptyObject'; +import { getSubstitutionMapKey } from './SearchRouter/getQueryWithSubstitutions'; +import type { SearchFilterKey, UserFriendlyKey } from './types'; type AutocompleteItemData = { filterKey: UserFriendlyKey; @@ -167,23 +167,33 @@ function SearchAutocompleteList({ ref, }: SearchAutocompleteListProps) { const styles = useThemeStyles(); - const {translate, localeCompare} = useLocalize(); - const {shouldUseNarrowLayout} = useResponsiveLayout(); + const { translate, localeCompare } = useLocalize(); + const { shouldUseNarrowLayout } = useResponsiveLayout(); - const [betas] = useOnyx(ONYXKEYS.BETAS, {canBeMissing: true}); + const [betas] = useOnyx(ONYXKEYS.BETAS, { canBeMissing: true }); const feedKeysWithCards = useFeedKeysWithAssignedCards(); - const [draftComments] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, {canBeMissing: true}); - const [nvpDismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING, {canBeMissing: true}); - const [recentSearches] = useOnyx(ONYXKEYS.RECENT_SEARCHES, {canBeMissing: true}); - const [countryCode] = useOnyx(ONYXKEYS.COUNTRY_CODE, {canBeMissing: false}); - const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST, {canBeMissing: true}); - const [policies = getEmptyObject>>()] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: false}); + const [draftComments] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, { canBeMissing: true }); + const [nvpDismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING, { canBeMissing: true }); + const [recentSearches] = useOnyx(ONYXKEYS.RECENT_SEARCHES, { canBeMissing: true }); + const [countryCode] = useOnyx(ONYXKEYS.COUNTRY_CODE, { canBeMissing: false }); + const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST, { canBeMissing: true }); + const [policies = getEmptyObject>>()] = useOnyx(ONYXKEYS.COLLECTION.POLICY, { canBeMissing: false }); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const currentUserEmail = currentUserPersonalDetails.email ?? ''; const currentUserAccountID = currentUserPersonalDetails.accountID; const expensifyIcons = useMemoizedLazyExpensifyIcons(['History', 'MagnifyingGlass']); - const {options, areOptionsInitialized} = useOptionsList(); + const { options, areOptionsInitialized } = useOptionsList(); + + const computeSpanStarted = useRef(false); + if (!computeSpanStarted.current) { + startSpan(CONST.TELEMETRY.SPAN_SEARCH_ROUTER_COMPUTE_OPTIONS, { + name: CONST.TELEMETRY.SPAN_SEARCH_ROUTER_COMPUTE_OPTIONS, + op: 'function', + }); + computeSpanStarted.current = true; + } + const searchOptions = (() => { if (!areOptionsInitialized) { return defaultListOptions; @@ -322,8 +332,8 @@ function SearchAutocompleteList({ // Thus passing an empty object to the `allCards` parameter. const feedAutoCompleteList = Object.values(getCardFeedsForDisplay(allFeeds, {}, translate, feedKeysWithCards)); - const [allPolicyCategories] = useOnyx(ONYXKEYS.COLLECTION.POLICY_CATEGORIES, {canBeMissing: false}); - const [allRecentCategories] = useOnyx(ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES, {canBeMissing: true}); + const [allPolicyCategories] = useOnyx(ONYXKEYS.COLLECTION.POLICY_CATEGORIES, { canBeMissing: false }); + const [allRecentCategories] = useOnyx(ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES, { canBeMissing: true }); const categoryAutocompleteList = getAutocompleteCategories(allPolicyCategories); const recentCategoriesAutocompleteList = getAutocompleteRecentCategories(allRecentCategories); @@ -338,17 +348,17 @@ function SearchAutocompleteList({ continue; } - result.push({id: singlePolicy.id, name: singlePolicy.name ?? ''}); + result.push({ id: singlePolicy.id, name: singlePolicy.name ?? '' }); } return result; })(); - const {currencyList} = useCurrencyListState(); + const { currencyList } = useCurrencyListState(); const currencyAutocompleteList = Object.keys(currencyList).filter((currency) => !currencyList[currency]?.retired); - const [recentCurrencyAutocompleteList] = useOnyx(ONYXKEYS.RECENTLY_USED_CURRENCIES, {canBeMissing: true}); - const [allPoliciesTags] = useOnyx(ONYXKEYS.COLLECTION.POLICY_TAGS, {canBeMissing: false}); - const [allRecentTags] = useOnyx(ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS, {canBeMissing: true}); + const [recentCurrencyAutocompleteList] = useOnyx(ONYXKEYS.RECENTLY_USED_CURRENCIES, { canBeMissing: true }); + const [allPoliciesTags] = useOnyx(ONYXKEYS.COLLECTION.POLICY_TAGS, { canBeMissing: false }); + const [allRecentTags] = useOnyx(ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS, { canBeMissing: true }); const tagAutocompleteList = getAutocompleteTags(allPoliciesTags); const recentTagsAutocompleteList = getAutocompleteRecentTags(allRecentTags); @@ -358,7 +368,7 @@ function SearchAutocompleteList({ })(); const autocompleteSuggestions: AutocompleteItemData[] = (() => { - const {autocomplete, ranges = []} = autocompleteParsedQuery ?? {}; + const { autocomplete, ranges = [] } = autocompleteParsedQuery ?? {}; let autocompleteKey = autocomplete?.key; let autocompleteValue = autocomplete?.value ?? ''; @@ -521,19 +531,19 @@ function SearchAutocompleteList({ .filter((type) => type.toLowerCase().includes(autocompleteValue.toLowerCase()) && !alreadyAutocompletedKeys.has(type.toLowerCase())) .sort(); - return filteredTypes.map((type) => ({filterKey: CONST.SEARCH.SEARCH_USER_FRIENDLY_KEYS.TYPE, text: type})); + return filteredTypes.map((type) => ({ filterKey: CONST.SEARCH.SEARCH_USER_FRIENDLY_KEYS.TYPE, text: type })); } case CONST.SEARCH.SYNTAX_ROOT_KEYS.GROUP_BY: { const filteredGroupBy = groupByAutocompleteList.filter( (groupByValue) => groupByValue.toLowerCase().includes(autocompleteValue.toLowerCase()) && !alreadyAutocompletedKeys.has(groupByValue.toLowerCase()), ); - return filteredGroupBy.map((groupByValue) => ({filterKey: CONST.SEARCH.SEARCH_USER_FRIENDLY_KEYS.GROUP_BY, text: groupByValue})); + return filteredGroupBy.map((groupByValue) => ({ filterKey: CONST.SEARCH.SEARCH_USER_FRIENDLY_KEYS.GROUP_BY, text: groupByValue })); } case CONST.SEARCH.SYNTAX_ROOT_KEYS.VIEW: { const filteredViews = viewAutocompleteList.filter( (viewValue) => viewValue.toLowerCase().includes(autocompleteValue.toLowerCase()) && !alreadyAutocompletedKeys.has(viewValue.toLowerCase()), ); - return filteredViews.map((viewValue) => ({filterKey: CONST.SEARCH.SEARCH_USER_FRIENDLY_KEYS.VIEW, text: viewValue})); + return filteredViews.map((viewValue) => ({ filterKey: CONST.SEARCH.SEARCH_USER_FRIENDLY_KEYS.VIEW, text: viewValue })); } case CONST.SEARCH.SYNTAX_ROOT_KEYS.STATUS: { const filteredStatuses = statusAutocompleteList @@ -541,7 +551,7 @@ function SearchAutocompleteList({ .sort() .slice(0, 10); - return filteredStatuses.map((status) => ({filterKey: CONST.SEARCH.SEARCH_USER_FRIENDLY_KEYS.STATUS, text: status})); + return filteredStatuses.map((status) => ({ filterKey: CONST.SEARCH.SEARCH_USER_FRIENDLY_KEYS.STATUS, text: status })); } case CONST.SEARCH.SYNTAX_FILTER_KEYS.EXPENSE_TYPE: { const expenseTypes = Object.values(CONST.SEARCH.TRANSACTION_TYPE).map((value) => getUserFriendlyValue(value)); @@ -641,7 +651,7 @@ function SearchAutocompleteList({ return isValue.toLowerCase().includes(autocompleteValue.toLowerCase()) && !alreadyAutocompletedKeys.has(isValue.toLowerCase()); }); - return filteredIsValues.map((isValue) => ({filterKey: CONST.SEARCH.SEARCH_USER_FRIENDLY_KEYS.IS, text: isValue})); + return filteredIsValues.map((isValue) => ({ filterKey: CONST.SEARCH.SEARCH_USER_FRIENDLY_KEYS.IS, text: isValue })); } case CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE: case CONST.SEARCH.SYNTAX_FILTER_KEYS.SUBMITTED: @@ -654,7 +664,7 @@ function SearchAutocompleteList({ .filter((datePreset) => datePreset.toLowerCase().includes(autocompleteValue.toLowerCase()) && !alreadyAutocompletedKeys.has(datePreset.toLowerCase())) .sort() .slice(0, 10); - return filteredDatePresets.map((datePreset) => ({filterKey: autocompleteKey, text: datePreset})); + return filteredDatePresets.map((datePreset) => ({ filterKey: autocompleteKey, text: datePreset })); } default: { return []; @@ -664,7 +674,7 @@ function SearchAutocompleteList({ const sortedRecentSearches = Object.values(recentSearches ?? {}).sort((a, b) => localeCompare(b.timestamp, a.timestamp)); - const recentSearchesData = sortedRecentSearches?.slice(0, 5).map(({query, timestamp}) => { + const recentSearchesData = sortedRecentSearches?.slice(0, 5).map(({ query, timestamp }) => { const searchQueryJSON = buildSearchQueryJSON(query); return { text: searchQueryJSON @@ -813,7 +823,7 @@ function SearchAutocompleteList({ let sectionIndex = 0; if (searchQueryItem) { - sections.push({data: [searchQueryItem as AutocompleteListItem], sectionIndex: sectionIndex++}); + sections.push({ data: [searchQueryItem as AutocompleteListItem], sectionIndex: sectionIndex++ }); } const additionalSections = getAdditionalSections?.(searchOptions, sectionIndex); @@ -826,7 +836,7 @@ function SearchAutocompleteList({ } if (!autocompleteQueryValue && recentSearchesData && recentSearchesData.length > 0) { - sections.push({title: translate('search.recentSearches'), data: recentSearchesData as AutocompleteListItem[], sectionIndex: sectionIndex++}); + sections.push({ title: translate('search.recentSearches'), data: recentSearchesData as AutocompleteListItem[], sectionIndex: sectionIndex++ }); } const styledRecentReports = recentReportsOptions.map((option) => { const report = getReportOrDraftReport(option.reportID); @@ -842,10 +852,10 @@ function SearchAutocompleteList({ } as AutocompleteListItem; }); - sections.push({title: autocompleteQueryValue.trim() === '' ? translate('search.recentChats') : undefined, data: styledRecentReports, sectionIndex: sectionIndex++}); + sections.push({ title: autocompleteQueryValue.trim() === '' ? translate('search.recentChats') : undefined, data: styledRecentReports, sectionIndex: sectionIndex++ }); if (autocompleteSuggestions.length > 0) { - const autocompleteData: AutocompleteListItem[] = autocompleteSuggestions.map(({filterKey, text, autocompleteID, mapKey}) => { + const autocompleteData: AutocompleteListItem[] = autocompleteSuggestions.map(({ filterKey, text, autocompleteID, mapKey }) => { return { text: getAutocompleteDisplayText(filterKey, text), mapKey: mapKey ? getSubstitutionMapKey(mapKey, text) : undefined, @@ -857,7 +867,7 @@ function SearchAutocompleteList({ }; }); - sections.push({title: translate('search.suggestions'), data: autocompleteData, sectionIndex: sectionIndex++}); + sections.push({ title: translate('search.suggestions'), data: autocompleteData, sectionIndex: sectionIndex++ }); } const sectionItemText = sections?.at(1)?.data?.[0]?.text ?? ''; @@ -901,6 +911,14 @@ function SearchAutocompleteList({ } }, [autocompleteQueryValue, onHighlightFirstItem, normalizedReferenceText]); + if (isInitialRender) { + endSpan(CONST.TELEMETRY.SPAN_SEARCH_ROUTER_COMPUTE_OPTIONS); + startSpan(CONST.TELEMETRY.SPAN_SEARCH_ROUTER_LIST_RENDER, { + name: CONST.TELEMETRY.SPAN_SEARCH_ROUTER_LIST_RENDER, + op: 'ui.render', + }); + } + return ( showLoadingPlaceholder @@ -922,6 +940,7 @@ function SearchAutocompleteList({ disableKeyboardShortcuts={!shouldSubscribeToArrowKeyEvents} addBottomSafeAreaPadding onLayout={() => { + endSpan(CONST.TELEMETRY.SPAN_SEARCH_ROUTER_LIST_RENDER); setPerformanceTimersEnd(); setIsInitialRender(false); innerListRef.current?.updateExternalTextInputFocus(textInputRef?.current?.isFocused() ?? false); @@ -933,5 +952,5 @@ function SearchAutocompleteList({ SearchAutocompleteList.displayName = 'SearchAutocompleteList'; export default React.memo(SearchAutocompleteList); -export {SearchRouterItem}; -export type {GetAdditionalSectionsCallback}; +export { SearchRouterItem }; +export type { GetAdditionalSectionsCallback }; diff --git a/src/components/Search/SearchRouter/SearchButton.tsx b/src/components/Search/SearchRouter/SearchButton.tsx index 15c6e70eb862c..0dc0e6a9e9c9b 100644 --- a/src/components/Search/SearchRouter/SearchButton.tsx +++ b/src/components/Search/SearchRouter/SearchButton.tsx @@ -1,28 +1,28 @@ -import React, {useRef} from 'react'; -import type {StyleProp, View, ViewStyle} from 'react-native'; +import React, { useRef } from 'react'; +import type { StyleProp, View, ViewStyle } from 'react-native'; import Icon from '@components/Icon'; -import {PressableWithoutFeedback} from '@components/Pressable'; +import { PressableWithoutFeedback } from '@components/Pressable'; import Tooltip from '@components/Tooltip'; -import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; +import { useMemoizedLazyExpensifyIcons } from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import Performance from '@libs/Performance'; -import {startSpan} from '@libs/telemetry/activeSpans'; -import {callFunctionIfActionIsAllowed} from '@userActions/Session'; +import { startSpan } from '@libs/telemetry/activeSpans'; +import { callFunctionIfActionIsAllowed } from '@userActions/Session'; import CONST from '@src/CONST'; -import {useSearchRouterActions} from './SearchRouterContext'; +import { useSearchRouterActions } from './SearchRouterContext'; type SearchButtonProps = { style?: StyleProp; shouldUseAutoHitSlop?: boolean; }; -function SearchButton({style, shouldUseAutoHitSlop = false}: SearchButtonProps) { +function SearchButton({ style, shouldUseAutoHitSlop = false }: SearchButtonProps) { const styles = useThemeStyles(); const theme = useTheme(); - const {translate} = useLocalize(); - const {openSearchRouter} = useSearchRouterActions(); + const { translate } = useLocalize(); + const { openSearchRouter } = useSearchRouterActions(); const pressableRef = useRef(null); const expensifyIcons = useMemoizedLazyExpensifyIcons(['MagnifyingGlass']); @@ -34,6 +34,9 @@ function SearchButton({style, shouldUseAutoHitSlop = false}: SearchButtonProps) startSpan(CONST.TELEMETRY.SPAN_OPEN_SEARCH_ROUTER, { name: CONST.TELEMETRY.SPAN_OPEN_SEARCH_ROUTER, op: CONST.TELEMETRY.SPAN_OPEN_SEARCH_ROUTER, + attributes: { + trigger: 'button', + }, }); openSearchRouter(); @@ -52,10 +55,7 @@ function SearchButton({style, shouldUseAutoHitSlop = false}: SearchButtonProps) sentryLabel={CONST.SENTRY_LABEL.SEARCH.SEARCH_BUTTON} onPress={onPress} > - + ); diff --git a/src/components/Search/SearchRouter/SearchRouter.tsx b/src/components/Search/SearchRouter/SearchRouter.tsx index 75a3294cde103..3512a77f0e5e5 100644 --- a/src/components/Search/SearchRouter/SearchRouter.tsx +++ b/src/components/Search/SearchRouter/SearchRouter.tsx @@ -1,56 +1,57 @@ -import {deepEqual} from 'fast-equals'; -import React, {useCallback, useEffect, useRef, useState} from 'react'; -import type {TextInputProps} from 'react-native'; -import {InteractionManager, View} from 'react-native'; -import type {ValueOf} from 'type-fest'; +import { deepEqual } from 'fast-equals'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import type { TextInputProps } from 'react-native'; +import { InteractionManager, View } from 'react-native'; +import type { ValueOf } from 'type-fest'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import {usePersonalDetails} from '@components/OnyxListItemProvider'; -import {useOptionsList} from '@components/OptionListContextProvider'; +import { usePersonalDetails } from '@components/OnyxListItemProvider'; +import { useOptionsList } from '@components/OptionListContextProvider'; import OptionsListSkeletonView from '@components/OptionsListSkeletonView'; -import type {AnimatedTextInputRef} from '@components/RNTextInput'; -import type {GetAdditionalSectionsCallback} from '@components/Search/SearchAutocompleteList'; +import type { AnimatedTextInputRef } from '@components/RNTextInput'; +import type { GetAdditionalSectionsCallback } from '@components/Search/SearchAutocompleteList'; import SearchAutocompleteList from '@components/Search/SearchAutocompleteList'; -import {useSearchContext} from '@components/Search/SearchContext'; +import { useSearchContext } from '@components/Search/SearchContext'; import SearchInputSelectionWrapper from '@components/Search/SearchInputSelectionWrapper'; -import type {SearchQueryString} from '@components/Search/types'; -import type {SelectionListWithSectionsHandle} from '@components/SelectionList/SelectionListWithSections/types'; -import type {SearchQueryItem} from '@components/SelectionListWithSections/Search/SearchQueryListItem'; -import {isSearchQueryItem} from '@components/SelectionListWithSections/Search/SearchQueryListItem'; +import type { SearchQueryString } from '@components/Search/types'; +import type { SelectionListWithSectionsHandle } from '@components/SelectionList/SelectionListWithSections/types'; +import type { SearchQueryItem } from '@components/SelectionListWithSections/Search/SearchQueryListItem'; +import { isSearchQueryItem } from '@components/SelectionListWithSections/Search/SearchQueryListItem'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useDebouncedState from '@hooks/useDebouncedState'; import useKeyboardShortcut from '@hooks/useKeyboardShortcut'; -import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; +import { useMemoizedLazyExpensifyIcons } from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import usePrivateIsArchivedMap from '@hooks/usePrivateIsArchivedMap'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useRootNavigationState from '@hooks/useRootNavigationState'; import useThemeStyles from '@hooks/useThemeStyles'; -import {scrollToRight} from '@libs/InputUtils'; +import { scrollToRight } from '@libs/InputUtils'; import Log from '@libs/Log'; import backHistory from '@libs/Navigation/helpers/backHistory'; -import type {SearchOption} from '@libs/OptionsListUtils'; -import {createOptionFromReport} from '@libs/OptionsListUtils'; +import type { SearchOption } from '@libs/OptionsListUtils'; +import { createOptionFromReport } from '@libs/OptionsListUtils'; import Parser from '@libs/Parser'; -import {getReportAction} from '@libs/ReportActionsUtils'; -import {getReportOrDraftReport} from '@libs/ReportUtils'; -import type {OptionData} from '@libs/ReportUtils'; -import {getAutocompleteQueryWithComma, getTrimmedUserSearchQueryPreservingComma} from '@libs/SearchAutocompleteUtils'; -import {getQueryWithUpdatedValues, sanitizeSearchValue} from '@libs/SearchQueryUtils'; +import { getReportAction } from '@libs/ReportActionsUtils'; +import { getReportOrDraftReport } from '@libs/ReportUtils'; +import type { OptionData } from '@libs/ReportUtils'; +import { getAutocompleteQueryWithComma, getTrimmedUserSearchQueryPreservingComma } from '@libs/SearchAutocompleteUtils'; +import { getQueryWithUpdatedValues, sanitizeSearchValue } from '@libs/SearchQueryUtils'; import StringUtils from '@libs/StringUtils'; +import { getSpan } from '@libs/telemetry/activeSpans'; import Navigation from '@navigation/Navigation'; import variables from '@styles/variables'; -import {navigateToAndOpenReport, searchInServer} from '@userActions/Report'; -import {setSearchContext} from '@userActions/Search'; +import { navigateToAndOpenReport, searchInServer } from '@userActions/Report'; +import { setSearchContext } from '@userActions/Search'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type Report from '@src/types/onyx/Report'; import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; -import type {SubstitutionMap} from './getQueryWithSubstitutions'; -import {getQueryWithSubstitutions} from './getQueryWithSubstitutions'; -import {getUpdatedSubstitutionsMap} from './getUpdatedSubstitutionsMap'; -import {getContextualReportData, getContextualSearchAutocompleteKey, getContextualSearchQuery} from './SearchRouterUtils'; +import type { SubstitutionMap } from './getQueryWithSubstitutions'; +import { getQueryWithSubstitutions } from './getQueryWithSubstitutions'; +import { getUpdatedSubstitutionsMap } from './getUpdatedSubstitutionsMap'; +import { getContextualReportData, getContextualSearchAutocompleteKey, getContextualSearchQuery } from './SearchRouterUtils'; type SearchRouterProps = { onRouterClose: () => void; @@ -59,24 +60,33 @@ type SearchRouterProps = { ref?: React.Ref; }; -function SearchRouter({onRouterClose, shouldHideInputCaret, isSearchRouterDisplayed, ref}: SearchRouterProps) { - const {translate} = useLocalize(); +function SearchRouter({ onRouterClose, shouldHideInputCaret, isSearchRouterDisplayed, ref }: SearchRouterProps) { + const { translate } = useLocalize(); const styles = useThemeStyles(); - const {setShouldResetSearchQuery} = useSearchContext(); + const { setShouldResetSearchQuery } = useSearchContext(); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const currentUserAccountID = currentUserPersonalDetails.accountID; - const [, recentSearchesMetadata] = useOnyx(ONYXKEYS.RECENT_SEARCHES, {canBeMissing: true}); - const {areOptionsInitialized} = useOptionsList(); - const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false, canBeMissing: true}); + const [, recentSearchesMetadata] = useOnyx(ONYXKEYS.RECENT_SEARCHES, { canBeMissing: true }); + const { areOptionsInitialized } = useOptionsList(); + const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, { initWithStoredValues: false, canBeMissing: true }); const isRecentSearchesDataLoaded = !isLoadingOnyxValue(recentSearchesMetadata); const shouldShowList = isRecentSearchesDataLoaded && areOptionsInitialized; + + const coldStartAttributeSet = useRef(false); + if (!coldStartAttributeSet.current) { + const parentSpan = getSpan(CONST.TELEMETRY.SPAN_OPEN_SEARCH_ROUTER); + if (parentSpan) { + parentSpan.setAttribute('cold_start', !areOptionsInitialized); + coldStartAttributeSet.current = true; + } + } const personalDetails = usePersonalDetails(); - const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: true}); - const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: true}); - const [personalAndWorkspaceCards] = useOnyx(ONYXKEYS.DERIVED.PERSONAL_AND_WORKSPACE_CARD_LIST, {canBeMissing: true}); - const [allFeeds] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER, {canBeMissing: true}); + const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, { canBeMissing: true }); + const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, { canBeMissing: true }); + const [personalAndWorkspaceCards] = useOnyx(ONYXKEYS.DERIVED.PERSONAL_AND_WORKSPACE_CARD_LIST, { canBeMissing: true }); + const [allFeeds] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER, { canBeMissing: true }); const privateIsArchivedMap = usePrivateIsArchivedMap(); - const {shouldUseNarrowLayout} = useResponsiveLayout(); + const { shouldUseNarrowLayout } = useResponsiveLayout(); const listRef = useRef(null); const expensifyIcons = useMemoizedLazyExpensifyIcons(['MagnifyingGlass']); @@ -84,14 +94,14 @@ function SearchRouter({onRouterClose, shouldHideInputCaret, isSearchRouterDispla const [textInputValue, , setTextInputValue] = useDebouncedState('', 500); // The input text that was last used for autocomplete; needed for the SearchAutocompleteList when browsing list via arrow keys const [autocompleteQueryValue, setAutocompleteQueryValue] = useState(textInputValue); - const [selection, setSelection] = useState({start: textInputValue.length, end: textInputValue.length}); + const [selection, setSelection] = useState({ start: textInputValue.length, end: textInputValue.length }); const [autocompleteSubstitutions, setAutocompleteSubstitutions] = useState({}); const textInputRef = useRef(null); - const {contextualReportID, isSearchRouterScreen} = useRootNavigationState(getContextualReportData); + const { contextualReportID, isSearchRouterScreen } = useRootNavigationState(getContextualReportData); const getAdditionalSections: GetAdditionalSectionsCallback = useCallback( - ({recentReports}, sectionIndex) => { + ({ recentReports }, sectionIndex) => { if (!contextualReportID) { return undefined; } @@ -115,7 +125,7 @@ function SearchRouter({onRouterClose, shouldHideInputCaret, isSearchRouterDispla } const privateIsArchived = privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${contextualReportID}`]; - const option = createOptionFromReport(report, personalDetails, currentUserAccountID, privateIsArchived, undefined, {showPersonalDetails: true}); + const option = createOptionFromReport(report, personalDetails, currentUserAccountID, privateIsArchived, undefined, { showPersonalDetails: true }); reportForContextualSearch = option; } @@ -268,7 +278,7 @@ function SearchRouter({onRouterClose, shouldHideInputCaret, isSearchRouterDispla backHistory(() => { onRouterClose(); setSearchContext(true); - Navigation.navigate(ROUTES.SEARCH_ROOT.getRoute({query: updatedQuery})); + Navigation.navigate(ROUTES.SEARCH_ROOT.getRoute({ query: updatedQuery })); }); setTextInputValue(''); @@ -330,11 +340,11 @@ function SearchRouter({onRouterClose, shouldHideInputCaret, isSearchRouterDispla const searchQuery = getContextualSearchQuery(item, policies, reports); const newSearchQuery = `${searchQuery}\u00A0`; onSearchQueryChange(newSearchQuery, true); - setSelection({start: newSearchQuery.length, end: newSearchQuery.length}); + setSelection({ start: newSearchQuery.length, end: newSearchQuery.length }); const autocompleteKey = getContextualSearchAutocompleteKey(item, policies, reports); if (autocompleteKey && item.autocompleteID) { - const substitutions = {...autocompleteSubstitutions, [autocompleteKey]: item.autocompleteID}; + const substitutions = { ...autocompleteSubstitutions, [autocompleteKey]: item.autocompleteID }; setAutocompleteSubstitutions(substitutions); } setFocusAndScrollToRight(); @@ -352,10 +362,10 @@ function SearchRouter({onRouterClose, shouldHideInputCaret, isSearchRouterDispla const trimmedUserSearchQuery = getTrimmedUserSearchQueryPreservingComma(textInputValue, fieldKey); const newSearchQuery = `${trimmedUserSearchQuery}${sanitizeSearchValue(item.searchQuery)}\u00A0`; onSearchQueryChange(newSearchQuery, true); - setSelection({start: newSearchQuery.length, end: newSearchQuery.length}); + setSelection({ start: newSearchQuery.length, end: newSearchQuery.length }); if (item.mapKey && item.autocompleteID) { - const substitutions = {...autocompleteSubstitutions, [item.mapKey]: item.autocompleteID}; + const substitutions = { ...autocompleteSubstitutions, [item.mapKey]: item.autocompleteID }; setAutocompleteSubstitutions(substitutions); } setFocusAndScrollToRight(); @@ -420,21 +430,11 @@ function SearchRouter({onRouterClose, shouldHideInputCaret, isSearchRouterDispla }); const updateAndScrollToFocusedIndex = useCallback(() => listRef.current?.updateAndScrollToFocusedIndex(1, true), []); - const modalWidth = shouldUseNarrowLayout ? styles.w100 : {width: variables.searchRouterPopoverWidth}; + const modalWidth = shouldUseNarrowLayout ? styles.w100 : { width: variables.searchRouterPopoverWidth }; return ( - - {shouldUseNarrowLayout && ( - onRouterClose()} - shouldDisplayHelpButton={false} - /> - )} + + {shouldUseNarrowLayout && onRouterClose()} shouldDisplayHelpButton={false} />} )} - {!shouldShowList && ( - - )} + {!shouldShowList && } ); } diff --git a/src/components/Search/SearchRouter/SearchRouterContext.tsx b/src/components/Search/SearchRouter/SearchRouterContext.tsx index 56a60c8fb59af..5270209041006 100644 --- a/src/components/Search/SearchRouter/SearchRouterContext.tsx +++ b/src/components/Search/SearchRouter/SearchRouterContext.tsx @@ -1,14 +1,14 @@ -import React, {useContext, useEffect, useRef, useState} from 'react'; -import type {AnimatedTextInputRef} from '@components/RNTextInput'; +import React, { useContext, useEffect, useRef, useState } from 'react'; +import type { AnimatedTextInputRef } from '@components/RNTextInput'; import isSearchTopmostFullScreenRoute from '@libs/Navigation/helpers/isSearchTopmostFullScreenRoute'; -import {navigationRef} from '@libs/Navigation/Navigation'; -import {startSpan} from '@libs/telemetry/activeSpans'; -import {close} from '@userActions/Modal'; +import { navigationRef } from '@libs/Navigation/Navigation'; +import { endSpan, startSpan } from '@libs/telemetry/activeSpans'; +import { close } from '@userActions/Modal'; import CONST from '@src/CONST'; import NAVIGATORS from '@src/NAVIGATORS'; import SCREENS from '@src/SCREENS'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; -import {closeSearch, openSearch} from './toggleSearch'; +import { closeSearch, openSearch } from './toggleSearch'; type SearchRouterStateContextType = { isSearchRouterDisplayed: boolean; @@ -34,14 +34,14 @@ const defaultSearchRouterActionsContext: SearchRouterActionsContextType = { unregisterSearchPageInput: () => {}, }; -const SearchRouterStateContext = React.createContext({isSearchRouterDisplayed: false}); +const SearchRouterStateContext = React.createContext({ isSearchRouterDisplayed: false }); const SearchRouterActionsContext = React.createContext(defaultSearchRouterActionsContext); const isBrowserWithHistory = typeof window !== 'undefined' && typeof window.history !== 'undefined'; const canListenPopState = typeof window !== 'undefined' && typeof window.addEventListener === 'function'; -function SearchRouterContextProvider({children}: ChildrenProps) { +function SearchRouterContextProvider({ children }: ChildrenProps) { const [isSearchRouterDisplayed, setIsSearchRouterDisplayed] = useState(false); const searchRouterDisplayedRef = useRef(false); const searchPageInputRef = useRef(undefined); @@ -75,10 +75,15 @@ function SearchRouterContextProvider({children}: ChildrenProps) { const openSearchRouter = () => { if (isBrowserWithHistory) { - window.history.pushState({isSearchModalOpen: true} satisfies HistoryState, ''); + window.history.pushState({ isSearchModalOpen: true } satisfies HistoryState, ''); } + startSpan(CONST.TELEMETRY.SPAN_SEARCH_ROUTER_MODAL_CLOSE_WAIT, { + name: CONST.TELEMETRY.SPAN_SEARCH_ROUTER_MODAL_CLOSE_WAIT, + op: 'ui.modal.wait', + }); close( () => { + endSpan(CONST.TELEMETRY.SPAN_SEARCH_ROUTER_MODAL_CLOSE_WAIT); openSearch(setIsSearchRouterDisplayed); searchRouterDisplayedRef.current = true; }, @@ -93,7 +98,7 @@ function SearchRouterContextProvider({children}: ChildrenProps) { if (isBrowserWithHistory) { const state = window.history.state as HistoryState | null; if (state?.isSearchModalOpen) { - window.history.replaceState({isSearchModalOpen: false} satisfies HistoryState, ''); + window.history.replaceState({ isSearchModalOpen: false } satisfies HistoryState, ''); } } }; @@ -151,7 +156,7 @@ function SearchRouterContextProvider({children}: ChildrenProps) { // Because of the React Compiler we don't need to memoize it manually // eslint-disable-next-line react/jsx-no-constructed-context-values - const stateContextValue = {isSearchRouterDisplayed}; + const stateContextValue = { isSearchRouterDisplayed }; return ( @@ -168,4 +173,4 @@ function useSearchRouterActions() { return useContext(SearchRouterActionsContext); } -export {SearchRouterContextProvider, useSearchRouterState, useSearchRouterActions}; +export { SearchRouterContextProvider, useSearchRouterState, useSearchRouterActions };