Skip to content

Commit 8a9ecc6

Browse files
authored
Merge pull request #60424 from software-mansion-labs/289Adam289/59367-add-workspace-autocomplete
Add workspace autocomplete list and highlighting
2 parents fe08e21 + 7f259f5 commit 8a9ecc6

9 files changed

+156
-34
lines changed

src/components/Search/SearchAutocompleteList.tsx

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import UserListItem from '@components/SelectionList/UserListItem';
1313
import useActiveWorkspace from '@hooks/useActiveWorkspace';
1414
import useFastSearchFromOptions from '@hooks/useFastSearchFromOptions';
1515
import useLocalize from '@hooks/useLocalize';
16+
import usePermissions from '@hooks/usePermissions';
1617
import usePolicy from '@hooks/usePolicy';
1718
import useResponsiveLayout from '@hooks/useResponsiveLayout';
1819
import useThemeStyles from '@hooks/useThemeStyles';
@@ -23,7 +24,7 @@ import memoize from '@libs/memoize';
2324
import {combineOrderingOfReportsAndPersonalDetails, getSearchOptions, getValidPersonalDetailOptions} from '@libs/OptionsListUtils';
2425
import type {Options, SearchOption} from '@libs/OptionsListUtils';
2526
import Performance from '@libs/Performance';
26-
import {getAllTaxRates, getCleanedTagName} from '@libs/PolicyUtils';
27+
import {getAllTaxRates, getCleanedTagName, shouldShowPolicy} from '@libs/PolicyUtils';
2728
import type {OptionData} from '@libs/ReportUtils';
2829
import {
2930
getAutocompleteCategories,
@@ -145,11 +146,12 @@ function SearchAutocompleteList(
145146

146147
const {activeWorkspaceID} = useActiveWorkspace();
147148
const policy = usePolicy(activeWorkspaceID);
148-
const [betas] = useOnyx(ONYXKEYS.BETAS);
149-
const [recentSearches] = useOnyx(ONYXKEYS.RECENT_SEARCHES);
149+
const [betas] = useOnyx(ONYXKEYS.BETAS, {canBeMissing: true});
150+
const [recentSearches] = useOnyx(ONYXKEYS.RECENT_SEARCHES, {canBeMissing: true});
150151
const personalDetails = usePersonalDetails();
151-
const [reports = {}] = useOnyx(ONYXKEYS.COLLECTION.REPORT);
152+
const [reports = {}] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: true});
152153
const taxRates = getAllTaxRates();
154+
const {canUseLeftHandBar} = usePermissions();
153155

154156
const {options, areOptionsInitialized} = useOptionsList();
155157
const searchOptions = useMemo(() => {
@@ -167,8 +169,8 @@ function SearchAutocompleteList(
167169
const expenseTypes = Object.values(CONST.SEARCH.TRANSACTION_TYPE);
168170
const booleanTypes = Object.values(CONST.SEARCH.BOOLEAN);
169171

170-
const [userCardList] = useOnyx(ONYXKEYS.CARD_LIST);
171-
const [workspaceCardFeeds] = useOnyx(ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST);
172+
const [userCardList] = useOnyx(ONYXKEYS.CARD_LIST, {canBeMissing: true});
173+
const [workspaceCardFeeds] = useOnyx(ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST, {canBeMissing: true});
172174
const allCards = useMemo(() => mergeCardListWithWorkspaceFeeds(workspaceCardFeeds ?? CONST.EMPTY_OBJECT, userCardList), [userCardList, workspaceCardFeeds]);
173175
const cardAutocompleteList = Object.values(allCards);
174176
const cardFeedNamesWithType = useMemo(() => {
@@ -210,20 +212,31 @@ function SearchAutocompleteList(
210212

211213
const taxAutocompleteList = useMemo(() => getAutocompleteTaxList(taxRates, policy), [policy, taxRates]);
212214

213-
const [allPolicyCategories] = useOnyx(ONYXKEYS.COLLECTION.POLICY_CATEGORIES);
214-
const [allRecentCategories] = useOnyx(ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES);
215+
const [allPolicyCategories] = useOnyx(ONYXKEYS.COLLECTION.POLICY_CATEGORIES, {canBeMissing: false});
216+
const [allRecentCategories] = useOnyx(ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES, {canBeMissing: true});
215217
const categoryAutocompleteList = useMemo(() => {
216218
return getAutocompleteCategories(allPolicyCategories, activeWorkspaceID);
217219
}, [activeWorkspaceID, allPolicyCategories]);
218220
const recentCategoriesAutocompleteList = useMemo(() => {
219221
return getAutocompleteRecentCategories(allRecentCategories, activeWorkspaceID);
220222
}, [activeWorkspaceID, allRecentCategories]);
221223

222-
const [currencyList] = useOnyx(ONYXKEYS.CURRENCY_LIST);
224+
const [policies = {}] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: false});
225+
const [currentUserLogin] = useOnyx(ONYXKEYS.SESSION, {selector: (session) => session?.email, canBeMissing: false});
226+
227+
const workspaceList = useMemo(
228+
() =>
229+
Object.values(policies)
230+
.filter((singlePolicy) => !!singlePolicy && shouldShowPolicy(singlePolicy, false, currentUserLogin) && !singlePolicy?.isJoinRequestPending)
231+
.map((singlePolicy) => ({id: singlePolicy?.id, name: singlePolicy?.name ?? ''})),
232+
[policies, currentUserLogin],
233+
);
234+
235+
const [currencyList] = useOnyx(ONYXKEYS.CURRENCY_LIST, {canBeMissing: false});
223236
const currencyAutocompleteList = Object.keys(currencyList ?? {}).filter((currency) => !currencyList?.[currency]?.retired);
224-
const [recentCurrencyAutocompleteList] = useOnyx(ONYXKEYS.RECENTLY_USED_CURRENCIES);
225-
const [allPoliciesTags] = useOnyx(ONYXKEYS.COLLECTION.POLICY_TAGS);
226-
const [allRecentTags] = useOnyx(ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS);
237+
const [recentCurrencyAutocompleteList] = useOnyx(ONYXKEYS.RECENTLY_USED_CURRENCIES, {canBeMissing: true});
238+
const [allPoliciesTags] = useOnyx(ONYXKEYS.COLLECTION.POLICY_TAGS, {canBeMissing: false});
239+
const [allRecentTags] = useOnyx(ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS, {canBeMissing: true});
227240
const tagAutocompleteList = useMemo(() => {
228241
return getAutocompleteTags(allPoliciesTags, activeWorkspaceID);
229242
}, [activeWorkspaceID, allPoliciesTags]);
@@ -403,6 +416,22 @@ function SearchAutocompleteList(
403416
text: value,
404417
}));
405418
}
419+
case CONST.SEARCH.SYNTAX_FILTER_KEYS.POLICY_ID: {
420+
if (!canUseLeftHandBar) {
421+
return [];
422+
}
423+
const filteredPolicies = workspaceList
424+
.filter((workspace) => workspace.name.toLowerCase().includes(autocompleteValue.toLowerCase()) && !alreadyAutocompletedKeys.includes(workspace.name.toLowerCase()))
425+
.sort()
426+
.slice(0, 10);
427+
428+
return filteredPolicies.map((workspace) => ({
429+
filterKey: CONST.SEARCH.SEARCH_USER_FRIENDLY_KEYS.POLICY_ID,
430+
text: workspace.name,
431+
autocompleteID: workspace.id,
432+
mapKey: CONST.SEARCH.SYNTAX_FILTER_KEYS.POLICY_ID,
433+
}));
434+
}
406435
default: {
407436
return [];
408437
}
@@ -419,14 +448,16 @@ function SearchAutocompleteList(
419448
getParticipantsAutocompleteList,
420449
searchOptions.recentReports,
421450
typeAutocompleteList,
451+
groupByAutocompleteList,
422452
statusAutocompleteList,
423453
expenseTypes,
424454
feedAutoCompleteList,
425455
workspaceCardFeeds,
426456
cardAutocompleteList,
427457
allCards,
428-
groupByAutocompleteList,
429458
booleanTypes,
459+
workspaceList,
460+
canUseLeftHandBar,
430461
]);
431462

432463
const sortedRecentSearches = useMemo(() => {

src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {
3636
buildUserReadableQueryString,
3737
buildUserReadableQueryStringWithPolicyID,
3838
getQueryWithUpdatedValues,
39+
getQueryWithUpdatedValuesWithoutPolicy,
3940
isDefaultExpensesQuery,
4041
isDefaultExpensesQueryWithPolicyIDCheck,
4142
sanitizeSearchValue,
@@ -68,11 +69,11 @@ function SearchPageHeaderInput({queryJSON, searchRouterListVisible, hideSearchRo
6869
const theme = useTheme();
6970
const {shouldUseNarrowLayout: displayNarrowHeader} = useResponsiveLayout();
7071
const personalDetails = usePersonalDetails();
71-
const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT);
72-
const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY);
72+
const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: true});
73+
const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: false});
7374
const taxRates = useMemo(() => getAllTaxRates(), []);
74-
const [userCardList] = useOnyx(ONYXKEYS.CARD_LIST);
75-
const [workspaceCardFeeds] = useOnyx(ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST);
75+
const [userCardList] = useOnyx(ONYXKEYS.CARD_LIST, {canBeMissing: false});
76+
const [workspaceCardFeeds] = useOnyx(ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST, {canBeMissing: true});
7677
const allCards = useMemo(() => mergeCardListWithWorkspaceFeeds(workspaceCardFeeds ?? CONST.EMPTY_OBJECT, userCardList), [userCardList, workspaceCardFeeds]);
7778
const cardFeedNamesWithType = useMemo(() => {
7879
return getCardFeedNamesWithType({workspaceCardFeeds, translate});
@@ -121,9 +122,9 @@ function SearchPageHeaderInput({queryJSON, searchRouterListVisible, hideSearchRo
121122
}, [isDefaultQuery, queryText]);
122123

123124
useEffect(() => {
124-
const substitutionsMap = buildSubstitutionsMap(originalInputQuery, personalDetails, reports, taxRates, allCards, cardFeedNamesWithType);
125+
const substitutionsMap = buildSubstitutionsMap(originalInputQuery, personalDetails, reports, taxRates, allCards, cardFeedNamesWithType, policies, canUseLeftHandBar);
125126
setAutocompleteSubstitutions(substitutionsMap);
126-
}, [cardFeedNamesWithType, allCards, originalInputQuery, personalDetails, reports, taxRates]);
127+
}, [cardFeedNamesWithType, allCards, originalInputQuery, personalDetails, reports, taxRates, policies, canUseLeftHandBar]);
127128

128129
useEffect(() => {
129130
if (searchRouterListVisible) {
@@ -167,7 +168,7 @@ function SearchPageHeaderInput({queryJSON, searchRouterListVisible, hideSearchRo
167168
const submitSearch = useCallback(
168169
(queryString: SearchQueryString) => {
169170
const queryWithSubstitutions = getQueryWithSubstitutions(queryString, autocompleteSubstitutions);
170-
const updatedQuery = getQueryWithUpdatedValues(queryWithSubstitutions, queryJSON.policyID);
171+
const updatedQuery = canUseLeftHandBar ? getQueryWithUpdatedValuesWithoutPolicy(queryWithSubstitutions) : getQueryWithUpdatedValues(queryWithSubstitutions, queryJSON.policyID);
171172

172173
if (!updatedQuery) {
173174
return;
@@ -182,7 +183,7 @@ function SearchPageHeaderInput({queryJSON, searchRouterListVisible, hideSearchRo
182183
setAutocompleteQueryValue('');
183184
}
184185
},
185-
[autocompleteSubstitutions, hideSearchRouterList, originalInputQuery, queryJSON.policyID],
186+
[autocompleteSubstitutions, hideSearchRouterList, originalInputQuery, queryJSON.policyID, canUseLeftHandBar],
186187
);
187188

188189
const onListItemPress = useCallback(

src/components/Search/SearchRouter/SearchRouter.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,14 @@ import useActiveWorkspace from '@hooks/useActiveWorkspace';
1919
import useDebouncedState from '@hooks/useDebouncedState';
2020
import useKeyboardShortcut from '@hooks/useKeyboardShortcut';
2121
import useLocalize from '@hooks/useLocalize';
22+
import usePermissions from '@hooks/usePermissions';
2223
import useResponsiveLayout from '@hooks/useResponsiveLayout';
2324
import useThemeStyles from '@hooks/useThemeStyles';
2425
import {scrollToRight} from '@libs/InputUtils';
2526
import type {SearchOption} from '@libs/OptionsListUtils';
2627
import type {OptionData} from '@libs/ReportUtils';
2728
import {getAutocompleteQueryWithComma, getQueryWithoutAutocompletedPart} from '@libs/SearchAutocompleteUtils';
28-
import {getQueryWithUpdatedValues, sanitizeSearchValue} from '@libs/SearchQueryUtils';
29+
import {getQueryWithUpdatedValues, getQueryWithUpdatedValuesWithoutPolicy, sanitizeSearchValue} from '@libs/SearchQueryUtils';
2930
import StringUtils from '@libs/StringUtils';
3031
import Navigation from '@navigation/Navigation';
3132
import type {ReportsSplitNavigatorParamList} from '@navigation/types';
@@ -79,9 +80,10 @@ type SearchRouterProps = {
7980
function SearchRouter({onRouterClose, shouldHideInputCaret, isSearchRouterDisplayed}: SearchRouterProps, ref: React.Ref<View>) {
8081
const {translate} = useLocalize();
8182
const styles = useThemeStyles();
82-
const [, recentSearchesMetadata] = useOnyx(ONYXKEYS.RECENT_SEARCHES);
83-
const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false});
83+
const [, recentSearchesMetadata] = useOnyx(ONYXKEYS.RECENT_SEARCHES, {canBeMissing: true});
84+
const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false, canBeMissing: true});
8485
const {activeWorkspaceID} = useActiveWorkspace();
86+
const {canUseLeftHandBar} = usePermissions();
8587

8688
const {shouldUseNarrowLayout} = useResponsiveLayout();
8789
const listRef = useRef<SelectionListHandle>(null);
@@ -215,7 +217,7 @@ function SearchRouter({onRouterClose, shouldHideInputCaret, isSearchRouterDispla
215217
const submitSearch = useCallback(
216218
(queryString: SearchQueryString) => {
217219
const queryWithSubstitutions = getQueryWithSubstitutions(queryString, autocompleteSubstitutions);
218-
const updatedQuery = getQueryWithUpdatedValues(queryWithSubstitutions, activeWorkspaceID);
220+
const updatedQuery = canUseLeftHandBar ? getQueryWithUpdatedValuesWithoutPolicy(queryWithSubstitutions) : getQueryWithUpdatedValues(queryWithSubstitutions, activeWorkspaceID);
219221
if (!updatedQuery) {
220222
return;
221223
}
@@ -226,7 +228,7 @@ function SearchRouter({onRouterClose, shouldHideInputCaret, isSearchRouterDispla
226228
setTextInputValue('');
227229
setAutocompleteQueryValue('');
228230
},
229-
[autocompleteSubstitutions, onRouterClose, setTextInputValue, activeWorkspaceID],
231+
[autocompleteSubstitutions, onRouterClose, setTextInputValue, activeWorkspaceID, canUseLeftHandBar],
230232
);
231233

232234
const setTextAndUpdateSelection = useCallback(

src/components/Search/SearchRouter/buildSubstitutionsMap.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import type {OnyxCollection} from 'react-native-onyx';
22
import type {SearchAutocompleteQueryRange, SearchFilterKey} from '@components/Search/types';
33
import type {CardFeedNamesWithType} from '@libs/CardFeedUtils';
44
import {parse} from '@libs/SearchParser/autocompleteParser';
5-
import {getFilterDisplayValue} from '@libs/SearchQueryUtils';
5+
import {getFilterDisplayValue, getFilterDisplayValueWithPolicyID} from '@libs/SearchQueryUtils';
66
import CONST from '@src/CONST';
7-
import type {CardList, PersonalDetailsList, Report} from '@src/types/onyx';
7+
import type {CardList, PersonalDetailsList, Policy, Report} from '@src/types/onyx';
88
import type {SubstitutionMap} from './getQueryWithSubstitutions';
99

1010
const getSubstitutionsKey = (filterKey: SearchFilterKey, value: string) => `${filterKey}:${value}`;
@@ -32,6 +32,8 @@ function buildSubstitutionsMap(
3232
allTaxRates: Record<string, string[]>,
3333
cardList: CardList,
3434
cardFeedNamesWithType: CardFeedNamesWithType,
35+
policies: OnyxCollection<Policy>,
36+
canUseLeftHandBar?: boolean,
3537
): SubstitutionMap {
3638
const parsedQuery = parse(query) as {ranges: SearchAutocompleteQueryRange[]};
3739

@@ -63,9 +65,12 @@ function buildSubstitutionsMap(
6365
filterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.IN ||
6466
filterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.CARD_ID ||
6567
filterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.TAG ||
66-
filterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.FEED
68+
filterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.FEED ||
69+
filterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.POLICY_ID
6770
) {
68-
const displayValue = getFilterDisplayValue(filterKey, filterValue, personalDetails, reports, cardList, cardFeedNamesWithType);
71+
const displayValue = canUseLeftHandBar
72+
? getFilterDisplayValueWithPolicyID(filterKey, filterValue, personalDetails, reports, cardList, cardFeedNamesWithType, policies)
73+
: getFilterDisplayValue(filterKey, filterValue, personalDetails, reports, cardList, cardFeedNamesWithType);
6974

7075
// If displayValue === filterValue, then it means there is nothing to substitute, so we don't add any key to map
7176
if (displayValue !== filterValue) {

src/libs/SearchAutocompleteUtils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ function filterOutRangesWithCorrectValue(
159159
case CONST.SEARCH.SYNTAX_FILTER_KEYS.TAX_RATE:
160160
case CONST.SEARCH.SYNTAX_FILTER_KEYS.FEED:
161161
case CONST.SEARCH.SYNTAX_FILTER_KEYS.CARD_ID:
162+
case CONST.SEARCH.SYNTAX_FILTER_KEYS.POLICY_ID:
162163
return substitutionMap[`${range.key}:${range.value}`] !== undefined;
163164

164165
case CONST.SEARCH.SYNTAX_FILTER_KEYS.TO:

src/libs/SearchParser/autocompleteParser.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,9 @@ function peg$parse(input, options) {
718718
s1 = peg$parsereimbursable();
719719
if (s1 === peg$FAILED) {
720720
s1 = peg$parsebillable();
721+
if (s1 === peg$FAILED) {
722+
s1 = peg$parsepolicyID();
723+
}
721724
}
722725
}
723726
}

src/libs/SearchParser/autocompleteParser.peggy

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ autocompleteKey "key"
7272
/ groupBy
7373
/ reimbursable
7474
/ billable
75+
/ policyID
7576
)
7677

7778
filterKey

0 commit comments

Comments
 (0)