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

Add backTo param handling when opening Report from Search #49641

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
14 changes: 9 additions & 5 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,9 @@ const ROUTES = {
SEARCH_ADVANCED_FILTERS_IN: 'search/filters/in',
SEARCH_REPORT: {
route: 'search/view/:reportID/:reportActionID?',
getRoute: (reportID: string, reportActionID?: string) => {
if (reportActionID) {
return `search/view/${reportID}/${reportActionID}` as const;
}
return `search/view/${reportID}` as const;
getRoute: ({reportID, reportActionID, backTo}: {reportID: string; reportActionID?: string; backTo?: string}) => {
const baseRoute = reportActionID ? (`search/view/${reportID}/${reportActionID}` as const) : (`search/view/${reportID}` as const);
return getUrlWithBackToParam(baseRoute, backTo);
},
},
TRANSACTION_HOLD_REASON_RHP: 'search/hold',
Expand Down Expand Up @@ -1552,6 +1550,12 @@ type Route = {

type RoutesValidationError = 'Error: One or more routes defined within `ROUTES` have not correctly used `as const` in their `getRoute` function return value.';

/**
* Represents all routes in the app as a union of literal strings.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added this comment, because I found the RouteIsPlainString quite confusing. After digging in git blame I found that this comment was originally there, but got removed in here:
https://github.com/Expensify/App/pull/40017/files#diff-8c80af31c75a0f4fcb67272bd2df5b42c9611c040ecc66584fcd6bde05dbbd78L749

I think having this comment is much better than not having it, because this type is not exported and not used anywhere, so it will 100% confuse others.

*
* If TS throws on this line, it implies that one or more routes defined within `ROUTES` have not correctly used
* `as const` in their `getRoute` function return value.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
type RouteIsPlainString = AssertTypesNotEqual<string, Route, RoutesValidationError>;

Expand Down
2 changes: 1 addition & 1 deletion src/components/PromotedActionsBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ const PromotedActions = {
return;
}

ReportUtils.changeMoneyRequestHoldStatus(reportAction, ROUTES.SEARCH_REPORT.getRoute(targetedReportID));
ReportUtils.changeMoneyRequestHoldStatus(reportAction, ROUTES.SEARCH_REPORT.getRoute({reportID: targetedReportID}));
},
}),
} satisfies PromotedActionsType;
Expand Down
8 changes: 5 additions & 3 deletions src/components/Search/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ function Search({queryJSON}: SearchProps) {
}

const ListItem = SearchUtils.getListItem(type, status);
const data = SearchUtils.getSections(type, status, searchResults.data, searchResults.search);
const data = SearchUtils.getSections(type, status, searchResults.data, searchResults.search, queryJSON.inputQuery);
const sortedData = SearchUtils.getSortedSections(type, status, data, sortBy, sortOrder);
const sortedSelectedData = sortedData.map((item) => mapToItemWithSelectionInfo(item, selectedTransactions, canSelectMultiple));

Expand Down Expand Up @@ -294,13 +294,15 @@ function Search({queryJSON}: SearchProps) {
SearchActions.createTransactionThread(hash, item.transactionID, reportID, item.moneyRequestReportActionID);
}

const backTo = ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: queryJSON.inputQuery});

if (SearchUtils.isReportActionListItemType(item)) {
const reportActionID = item.reportActionID;
Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute(reportID, reportActionID));
Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID, reportActionID, backTo}));
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we use Navigation.getActiveRoute() instead of passing the queryJSON.inputQuery and building the route? Same for the other instances of backTo in this PR

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a great improvement Carlos! thanks, I tested and it works.

The only small difference is that if I generate backTo it looks like this: search?foo whereas generated from getActiveRoute() it has the extra leading slash: /search?foo.
But as far as I can see it makes no changes in navigation logic, so I'm fixing this 👍

Copy link
Contributor

Choose a reason for hiding this comment

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

Nice! Yea, I think that should be ok. Thanks for the changes!

return;
}

Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute(reportID));
Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID, backTo}));
};

const fetchMoreResults = () => {
Expand Down
4 changes: 3 additions & 1 deletion src/components/SelectionList/Search/ReportListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ function ReportListItem<TItem extends ListItem>({
};

const openReportInRHP = (transactionItem: TransactionListItemType) => {
Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute(transactionItem.transactionThreadReportID));
const backTo = ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: reportItem.currentSearchQuery});

Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID: transactionItem.transactionThreadReportID, backTo}));
};

if (!reportItem?.reportName && reportItem.transactions.length > 1) {
Expand Down
5 changes: 5 additions & 0 deletions src/components/SelectionList/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type {MutableRefObject, ReactElement, ReactNode} from 'react';
import type {GestureResponderEvent, InputModeOptions, LayoutChangeEvent, SectionListData, StyleProp, TextInput, TextStyle, ViewStyle} from 'react-native';
import type {SearchQueryString} from '@components/Search/types';
import type {BrickRoad} from '@libs/WorkspacesSettingsUtils';
// eslint-disable-next-line no-restricted-imports
import type CursorStyles from '@styles/utils/cursor/types';
Expand Down Expand Up @@ -233,7 +234,11 @@ type ReportListItemType = ListItem &
/** The personal details of the user paying the request */
to: SearchPersonalDetails;

/** List of transactions that belong to this report */
transactions: TransactionListItemType[];

/** The current search query that was used to display these report items */
currentSearchQuery: SearchQueryString;
};

type ListItemProps<TItem extends ListItem> = CommonListItemProps<TItem> & {
Expand Down
24 changes: 12 additions & 12 deletions src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,22 +114,22 @@ function getMatchingRootRouteForRHPRoute(route: NavigationPartialRoute): Navigat
if (route.params && 'backTo' in route.params && typeof route.params.backTo === 'string') {
const stateForBackTo = getStateFromPath(route.params.backTo, config);
if (stateForBackTo) {
// eslint-disable-next-line @typescript-eslint/no-shadow
const rhpNavigator = stateForBackTo.routes.find((route) => route.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR);

const centralPaneOrFullScreenNavigator = stateForBackTo.routes.find(
// eslint-disable-next-line @typescript-eslint/no-shadow
(route) => isCentralPaneName(route.name) || route.name === NAVIGATORS.FULL_SCREEN_NAVIGATOR,
);

// If there is rhpNavigator in the state generated for backTo url, we want to get root route matching to this rhp screen.
const rhpNavigator = stateForBackTo.routes.find((rt) => rt.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR);
if (rhpNavigator && rhpNavigator.state) {
return getMatchingRootRouteForRHPRoute(findFocusedRoute(stateForBackTo) as NavigationPartialRoute);
}

// If we know that backTo targets the root route (central pane or full screen) we want to use it.
if (centralPaneOrFullScreenNavigator && centralPaneOrFullScreenNavigator.state) {
return centralPaneOrFullScreenNavigator as NavigationPartialRoute<CentralPaneName | typeof NAVIGATORS.FULL_SCREEN_NAVIGATOR>;
// If we know that backTo targets the root route (full screen) we want to use it.
const fullScreenNavigator = stateForBackTo.routes.find((rt) => rt.name === NAVIGATORS.FULL_SCREEN_NAVIGATOR);
if (fullScreenNavigator && fullScreenNavigator.state) {
return fullScreenNavigator as NavigationPartialRoute<CentralPaneName | typeof NAVIGATORS.FULL_SCREEN_NAVIGATOR>;
}

// If we know that backTo targets a central pane screen we want to use it.
const centralPaneScreen = stateForBackTo.routes.find((rt) => isCentralPaneName(rt.name));
if (centralPaneScreen) {
return centralPaneScreen as NavigationPartialRoute<CentralPaneName | typeof NAVIGATORS.FULL_SCREEN_NAVIGATOR>;
}
}
}
Expand Down Expand Up @@ -191,7 +191,7 @@ function getAdaptedState(state: PartialState<NavigationState<RootStackParamList>
if (focusedRHPRoute) {
let matchingRootRoute = getMatchingRootRouteForRHPRoute(focusedRHPRoute);
const isRHPScreenOpenedFromLHN = focusedRHPRoute?.name && RHP_SCREENS_OPENED_FROM_LHN.includes(focusedRHPRoute?.name as RHPScreenOpenedFromLHN);
// This may happen if this RHP doens't have a route that should be under the overlay defined.
// This may happen if this RHP doesn't have a route that should be under the overlay defined.
if (!matchingRootRoute || isRHPScreenOpenedFromLHN) {
metainfo.isCentralPaneAndBottomTabMandatory = false;
metainfo.isFullScreenNavigatorMandatory = false;
Expand Down
20 changes: 15 additions & 5 deletions src/libs/SearchUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ function getIOUReportName(data: OnyxTypes.SearchResults['data'], reportItem: Sea
return reportItem.reportName;
}

function getReportSections(data: OnyxTypes.SearchResults['data'], metadata: OnyxTypes.SearchResults['search']): ReportListItemType[] {
function getReportSections(data: OnyxTypes.SearchResults['data'], metadata: OnyxTypes.SearchResults['search'], currentSearchQuery: SearchQueryString): ReportListItemType[] {
const shouldShowMerchant = getShouldShowMerchant(data);

const doesDataContainAPastYearTransaction = shouldShowYear(data);
Expand All @@ -269,6 +269,7 @@ function getReportSections(data: OnyxTypes.SearchResults['data'], metadata: Onyx
from: data.personalDetailsList?.[reportItem.accountID ?? -1],
to: reportItem.managerID ? data.personalDetailsList?.[reportItem.managerID] : emptyPersonalDetails,
transactions,
currentSearchQuery,
reportName: isIOUReport ? getIOUReportName(data, reportItem) : reportItem.reportName,
};
} else if (isTransactionEntry(key)) {
Expand Down Expand Up @@ -311,21 +312,30 @@ function getListItem(type: SearchDataTypes, status: SearchStatus): ListItemType<
if (type === CONST.SEARCH.DATA_TYPES.CHAT) {
return ChatListItem;
}
return status === CONST.SEARCH.STATUS.EXPENSE.ALL ? TransactionListItem : ReportListItem;
if (status === CONST.SEARCH.STATUS.EXPENSE.ALL) {
return TransactionListItem;
}
return ReportListItem;
}

function getSections(type: SearchDataTypes, status: SearchStatus, data: OnyxTypes.SearchResults['data'], metadata: OnyxTypes.SearchResults['search']) {
function getSections(type: SearchDataTypes, status: SearchStatus, data: OnyxTypes.SearchResults['data'], metadata: OnyxTypes.SearchResults['search'], currentSearchQuery: SearchQueryString) {
if (type === CONST.SEARCH.DATA_TYPES.CHAT) {
return getReportActionsSections(data);
}
return status === CONST.SEARCH.STATUS.EXPENSE.ALL ? getTransactionsSections(data, metadata) : getReportSections(data, metadata);
if (status === CONST.SEARCH.STATUS.EXPENSE.ALL) {
return getTransactionsSections(data, metadata);
}
return getReportSections(data, metadata, currentSearchQuery);
}

function getSortedSections(type: SearchDataTypes, status: SearchStatus, data: ListItemDataType<typeof type, typeof status>, sortBy?: SearchColumnType, sortOrder?: SortOrder) {
if (type === CONST.SEARCH.DATA_TYPES.CHAT) {
return getSortedReportActionData(data as ReportActionListItemType[]);
}
return status === CONST.SEARCH.STATUS.EXPENSE.ALL ? getSortedTransactionData(data as TransactionListItemType[], sortBy, sortOrder) : getSortedReportData(data as ReportListItemType[]);
if (status === CONST.SEARCH.STATUS.EXPENSE.ALL) {
return getSortedTransactionData(data as TransactionListItemType[], sortBy, sortOrder);
}
return getSortedReportData(data as ReportListItemType[]);
}

function getSortedTransactionData(data: TransactionListItemType[], sortBy?: SearchColumnType, sortOrder?: SortOrder) {
Expand Down
Loading