diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index 2bebeefcf8050..e942e7bf1d14b 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -100,7 +100,11 @@ 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 = (Object.keys(feedNamesMapping) as Array).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 +588,25 @@ function getCompanyFeeds(cardFeeds: OnyxEntry, shouldFilterOu } function getBankName(feedType: CardFeedWithNumber | CardFeedWithDomainID): string { - if (feedType?.includes(CONST.COMPANY_CARD.FEED_BANK_NAME.CSV)) { - return CONST.COMPANY_CARDS.CARD_TYPE.CSV; + const cacheKey = feedType ?? ''; + const cached = getBankNameCache.get(cacheKey); + if (cached !== undefined) { + return cached; } - // 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)); - - if (!feedKey) { - return ''; + let result: string; + if (feedType?.includes(CONST.COMPANY_CARD.FEED_BANK_NAME.CSV)) { + result = CONST.COMPANY_CARDS.CARD_TYPE.CSV; + } else { + const feedKey = feedNamesMappingKeysByLength.find((feed) => feedType?.startsWith(feed)); + result = feedKey ? feedNamesMapping[feedKey] : ''; } - return feedNamesMapping[feedKey]; + if (getBankNameCache.size >= GET_BANK_NAME_CACHE_MAX_SIZE) { + getBankNameCache.clear(); + } + 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 8b02c72e4d9af..d7c02f238dda3 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -2474,6 +2474,7 @@ function getCardSections( cardList?: OnyxTypes.CardList, ): [TransactionCardGroupListItemType[], number] { const cardSections: Record = {}; + const cardDescriptionByCardID = new Map(); for (const key in data) { if (isGroupEntry(key)) { @@ -2486,16 +2487,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, @@ -2504,7 +2502,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 2030b4aea896d..abd6be9f6c88a 100644 --- a/tests/unit/CardUtilsTest.ts +++ b/tests/unit/CardUtilsTest.ts @@ -1567,6 +1567,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', () => {