-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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
[Search v2] Add displaying advanced filter values and type/status #46022
Changes from all commits
346a55c
22693d4
9927d4a
422d75c
9aa2ffb
e373c47
c08eec0
173b619
b7387c0
5efe081
1496f73
2c95ef2
153102f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
@@ -1,10 +1,12 @@ | ||||
import type {ValueOf} from 'type-fest'; | ||||
import type {AllFieldKeys, ASTNode, QueryFilter, QueryFilters, SearchColumnType, SearchQueryJSON, SearchQueryString, SortOrder} from '@components/Search/types'; | ||||
import type {AdvancedFiltersKeys, ASTNode, QueryFilter, QueryFilters, SearchColumnType, SearchQueryJSON, SearchQueryString, SortOrder} from '@components/Search/types'; | ||||
import ReportListItem from '@components/SelectionList/Search/ReportListItem'; | ||||
import TransactionListItem from '@components/SelectionList/Search/TransactionListItem'; | ||||
import type {ListItem, ReportListItemType, TransactionListItemType} from '@components/SelectionList/types'; | ||||
import CONST from '@src/CONST'; | ||||
import ONYXKEYS from '@src/ONYXKEYS'; | ||||
import type {SearchAdvancedFiltersForm} from '@src/types/form'; | ||||
import INPUT_IDS from '@src/types/form/SearchAdvancedFiltersForm'; | ||||
import type * as OnyxTypes from '@src/types/onyx'; | ||||
import type {SearchAccountDetails, SearchDataTypes, SearchPersonalDetails, SearchTransaction, SearchTypeToItemMap, SectionsType} from '@src/types/onyx/SearchResults'; | ||||
import type SearchResults from '@src/types/onyx/SearchResults'; | ||||
|
@@ -316,7 +318,7 @@ function buildSearchQueryJSON(query: SearchQueryString, policyID?: string) { | |||
try { | ||||
// Add the full input and hash to the results | ||||
const result = searchParser.parse(query) as SearchQueryJSON; | ||||
result.input = query; | ||||
result.inputQuery = query; | ||||
|
||||
// Temporary solution until we move policyID filter into the AST - then remove this line and keep only query | ||||
const policyIDPart = policyID ?? ''; | ||||
|
@@ -351,7 +353,54 @@ function normalizeQuery(query: string) { | |||
return buildSearchQueryString(normalizedQueryJSON); | ||||
} | ||||
|
||||
function getFilters(query: SearchQueryString, fields: Array<Partial<AllFieldKeys>>) { | ||||
/** | ||||
* @private | ||||
* returns Date filter query string part, which needs special logic | ||||
*/ | ||||
function buildDateFilterQuery(filterValues: Partial<SearchAdvancedFiltersForm>) { | ||||
const dateBefore = filterValues[INPUT_IDS.DATE_BEFORE]; | ||||
const dateAfter = filterValues[INPUT_IDS.DATE_AFTER]; | ||||
|
||||
let dateFilter = ''; | ||||
if (dateBefore) { | ||||
dateFilter += `${CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE}<${dateBefore}`; | ||||
} | ||||
if (dateBefore && dateAfter) { | ||||
dateFilter += ' '; | ||||
} | ||||
if (dateAfter) { | ||||
dateFilter += `${CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE}>${dateAfter}`; | ||||
} | ||||
Comment on lines
+361
to
+373
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NAB: We can simplify |
||||
|
||||
return dateFilter; | ||||
} | ||||
|
||||
/** | ||||
* Given object with chosen search filters builds correct query string from them | ||||
*/ | ||||
function buildQueryStringFromFilters(filterValues: Partial<SearchAdvancedFiltersForm>) { | ||||
// TODO add handling of multiple values picked | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||
const filtersString = Object.entries(filterValues) | ||||
.map(([filterKey, filterValue]) => { | ||||
if (filterKey === INPUT_IDS.TYPE && filterValue) { | ||||
return `${CONST.SEARCH.SYNTAX_ROOT_KEYS.TYPE}:${filterValue as string}`; | ||||
} | ||||
|
||||
if (filterKey === INPUT_IDS.STATUS && filterValue) { | ||||
return `${CONST.SEARCH.SYNTAX_ROOT_KEYS.STATUS}:${filterValue as string}`; | ||||
} | ||||
|
||||
return undefined; | ||||
}) | ||||
.filter(Boolean) | ||||
.join(' '); | ||||
|
||||
const dateFilter = buildDateFilterQuery(filterValues); | ||||
|
||||
return dateFilter ? `${filtersString} ${dateFilter}` : filtersString; | ||||
} | ||||
|
||||
function getFilters(query: SearchQueryString, fields: Array<Partial<AdvancedFiltersKeys>>) { | ||||
let queryAST; | ||||
|
||||
try { | ||||
|
@@ -427,4 +476,5 @@ export { | |||
isSearchResultsEmpty, | ||||
getFilters, | ||||
normalizeQuery, | ||||
buildQueryStringFromFilters, | ||||
}; |
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -1,54 +1,111 @@ | ||||||||
import {Str} from 'expensify-common'; | ||||||||
import React, {useMemo} from 'react'; | ||||||||
import {View} from 'react-native'; | ||||||||
import {useOnyx} from 'react-native-onyx'; | ||||||||
import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; | ||||||||
import type {LocaleContextProps} from '@components/LocaleContextProvider'; | ||||||||
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; | ||||||||
import type {AdvancedFiltersKeys} from '@components/Search/types'; | ||||||||
import useLocalize from '@hooks/useLocalize'; | ||||||||
import useSingleExecution from '@hooks/useSingleExecution'; | ||||||||
import useThemeStyles from '@hooks/useThemeStyles'; | ||||||||
import useWaitForNavigation from '@hooks/useWaitForNavigation'; | ||||||||
import Navigation from '@libs/Navigation/Navigation'; | ||||||||
import * as SearchUtils from '@libs/SearchUtils'; | ||||||||
import * as SearchActions from '@userActions/Search'; | ||||||||
import CONST from '@src/CONST'; | ||||||||
import ONYXKEYS from '@src/ONYXKEYS'; | ||||||||
import ROUTES from '@src/ROUTES'; | ||||||||
import type {SearchAdvancedFiltersForm} from '@src/types/form'; | ||||||||
|
||||||||
function getFilterDisplayTitle(filters: Record<string, string>, fieldName: string) { | ||||||||
// This is temporary because the full parsing of search query is not yet done | ||||||||
// TODO once we have values from query, this value should be `filters[fieldName].value` | ||||||||
return fieldName; | ||||||||
function getFilterDisplayTitle(filters: Partial<SearchAdvancedFiltersForm>, fieldName: AdvancedFiltersKeys, translate: LocaleContextProps['translate']) { | ||||||||
if (fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE) { | ||||||||
// the value of date filter is a combination of dateBefore + dateAfter values | ||||||||
const {dateAfter, dateBefore} = filters; | ||||||||
let dateValue = ''; | ||||||||
if (dateBefore) { | ||||||||
dateValue = translate('search.filters.date.before', dateBefore); | ||||||||
} | ||||||||
if (dateBefore && dateAfter) { | ||||||||
dateValue += ', '; | ||||||||
} | ||||||||
if (dateAfter) { | ||||||||
dateValue += translate('search.filters.date.after', dateAfter); | ||||||||
} | ||||||||
|
||||||||
return dateValue; | ||||||||
} | ||||||||
|
||||||||
// Todo Once all Advanced filters are implemented this line can be cleaned up. See: https://github.com/Expensify/App/issues/45026 | ||||||||
// @ts-expect-error this property access is temporarily an error, because not every SYNTAX_FILTER_KEYS is handled by form. | ||||||||
// When all filters are updated here: src/types/form/SearchAdvancedFiltersForm.ts this line comment + type cast can be removed. | ||||||||
const filterValue = filters[fieldName] as string; | ||||||||
return filterValue ? Str.recapitalize(filterValue) : undefined; | ||||||||
} | ||||||||
|
||||||||
function AdvancedSearchFilters() { | ||||||||
const {translate} = useLocalize(); | ||||||||
const styles = useThemeStyles(); | ||||||||
const {singleExecution} = useSingleExecution(); | ||||||||
const waitForNavigate = useWaitForNavigation(); | ||||||||
|
||||||||
const [searchAdvancedFilters = {}] = useOnyx(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM); | ||||||||
|
||||||||
const advancedFilters = useMemo( | ||||||||
() => [ | ||||||||
{ | ||||||||
title: getFilterDisplayTitle({}, 'title'), | ||||||||
title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_ROOT_KEYS.TYPE, translate), | ||||||||
description: 'common.type' as const, | ||||||||
route: ROUTES.SEARCH_ADVANCED_FILTERS_TYPE, | ||||||||
}, | ||||||||
{ | ||||||||
title: getFilterDisplayTitle({}, 'date'), | ||||||||
title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_ROOT_KEYS.STATUS, translate), | ||||||||
description: 'search.filters.status' as const, | ||||||||
route: ROUTES.SEARCH_ADVANCED_FILTERS_STATUS, | ||||||||
}, | ||||||||
{ | ||||||||
title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE, translate), | ||||||||
description: 'common.date' as const, | ||||||||
route: ROUTES.SEARCH_ADVANCED_FILTERS_DATE, | ||||||||
}, | ||||||||
], | ||||||||
[], | ||||||||
[searchAdvancedFilters, translate], | ||||||||
); | ||||||||
|
||||||||
const onFormSubmit = () => { | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NAB:
Suggested change
From the Reviewer Checklist:
|
||||||||
const query = SearchUtils.buildQueryStringFromFilters(searchAdvancedFilters); | ||||||||
SearchActions.clearAdvancedFilters(); | ||||||||
Navigation.navigate( | ||||||||
ROUTES.SEARCH_CENTRAL_PANE.getRoute({ | ||||||||
query, | ||||||||
isCustomQuery: true, | ||||||||
}), | ||||||||
); | ||||||||
}; | ||||||||
|
||||||||
return ( | ||||||||
<View> | ||||||||
{advancedFilters.map((item) => { | ||||||||
const onPress = singleExecution(waitForNavigate(() => Navigation.navigate(item.route))); | ||||||||
|
||||||||
return ( | ||||||||
<MenuItemWithTopDescription | ||||||||
key={item.description} | ||||||||
title={item.title} | ||||||||
description={translate(item.description)} | ||||||||
shouldShowRightIcon | ||||||||
onPress={onPress} | ||||||||
/> | ||||||||
); | ||||||||
})} | ||||||||
<View style={[styles.flex1, styles.justifyContentBetween]}> | ||||||||
<View> | ||||||||
{advancedFilters.map((item) => { | ||||||||
const onPress = singleExecution(waitForNavigate(() => Navigation.navigate(item.route))); | ||||||||
|
||||||||
return ( | ||||||||
<MenuItemWithTopDescription | ||||||||
key={item.description} | ||||||||
title={item.title} | ||||||||
description={translate(item.description)} | ||||||||
shouldShowRightIcon | ||||||||
onPress={onPress} | ||||||||
/> | ||||||||
); | ||||||||
})} | ||||||||
</View> | ||||||||
<FormAlertWithSubmitButton | ||||||||
buttonText={translate('search.viewResults')} | ||||||||
containerStyles={[styles.m4]} | ||||||||
onSubmit={onFormSubmit} | ||||||||
enabledWhenOffline | ||||||||
/> | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that the "View results" button should not be disabled offline. based on OfflineUX_Patterns_Flowchart, No offline pattern is needed here.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we can keep it as is for now so that we don't block other issues, but I think we should disable it offline because the Search API command only works while the user is online. |
||||||||
</View> | ||||||||
); | ||||||||
} | ||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NAB we should eventually move this to
CONST.SEARCH.DATA_TYPES.EXPENSE
and get rid of transactions and reports. We'll do so as part of v2.2.