Skip to content
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

Use new Search query syntax when calling api #46481

Merged
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
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
20 changes: 6 additions & 14 deletions src/components/Search/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,19 @@ import type {SearchDataTypes} from '@src/types/onyx/SearchResults';
import {useSearchContext} from './SearchContext';
import SearchListWithHeader from './SearchListWithHeader';
import SearchPageHeader from './SearchPageHeader';
import type {SearchColumnType, SearchQueryJSON, SearchStatus, SortOrder} from './types';
import type {SearchColumnType, SearchQueryJSON, SortOrder} from './types';

type SearchProps = {
queryJSON: SearchQueryJSON;
policyIDs?: string;
isCustomQuery: boolean;
};

const sortableSearchTabs: SearchStatus[] = [CONST.SEARCH.STATUS.ALL];
const transactionItemMobileHeight = 100;
const reportItemTransactionHeight = 52;
const listItemPadding = 12; // this is equivalent to 'mb3' on every transaction/report list item
const searchHeaderHeight = 54;

function Search({queryJSON, policyIDs, isCustomQuery}: SearchProps) {
function Search({queryJSON, isCustomQuery}: SearchProps) {
const {isOffline} = useNetwork();
const styles = useThemeStyles();
const {isLargeScreenWidth, isSmallScreenWidth} = useWindowDimensions();
Expand All @@ -51,7 +49,7 @@ function Search({queryJSON, policyIDs, isCustomQuery}: SearchProps) {
const [selectionMode] = useOnyx(ONYXKEYS.MOBILE_SELECTION_MODE);
const [offset, setOffset] = React.useState(0);

const {status, sortBy, sortOrder, hash} = queryJSON;
const {sortBy, sortOrder, hash} = queryJSON;

const [currentSearchResults] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`);

Expand Down Expand Up @@ -98,9 +96,9 @@ function Search({queryJSON, policyIDs, isCustomQuery}: SearchProps) {

setCurrentSearchHash(hash);

SearchActions.search({hash, query: status, policyIDs, offset, sortBy, sortOrder});
SearchActions.search({queryJSON, offset});
// eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps
}, [hash, isOffline, offset]);
}, [isOffline, offset, queryJSON]);

const isDataLoaded = searchResults?.data !== undefined;
const shouldShowLoadingState = !isOffline && !isDataLoaded;
Expand Down Expand Up @@ -170,15 +168,10 @@ function Search({queryJSON, policyIDs, isCustomQuery}: SearchProps) {
const sortedData = SearchUtils.getSortedSections(type, data, sortBy, sortOrder);

const onSortPress = (column: SearchColumnType, order: SortOrder) => {
const currentSearchParams = SearchUtils.getCurrentSearchParams();
const currentQueryJSON = SearchUtils.buildSearchQueryJSON(currentSearchParams.q, policyIDs);
Copy link
Contributor Author

@Kicu Kicu Jul 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: I removed this because it was super redundant. We already have the parsed queryJSON object in this component's props. Instead of using it we were getting the params and parsing them once again which didn't add anything to this function.


const newQuery = SearchUtils.buildSearchQueryString({...currentQueryJSON, sortBy: column, sortOrder: order});
const newQuery = SearchUtils.buildSearchQueryString({...queryJSON, sortBy: column, sortOrder: order});
navigation.setParams({q: newQuery});
};

const isSortingAllowed = sortableSearchTabs.includes(status);

const shouldShowYear = SearchUtils.shouldShowYear(searchResults?.data);

const canSelectMultiple = isSmallScreenWidth ? selectionMode?.isEnabled : true;
Expand All @@ -197,7 +190,6 @@ function Search({queryJSON, policyIDs, isCustomQuery}: SearchProps) {
metadata={searchResults?.search}
onSortPress={onSortPress}
sortOrder={sortOrder}
isSortingAllowed={isSortingAllowed}
sortBy={sortBy}
shouldShowYear={shouldShowYear}
/>
Expand Down
2 changes: 1 addition & 1 deletion src/components/Search/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ type QueryFilter = {
type AdvancedFiltersKeys = ValueOf<typeof CONST.SEARCH.SYNTAX_FILTER_KEYS> | typeof CONST.SEARCH.SYNTAX_ROOT_KEYS.TYPE | typeof CONST.SEARCH.SYNTAX_ROOT_KEYS.STATUS;

type QueryFilters = {
[K in AdvancedFiltersKeys]?: QueryFilter | QueryFilter[];
[K in AdvancedFiltersKeys]?: QueryFilter[];
};

type SearchQueryString = string;
Expand Down
6 changes: 2 additions & 4 deletions src/components/SelectionList/SearchTableHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,11 @@ type SearchTableHeaderProps = {
metadata: OnyxTypes.SearchResults['search'];
sortBy?: SearchColumnType;
sortOrder?: SortOrder;
isSortingAllowed: boolean;
onSortPress: (column: SearchColumnType, order: SortOrder) => void;
shouldShowYear: boolean;
};

function SearchTableHeader({data, metadata, sortBy, sortOrder, isSortingAllowed, onSortPress, shouldShowYear}: SearchTableHeaderProps) {
function SearchTableHeader({data, metadata, sortBy, sortOrder, onSortPress, shouldShowYear}: SearchTableHeaderProps) {
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const {isSmallScreenWidth, isMediumScreenWidth} = useWindowDimensions();
Expand All @@ -116,7 +115,6 @@ function SearchTableHeader({data, metadata, sortBy, sortOrder, isSortingAllowed,

const isActive = sortBy === columnName;
const textStyle = columnName === CONST.SEARCH.TABLE_COLUMNS.RECEIPT ? StyleUtils.getTextOverflowStyle('clip') : null;
const isSortable = isSortingAllowed && isColumnSortable;

return (
<SortableHeaderText
Expand All @@ -126,7 +124,7 @@ function SearchTableHeader({data, metadata, sortBy, sortOrder, isSortingAllowed,
sortOrder={sortOrder ?? CONST.SEARCH.SORT_ORDER.ASC}
isActive={isActive}
containerStyle={[StyleUtils.getSearchTableColumnStyles(columnName, shouldShowYear)]}
isSortable={isSortable}
isSortable={isColumnSortable}
onPress={(order: SortOrder) => onSortPress(columnName, order)}
/>
);
Expand Down
8 changes: 2 additions & 6 deletions src/libs/API/parameters/Search.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import type {SortOrder} from '@components/Search/types';
import type {SearchQueryString} from '@components/Search/types';

type SearchParams = {
hash: number;
query: string;
policyIDs?: string;
sortBy?: string;
sortOrder?: SortOrder;
offset: number;
jsonQuery: SearchQueryString;
};

export default SearchParams;
72 changes: 34 additions & 38 deletions src/libs/SearchUtils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type {ValueOf} from 'type-fest';
import type {AdvancedFiltersKeys, ASTNode, QueryFilter, QueryFilters, SearchColumnType, SearchQueryJSON, SearchQueryString, SortOrder} from '@components/Search/types';
import type {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';
Expand Down Expand Up @@ -341,16 +341,31 @@ function buildSearchQueryJSON(query: SearchQueryString, policyID?: string) {
}
}

function buildSearchQueryString(partialQueryJSON?: Partial<SearchQueryJSON>) {
function buildSearchQueryString(queryJSON?: SearchQueryJSON) {
const queryParts: string[] = [];
const defaultQueryJSON = buildSearchQueryJSON('');

// For this const values are lowercase version of the keys. We are using lowercase for ast keys.
for (const [, value] of Object.entries(CONST.SEARCH.SYNTAX_ROOT_KEYS)) {
if (partialQueryJSON?.[value]) {
queryParts.push(`${value}:${partialQueryJSON[value]}`);
for (const [, key] of Object.entries(CONST.SEARCH.SYNTAX_ROOT_KEYS)) {
if (queryJSON?.[key]) {
queryParts.push(`${key}:${queryJSON[key]}`);
} else if (defaultQueryJSON) {
queryParts.push(`${value}:${defaultQueryJSON[value]}`);
queryParts.push(`${key}:${defaultQueryJSON[key]}`);
}
}

if (!queryJSON) {
return queryParts.join(' ');
}

const filters = getFilters(queryJSON);

for (const [, filterKey] of Object.entries(CONST.SEARCH.SYNTAX_FILTER_KEYS)) {
const queryFilter = filters[filterKey];

if (queryFilter) {
const filterValueString = buildFilterString(filterKey, queryFilter);
queryParts.push(filterValueString);
}
}

Expand Down Expand Up @@ -412,29 +427,9 @@ function buildQueryStringFromFilters(filterValues: Partial<SearchAdvancedFilters
return dateFilter ? `${filtersString} ${dateFilter}` : filtersString;
}

function getFilters(query: SearchQueryString, fields: Array<Partial<AdvancedFiltersKeys>>) {
let queryAST;

try {
queryAST = searchParser.parse(query) as SearchQueryJSON;
} catch (e) {
console.error(e);
return;
}

function getFilters(queryJSON: SearchQueryJSON) {
const filters = {} as QueryFilters;

fields.forEach((field) => {
const rootFieldKey = field as ValueOf<typeof CONST.SEARCH.SYNTAX_ROOT_KEYS>;
if (queryAST[rootFieldKey] === undefined) {
return;
}

filters[field] = {
operator: 'eq',
value: queryAST[rootFieldKey],
};
});
const filterKeys = Object.values(CONST.SEARCH.SYNTAX_FILTER_KEYS);

function traverse(node: ASTNode) {
if (!node.operator) {
Expand All @@ -450,34 +445,35 @@ function getFilters(query: SearchQueryString, fields: Array<Partial<AdvancedFilt
}

const nodeKey = node.left as ValueOf<typeof CONST.SEARCH.SYNTAX_FILTER_KEYS>;
if (!fields.includes(nodeKey)) {
if (!filterKeys.includes(nodeKey)) {
return;
}

if (!filters[nodeKey]) {
filters[nodeKey] = [];
}

const filterArray = filters[nodeKey] as QueryFilter[];
// the "?? []" is added only for typescript because otherwise TS throws an error, in newer TS versions this should be fixed
const filterArray = filters[nodeKey] ?? [];
filterArray.push({
operator: node.operator,
value: node.right as string | number,
});
}

if (queryAST.filters) {
traverse(queryAST.filters);
if (queryJSON.filters) {
traverse(queryJSON.filters);
}

return filters;
}

function buildFilterValueString(filterName: string, queryFilters: QueryFilter[]) {
function buildFilterString(filterName: string, queryFilters: QueryFilter[]) {
let filterValueString = '';
queryFilters.forEach((queryFilter, index) => {
// If the previous queryFilter has the same operator (this rule applies only to eq and neq operators) then append the current value
if ((queryFilter.operator === 'eq' && queryFilters[index - 1]?.operator === 'eq') || (queryFilter.operator === 'neq' && queryFilters[index - 1]?.operator === 'neq')) {
filterValueString += `,${queryFilter.value}`;
filterValueString += `,${filterName}:${queryFilter.value}`;
} else {
filterValueString += ` ${filterName}${operatorToSignMap[queryFilter.operator]}${queryFilter.value}`;
}
Expand All @@ -487,14 +483,14 @@ function buildFilterValueString(filterName: string, queryFilters: QueryFilter[])
}

function getSearchHeaderTitle(queryJSON: SearchQueryJSON) {
const {inputQuery, type, status} = queryJSON;
const filters = getFilters(inputQuery, Object.values(CONST.SEARCH.SYNTAX_FILTER_KEYS)) ?? {};
const {type, status} = queryJSON;
const filters = getFilters(queryJSON) ?? {};

let title = `type:${type} status:${status}`;

Object.keys(filters).forEach((key) => {
const queryFilter = filters[key as ValueOf<typeof CONST.SEARCH.SYNTAX_FILTER_KEYS>] as QueryFilter[];
title += buildFilterValueString(key, queryFilter);
const queryFilter = filters[key as ValueOf<typeof CONST.SEARCH.SYNTAX_FILTER_KEYS>] ?? [];
title += buildFilterString(key, queryFilter);
});

return title;
Expand Down
31 changes: 10 additions & 21 deletions src/libs/actions/Search.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import Onyx from 'react-native-onyx';
import type {OnyxUpdate} from 'react-native-onyx';
import type {FormOnyxValues} from '@components/Form/types';
import type {SearchQueryString} from '@components/Search/types';
import type {SearchQueryJSON} from '@components/Search/types';
import * as API from '@libs/API';
import type {ExportSearchItemsToCSVParams, SearchParams} from '@libs/API/parameters';
import type {ExportSearchItemsToCSVParams} from '@libs/API/parameters';
import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
import * as ApiUtils from '@libs/ApiUtils';
import fileDownload from '@libs/fileDownload';
import enhanceParameters from '@libs/Network/enhanceParameters';
import {buildSearchQueryJSON} from '@libs/SearchUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {SearchTransaction} from '@src/types/onyx/SearchResults';
Expand Down Expand Up @@ -50,26 +49,16 @@ function getOnyxLoadingData(hash: number): {optimisticData: OnyxUpdate[]; finall
return {optimisticData, finallyData};
}

function search({hash, query, policyIDs, offset, sortBy, sortOrder}: SearchParams) {
const {optimisticData, finallyData} = getOnyxLoadingData(hash);

API.read(READ_COMMANDS.SEARCH, {hash, query, offset, policyIDs, sortBy, sortOrder}, {optimisticData, finallyData});
}

// TODO_SEARCH: use this function after backend changes.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function searchV2(queryString: SearchQueryString) {
const queryJSON = buildSearchQueryJSON(queryString);

if (!queryJSON) {
return;
}

function search({queryJSON, offset}: {queryJSON: SearchQueryJSON; offset?: number}) {
const {optimisticData, finallyData} = getOnyxLoadingData(queryJSON.hash);

// TODO_SEARCH: uncomment this line after backend changes
// @ts-expect-error waiting for backend changes
API.read(READ_COMMANDS.SEARCH, {hash: queryJSON.hash, jsonQuery: JSON.stringify(queryJSON)}, {optimisticData, finallyData});
const queryWithOffset = {
...queryJSON,
offset,
};
const jsonQuery = JSON.stringify(queryWithOffset);

API.read(READ_COMMANDS.SEARCH, {hash: queryJSON.hash, jsonQuery}, {optimisticData, finallyData});
}

/**
Expand Down
1 change: 0 additions & 1 deletion src/pages/Search/SearchPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ function SearchPage({route}: SearchPageProps) {
<Search
isCustomQuery={isCustomQuery}
queryJSON={queryJSON}
policyIDs={policyIDs}
/>
)}
</FullPageNotFoundView>
Expand Down
1 change: 0 additions & 1 deletion src/pages/Search/SearchPageBottomTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ function SearchPageBottomTab() {
{shouldUseNarrowLayout && queryJSON && (
<Search
queryJSON={queryJSON}
policyIDs={policyIDs}
isCustomQuery={isCustomQuery}
/>
)}
Expand Down
Loading