Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
29 changes: 20 additions & 9 deletions src/libs/CardUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,11 @@ const feedNamesMapping = {
[CONST.COMPANY_CARD.FEED_BANK_NAME.PEX]: CONST.COMPANY_CARDS.NON_CONNECTABLE_BANKS.PEX,
} satisfies Partial<Record<CardFeed, BankName | NonConnectableBankName | CardTypeName>>;

const feedNamesMappingKeys = Object.keys(feedNamesMapping) as Array<keyof typeof feedNamesMapping>;
// Longest prefix first so e.g. AMEX_1205 matches before AMEX
const feedNamesMappingKeysByLength = (Object.keys(feedNamesMapping) as Array<keyof typeof feedNamesMapping>).sort((a, b) => b.length - a.length);

const GET_BANK_NAME_CACHE_MAX_SIZE = 200;
const getBankNameCache = new Map<string, string>();

/**
* @returns string with a month in MM format
Expand Down Expand Up @@ -584,18 +588,25 @@ function getCompanyFeeds(cardFeeds: OnyxEntry<CombinedCardFeeds>, 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 => {
Expand Down
31 changes: 20 additions & 11 deletions src/libs/SearchUIUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2474,6 +2474,7 @@ function getCardSections(
cardList?: OnyxTypes.CardList,
): [TransactionCardGroupListItemType[], number] {
const cardSections: Record<string, TransactionCardGroupListItemType> = {};
const cardDescriptionByCardID = new Map<number, string>();
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we can also move it to the top-level scope of the file.

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 think we can keep it inside the function to avoid issues where the locale is changed and the description doesn't change

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, I agree. I can see the cardTransactions.notActivated translation


for (const key in data) {
if (isGroupEntry(key)) {
Expand All @@ -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,
Expand All @@ -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),
};
}
Expand Down
12 changes: 12 additions & 0 deletions tests/unit/CardUtilsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
Loading