From ff0264c4a2e38619fc151c8ffda47e10e642d0a9 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 3 Mar 2026 10:21:45 -0700 Subject: [PATCH 1/4] Cache getBankName --- src/libs/CardUtils.ts | 29 +++++++++++++++++++++++------ src/libs/SearchUIUtils.ts | 31 ++++++++++++++++++++----------- tests/unit/CardUtilsTest.ts | 12 ++++++++++++ 3 files changed, 55 insertions(+), 17 deletions(-) diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index a5bca8c099238..2f8ea78d3e48f 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -101,6 +101,11 @@ const feedNamesMapping = { } satisfies Partial>; const feedNamesMappingKeys = Object.keys(feedNamesMapping) as Array; +// Longest prefix first so e.g. AMEX_1205 matches before AMEX +const feedNamesMappingKeysByLength = [...feedNamesMappingKeys].sort((a, b) => b.length - a.length); + +const GET_BANK_NAME_CACHE_MAX_SIZE = 200; +const getBankNameCache = new Map(); /** * @returns string with a month in MM format @@ -584,18 +589,30 @@ function getCompanyFeeds(cardFeeds: OnyxEntry, shouldFilterOu } function getBankName(feedType: CardFeedWithNumber | CardFeedWithDomainID): string { + const cacheKey = feedType ?? ''; + const cached = getBankNameCache.get(cacheKey); + if (cached !== undefined) { + return cached; + } + if (feedType?.includes(CONST.COMPANY_CARD.FEED_BANK_NAME.CSV)) { - return CONST.COMPANY_CARDS.CARD_TYPE.CSV; + const result = CONST.COMPANY_CARDS.CARD_TYPE.CSV; + if (getBankNameCache.size >= GET_BANK_NAME_CACHE_MAX_SIZE) { + getBankNameCache.clear(); + } + getBankNameCache.set(cacheKey, result); + return result; } // In existing OldDot setups other variations of feeds could exist, ex: vcf2, vcf3, oauth.americanexpressfdx.com 2003 - const feedKey = feedNamesMappingKeys.find((feed) => feedType?.startsWith(feed)); + const feedKey = feedNamesMappingKeysByLength.find((feed) => feedType?.startsWith(feed)); + const result = feedKey ? feedNamesMapping[feedKey] : ''; - if (!feedKey) { - return ''; + if (getBankNameCache.size >= GET_BANK_NAME_CACHE_MAX_SIZE) { + getBankNameCache.clear(); } - - return feedNamesMapping[feedKey]; + getBankNameCache.set(cacheKey, result); + return result; } const getBankCardDetailsImage = (bank: BankName, illustrations: IllustrationsType, companyCardIllustrations: CompanyCardBankIcons): IconAsset => { diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index cf2eceb3dc993..4f30ecadd78be 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -2445,6 +2445,7 @@ function getCardSections( cardList?: OnyxTypes.CardList, ): [TransactionCardGroupListItemType[], number] { const cardSections: Record = {}; + const cardDescriptionByCardID = new Map(); for (const key in data) { if (isGroupEntry(key)) { @@ -2457,16 +2458,13 @@ function getCardSections( } const card = cardList?.[cardGroup.cardID]; - - cardSections[key] = { - groupedBy: CONST.SEARCH.GROUP_BY.CARD, - transactions: [], - transactionsQueryJSON, - ...personalDetails, - ...cardGroup, - formattedCardName: - customCardNames?.[cardGroup.cardID] ?? - getCardDescription( + let formattedCardName = customCardNames?.[cardGroup.cardID]; + if (formattedCardName === undefined) { + const cached = cardDescriptionByCardID.get(cardGroup.cardID); + if (cached !== undefined) { + formattedCardName = cached; + } else { + formattedCardName = getCardDescription( { cardID: cardGroup.cardID, bank: cardGroup.bank, @@ -2475,7 +2473,18 @@ function getCardSections( lastFourPAN: cardGroup.lastFourPAN, } as OnyxTypes.Card, translate, - ), + ); + cardDescriptionByCardID.set(cardGroup.cardID, formattedCardName); + } + } + + cardSections[key] = { + groupedBy: CONST.SEARCH.GROUP_BY.CARD, + transactions: [], + transactionsQueryJSON, + ...personalDetails, + ...cardGroup, + formattedCardName, formattedFeedName: getFeedNameForDisplay(translate, cardGroup.bank as OnyxTypes.CompanyCardFeed, cardFeeds), }; } diff --git a/tests/unit/CardUtilsTest.ts b/tests/unit/CardUtilsTest.ts index 26c0436397be0..d9b90855ce9b0 100644 --- a/tests/unit/CardUtilsTest.ts +++ b/tests/unit/CardUtilsTest.ts @@ -1566,6 +1566,18 @@ describe('CardUtils', () => { const feedName = getBankName(feed as unknown as CompanyCardFeed); expect(feedName).toBe(''); }); + + it('Should return the same value for repeated calls with the same feedType (cache)', () => { + const feed = CONST.COMPANY_CARD.FEED_BANK_NAME.CHASE; + expect(getBankName(feed)).toBe('Chase'); + expect(getBankName(feed)).toBe('Chase'); + }); + + it('Should match longest prefix first (e.g. AMEX_1205 before AMEX)', () => { + const feedWithAmex1205Prefix = `${CONST.COMPANY_CARD.FEED_BANK_NAME.AMEX_1205}something` as CompanyCardFeed; + const feedName = getBankName(feedWithAmex1205Prefix); + expect(feedName).toBe('American Express'); + }); }); describe('getCardFeedIcon', () => { From 10a8506d53de85d9a90eb124f30cb11096846981 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 3 Mar 2026 10:49:49 -0700 Subject: [PATCH 2/4] Remove dupe arrays --- src/libs/CardUtils.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index 2f8ea78d3e48f..9f666a3843d5a 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -100,9 +100,10 @@ const feedNamesMapping = { [CONST.COMPANY_CARD.FEED_BANK_NAME.PEX]: CONST.COMPANY_CARDS.NON_CONNECTABLE_BANKS.PEX, } satisfies Partial>; -const feedNamesMappingKeys = Object.keys(feedNamesMapping) as Array; // Longest prefix first so e.g. AMEX_1205 matches before AMEX -const feedNamesMappingKeysByLength = [...feedNamesMappingKeys].sort((a, b) => b.length - a.length); +const feedNamesMappingKeysByLength = (Object.keys(feedNamesMapping) as Array).sort( + (a, b) => b.length - a.length, +); const GET_BANK_NAME_CACHE_MAX_SIZE = 200; const getBankNameCache = new Map(); From 88856e4af1293a598c1f817376c33d74a2921162 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 3 Mar 2026 10:57:10 -0700 Subject: [PATCH 3/4] Fix prettier --- src/libs/CardUtils.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index 9f666a3843d5a..7ca76a53c3abe 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -101,9 +101,7 @@ const feedNamesMapping = { } satisfies Partial>; // Longest prefix first so e.g. AMEX_1205 matches before AMEX -const feedNamesMappingKeysByLength = (Object.keys(feedNamesMapping) as Array).sort( - (a, b) => b.length - a.length, -); +const feedNamesMappingKeysByLength = (Object.keys(feedNamesMapping) as Array).sort((a, b) => b.length - a.length); const GET_BANK_NAME_CACHE_MAX_SIZE = 200; const getBankNameCache = new Map(); From 0a1d13d2faf93d4e105a37bd3ca694ccc9cec467 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 3 Mar 2026 13:19:27 -0700 Subject: [PATCH 4/4] DRY code --- src/libs/CardUtils.ts | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index 7ca76a53c3abe..d3c9fff0c50e6 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -594,19 +594,14 @@ function getBankName(feedType: CardFeedWithNumber | CardFeedWithDomainID): strin return cached; } + let result: string; if (feedType?.includes(CONST.COMPANY_CARD.FEED_BANK_NAME.CSV)) { - const result = CONST.COMPANY_CARDS.CARD_TYPE.CSV; - if (getBankNameCache.size >= GET_BANK_NAME_CACHE_MAX_SIZE) { - getBankNameCache.clear(); - } - getBankNameCache.set(cacheKey, result); - return result; + result = CONST.COMPANY_CARDS.CARD_TYPE.CSV; + } else { + const feedKey = feedNamesMappingKeysByLength.find((feed) => feedType?.startsWith(feed)); + result = feedKey ? feedNamesMapping[feedKey] : ''; } - // In existing OldDot setups other variations of feeds could exist, ex: vcf2, vcf3, oauth.americanexpressfdx.com 2003 - const feedKey = feedNamesMappingKeysByLength.find((feed) => feedType?.startsWith(feed)); - const result = feedKey ? feedNamesMapping[feedKey] : ''; - if (getBankNameCache.size >= GET_BANK_NAME_CACHE_MAX_SIZE) { getBankNameCache.clear(); }