feat: [Insights] [Release 2] Top Merchants - Add group-by:merchant and suggested search#80672
Conversation
…d suggested search
|
Hey, I noticed you changed If you want to automatically generate translations for other locales, an Expensify employee will have to:
Alternatively, if you are an external contributor, you can run the translation script locally with your own OpenAI API key. To learn more, try running: npx ts-node ./scripts/generateTranslations.ts --helpTypically, you'd want to translate only what you changed by running |
|
@shubham1206agra Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button] |
src/libs/SearchUIUtils.ts
Outdated
| * | ||
| * Do not use directly, use only via `getSections()` facade. | ||
| */ | ||
| function getMerchantSections(data: OnyxTypes.SearchResults['data'], queryJSON: SearchQueryJSON | undefined): [TransactionMerchantGroupListItemType[], number] { |
There was a problem hiding this comment.
❌ CONSISTENCY-3 (docs)
The getMerchantSections function is almost identical to getCategorySections function. The only differences are the entity type (merchant vs category) and the field names. This duplicated logic should be consolidated into a generic reusable function.
Suggested fix:
Create a generic helper function that can handle both merchants and categories:
function createGroupSections<T extends TransactionGroupListItemType>(
data: OnyxTypes.SearchResults'['data'],
queryJSON: SearchQueryJSON | undefined,
groupType: ValueOf<typeof CONST.SEARCH.GROUP_BY>,
filterKey: ValueOf<typeof CONST.SEARCH.SYNTAX_FILTER_KEYS>,
getGroupValue: (group: any) => string,
formatValue: (value: string) => string,
): [T[], number] {
const sections: Record<string, T> = {};
for (const key in data) {
if (isGroupEntry(key)) {
const group = data[key];
const groupValue = getGroupValue(group);
let transactionsQueryJSON: SearchQueryJSON | undefined;
if (queryJSON && groupValue \!== undefined) {
const newFlatFilters = queryJSON.flatFilters.filter((filter) => filter.key \!== filterKey);
newFlatFilters.push({
key: filterKey,
filters: [{operator: CONST.SEARCH.SYNTAX_OPERATORS.EQUAL_TO, value: groupValue}]
});
const newQueryJSON: SearchQueryJSON = {...queryJSON, groupBy: undefined, flatFilters: newFlatFilters};
const newQuery = buildSearchQueryString(newQueryJSON);
transactionsQueryJSON = buildSearchQueryJSON(newQuery);
}
sections[key] = {
groupedBy: groupType,
transactions: [],
transactionsQueryJSON,
...group,
formattedValue: formatValue(groupValue),
} as T;
}
}
const sectionsValues = Object.values(sections);
return [sectionsValues, sectionsValues.length];
}Then use it for both merchants and categories.
Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.
Codecov Report✅ Changes either increased or maintained existing code coverage, great job!
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 430a63ed06
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Reviewer Checklist
Screenshots/VideosAndroid: HybridAppAndroid: mWeb ChromeiOS: HybridAppiOS: mWeb Safari |
Can you write this last step in a clearer way? It would just expand the group and not navigate, right? |
…ts-Release-2-Top-Merchants-Add-group-by-merchant-and-suggested-search
trjExpensify
left a comment
There was a problem hiding this comment.
Core PR for a WN project. 👍
- Display "No merchant" instead of "(none)" for empty merchants - Fix sorting to put empty merchants at bottom instead of top - Add noMerchant translations to all language files - Update comment to include TOP_MERCHANTS - Add test for empty merchant query normalization
Updated @ShridharGoel. Thanks! |
Screen.Recording.2026-01-28.at.4.51.58.PM.mov |
|
Summary of the follow-ups to be done
|
Context explained here: https://expensify.slack.com/archives/C01GTK53T8Q/p1769602422027409?thread_ts=1769580954.489439&cid=C01GTK53T8Q
PR is now mergeable.
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
We're talking about the group name here, right? |
|
Yea |
|
Created a follow-up for the splitting expense one. Would be great if someone had concrete steps to add to it: #80767
Got it. I think that's fine. "Expense" is a technicality we set for off-workspace created expenses, but we display the merchant field as blank. |
|
🚀 Deployed to staging by https://github.com/lakchote in version: 9.3.11-0 🚀
|
|
@TaduJR @trjExpensify this PR, specifically step 13, is failing with this account
|
🤔 Thats strange. Is it failing for only this specific account or does other applause accounts work? |
|
@TaduJR it happens just for that specific account, other Expensifail accounts are working fine. |
|
It's because |
That means on is from SelfDM right the "Expense"? and what about the other? |
|
🚀 Deployed to production by https://github.com/Julesssss in version: 9.3.12-1 🚀
|




Explanation of Change
This PR adds the "Top Merchants" feature following the same pattern as "Top Spenders" and "Top Categories"
Fixed Issues
$ #80393
PROPOSAL:
Tests
Prerequisite: Log in with an account that has at least one paid workspace/policy and some expenses with merchant names.
Group By Merchant
Empty Merchant Handling
group-by:merchantTop Merchants Suggested Search
Offline tests
Same as tests
QA Steps
// TODO: These must be filled out, or the issue title must include "[No QA]."
Same as tests
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectioncanBeMissingparam foruseOnyxtoggleReportand notonIconClick)src/languages/*files and using the translation methodSTYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.ScrollViewcomponent to make it scrollable when more elements are added to the page.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari
Macbook-Chrome.mp4