Conversation
Codecov Report✅ Changes either increased or maintained existing code coverage, great job!
|
src/libs/ReportNameUtils.ts
Outdated
| reports?: OnyxCollection<Report>, | ||
| policies?: OnyxCollection<Policy>, | ||
| transactions?: OnyxCollection<Transaction>, | ||
| reportNameValuePairsList?: OnyxCollection<ReportNameValuePairs>, |
There was a problem hiding this comment.
NAB: rename to reportNameValuePairs since we shouldn't include data types in names, e.g, "list".
src/libs/ReportUtils.ts
Outdated
|
|
||
| /** | ||
| * Get the title for an IOU or expense chat which will be showing the payer and the amount | ||
| * @deprecated |
There was a problem hiding this comment.
When deprecating a function please clearly explain why. Otherwise it may be confusing for others in the future.
|
|
||
| /** | ||
| * @deprecated | ||
| * @param props |
There was a problem hiding this comment.
Pls only add doc comments for params if they need additional explanation
- // src/libs/ReportUtils.ts
+ // src/libs/ReportNameUtils.ts
function generateArchivedReportName(reportName: string): string {
// eslint-disable-next-line @typescript-eslint/no-deprecated
return `${reportName} (${translateLocal('common.archived')}) `;
}
////////////////////////////////////////////////////////////////////////////////
// buildReportNameFromParticipantNames
////////////////////////////////////////////////////////////////////////////////
- // src/libs/ReportUtils.ts
- const buildReportNameFromParticipantNames = ({report, personalDetails: personalDetailsData}: {report: OnyxEntry<Report>; personalDetails?: Partial<PersonalDetailsList>}) =>
+ // src/libs/ReportNameUtils.ts
+ const buildReportNameFromParticipantNames = ({report, personalDetailsList: personalDetailsData}: {report: OnyxEntry<Report>; personalDetailsList?: Partial<PersonalDetailsList>}) =>
Object.keys(report?.participants ?? {})
.map(Number)
.filter((id) => id !== currentUserAccountID)
.slice(0, 5)
.map((accountID) => ({
accountID,
name: getDisplayNameForParticipant({
accountID,
shouldUseShortForm: true,
personalDetailsData,
}),
}))
.filter((participant) => participant.name)
.reduce((formattedNames, {name, accountID}, _, array) => {
// If there is only one participant (if it is 0 or less the function will return empty string), return their full name
if (array.length < 2) {
return getDisplayNameForParticipant({
accountID,
personalDetailsData,
});
}
return formattedNames ? `${formattedNames}, ${name}` : name;
}, '');
////////////////////////////////////////////////////////////////////////////////
// getGroupChatName
////////////////////////////////////////////////////////////////////////////////
- // src/libs/ReportUtils.ts
+ // src/libs/ReportNameUtils.ts
const customCollator = new Intl.Collator('en', {usage: 'sort', sensitivity: 'variant', numeric: true, caseFirst: 'upper'});
/**
* Returns the report name if the report is a group chat
*/
function getGroupChatName(
participants?: SelectedParticipant[],
shouldApplyLimit = false,
report?: OnyxEntry<Report>,
reportMetadataParam?: OnyxEntry<ReportMetadata>,
): string | undefined {
// If we have a report always try to get the name from the report.
if (report?.reportName) {
return report.reportName;
}
const reportMetadata = reportMetadataParam ?? getReportMetadata(report?.reportID);
const pendingMemberAccountIDs = new Set(
reportMetadata?.pendingChatMembers?.filter((member) => member.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE).map((member) => member.accountID),
);
let participantAccountIDs =
participants?.map((participant) => participant.accountID) ??
Object.keys(report?.participants ?? {})
.map(Number)
.filter((accountID) => !pendingMemberAccountIDs.has(accountID.toString()));
const shouldAddEllipsis = participantAccountIDs.length > CONST.DISPLAY_PARTICIPANTS_LIMIT && shouldApplyLimit;
if (shouldApplyLimit) {
participantAccountIDs = participantAccountIDs.slice(0, CONST.DISPLAY_PARTICIPANTS_LIMIT);
}
const isMultipleParticipantReport = participantAccountIDs.length > 1;
if (isMultipleParticipantReport) {
return participantAccountIDs
.map(
(participantAccountID, index) =>
getDisplayNameForParticipant({accountID: participantAccountID, shouldUseShortForm: isMultipleParticipantReport}) ||
formatPhoneNumber(participants?.[index]?.login ?? ''),
)
.sort((first, second) => customCollator.compare(first ?? '', second ?? ''))
.filter(Boolean)
.join(', ')
.slice(0, CONST.REPORT_NAME_LIMIT)
.concat(shouldAddEllipsis ? '...' : '');
}
// eslint-disable-next-line @typescript-eslint/no-deprecated
return translateLocal('groupChat.defaultReportName', {displayName: getDisplayNameForParticipant({accountID: participantAccountIDs.at(0)})});
}
////////////////////////////////////////////////////////////////////////////////
// getPolicyExpenseChatName
////////////////////////////////////////////////////////////////////////////////
- // src/libs/ReportUtils.ts
+ // src/libs/ReportNameUtils.ts
function getPolicyExpenseChatName({report, personalDetailsList}: {report: OnyxEntry<Report>; personalDetailsList?: Partial<PersonalDetailsList>}): string | undefined {
const ownerAccountID = report?.ownerAccountID;
const personalDetails = ownerAccountID ? personalDetailsList?.[ownerAccountID] : undefined;
const login = personalDetails ? personalDetails.login : null;
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const reportOwnerDisplayName = getDisplayNameForParticipant({accountID: ownerAccountID, shouldRemoveDomain: true}) || login;
if (reportOwnerDisplayName) {
// eslint-disable-next-line @typescript-eslint/no-deprecated
return translateLocal('workspace.common.policyExpenseChatName', {displayName: reportOwnerDisplayName});
}
return report?.reportName;
}
////////////////////////////////////////////////////////////////////////////////
// getInvoicesChatName
////////////////////////////////////////////////////////////////////////////////
- // src/libs/ReportUtils.ts
- function getInvoicesChatName({
- report,
- receiverPolicy,
- personalDetails,
- policies,
-}: {
- report: OnyxEntry<Report>;
- receiverPolicy: OnyxEntry<Policy>;
- personalDetails?: Partial<PersonalDetailsList>;
- policies?: Policy[];
-}): string {
- const invoiceReceiver = report?.invoiceReceiver;
- const isIndividual = invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL;
- const invoiceReceiverAccountID = isIndividual ? invoiceReceiver.accountID : CONST.DEFAULT_NUMBER_ID;
- const invoiceReceiverPolicyID = isIndividual ? undefined : invoiceReceiver?.policyID;
- // This will be fixed as part of https://github.com/Expensify/Expensify/issues/507850
- // eslint-disable-next-line @typescript-eslint/no-deprecated
- const invoiceReceiverPolicy = receiverPolicy ?? getPolicy(invoiceReceiverPolicyID);
- const isCurrentUserReceiver = (isIndividual && invoiceReceiverAccountID === currentUserAccountID) || (!isIndividual && isPolicyAdminPolicyUtils(invoiceReceiverPolicy));
-
- if (isCurrentUserReceiver) {
- return getPolicyName({report, policies});
- }
-
- if (isIndividual) {
- return formatPhoneNumber(getDisplayNameOrDefault((personalDetails ?? allPersonalDetails)?.[invoiceReceiverAccountID]));
- }
-
- return getPolicyName({report, policy: invoiceReceiverPolicy, policies});
- }
+ // src/libs/ReportNameUtils.ts
+ function getInvoicesChatName({
+ report,
+ receiverPolicy,
+ personalDetails,
+ policies,
+ }: {
+ report: OnyxEntry<Report>;
+ receiverPolicy: OnyxEntry<Policy>;
+ personalDetails?: Partial<PersonalDetailsList>;
+ policies?: Policy[];
+ }): string {
+ const invoiceReceiver = report?.invoiceReceiver;
+ const isIndividual = invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL;
+ const invoiceReceiverAccountID = isIndividual ? invoiceReceiver.accountID : CONST.DEFAULT_NUMBER_ID;
+ const invoiceReceiverPolicyID = isIndividual ? undefined : invoiceReceiver?.policyID;
+ // This will be fixed as part of https://github.com/Expensify/Expensify/issues/507850
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
+ const receiverPolicyResolved = receiverPolicy ?? getPolicy(invoiceReceiverPolicyID);
+ const isCurrentUserReceiver = (isIndividual && invoiceReceiverAccountID === currentUserAccountID) || (!isIndividual && isPolicyAdmin(receiverPolicyResolved));
+
+ if (isCurrentUserReceiver) {
+ return getPolicyName({report, policies});
+ }
+
+ if (isIndividual) {
+ return formatPhoneNumber(getDisplayNameOrDefault((personalDetails ?? allPersonalDetails)?.[invoiceReceiverAccountID]));
+ }
+
+ return getPolicyName({report, policy: receiverPolicyResolved, policies});
+ }
////////////////////////////////////////////////////////////////////////////////
// getInvoiceReportName
////////////////////////////////////////////////////////////////////////////////
- // src/libs/ReportUtils.ts
+ // src/libs/ReportNameUtils.ts
function getInvoiceReportName(report: OnyxEntry<Report>, policy?: OnyxEntry<Policy>, invoiceReceiverPolicy?: OnyxEntry<Policy>): string {
const moneyRequestReportName = getMoneyRequestReportName({report, policy, invoiceReceiverPolicy});
const oldDotInvoiceName = report?.reportName ?? moneyRequestReportName;
return isNewDotInvoice(report?.chatReportID) ? moneyRequestReportName : oldDotInvoiceName;
}
////////////////////////////////////////////////////////////////////////////////
// getInvoicePayerName
////////////////////////////////////////////////////////////////////////////////
- // src/libs/ReportUtils.ts
- function getInvoicePayerName(report: OnyxEntry<Report>, invoiceReceiverPolicy?: OnyxEntry<Policy>, invoiceReceiverPersonalDetail?: PersonalDetails | null): string {
- const invoiceReceiver = report?.invoiceReceiver;
- const isIndividual = invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL;
-
- if (isIndividual) {
- return formatPhoneNumber(getDisplayNameOrDefault(invoiceReceiverPersonalDetail ?? allPersonalDetails?.[invoiceReceiver.accountID]));
- }
-
- return getPolicyName({report, policy: invoiceReceiverPolicy ?? allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${invoiceReceiver?.policyID}`]});
- }
+ // src/libs/ReportNameUtils.ts
+ function getInvoicePayerName(report: OnyxEntry<Report>, invoiceReceiverPolicy?: OnyxEntry<Policy>, invoiceReceiverPersonalDetail?: PersonalDetails | null): string {
+ const invoiceReceiver = report?.invoiceReceiver;
+ const isIndividual = invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL;
+
+ if (isIndividual) {
+ const personalDetail = invoiceReceiverPersonalDetail ?? allPersonalDetails?.[invoiceReceiver.accountID];
+ return formatPhoneNumber(getDisplayNameOrDefault(personalDetail ?? undefined));
+ }
+
+ let policyToUse = invoiceReceiverPolicy;
+ if (!policyToUse && invoiceReceiver?.policyID) {
+ policyToUse = getPolicy(invoiceReceiver.policyID);
+ }
+
+ return getPolicyName({report, policy: policyToUse});
+ }
////////////////////////////////////////////////////////////////////////////////
// getMoneyRequestReportName
////////////////////////////////////////////////////////////////////////////////
- // src/libs/ReportUtils.ts
+ // src/libs/ReportNameUtils.ts
function getMoneyRequestReportName({report, policy, invoiceReceiverPolicy}: {report: OnyxEntry<Report>; policy?: OnyxEntry<Policy>; invoiceReceiverPolicy?: OnyxEntry<Policy>}): string {
if (report?.reportName && isExpenseReport(report)) {
return report.reportName;
}
const moneyRequestTotal = getMoneyRequestSpendBreakdown(report).totalDisplaySpend;
const formattedAmount = convertToDisplayString(moneyRequestTotal, report?.currency);
let payerOrApproverName;
if (isExpenseReport(report)) {
const parentReport = getParentReport(report);
payerOrApproverName = getPolicyName({report: parentReport ?? report, policy});
} else if (isInvoiceReport(report)) {
const chatReport = getReportOrDraftReport(report?.chatReportID);
payerOrApproverName = getInvoicePayerName(chatReport, invoiceReceiverPolicy);
} else {
payerOrApproverName = getDisplayNameForParticipant({accountID: report?.managerID}) ?? '';
}
// eslint-disable-next-line @typescript-eslint/no-deprecated
const payerPaidAmountMessage = translateLocal('iou.payerPaidAmount', {
payer: payerOrApproverName,
amount: formattedAmount,
});
if (isReportApproved({report})) {
// eslint-disable-next-line @typescript-eslint/no-deprecated
return translateLocal('iou.managerApprovedAmount', {
manager: payerOrApproverName,
amount: formattedAmount,
});
}
if (report?.isWaitingOnBankAccount) {
// eslint-disable-next-line @typescript-eslint/no-deprecated
return `${payerPaidAmountMessage} ${CONST.DOT_SEPARATOR} ${translateLocal('iou.pending')}`;
}
if (!isSettled(report?.reportID) && hasNonReimbursableTransactions(report?.reportID)) {
payerOrApproverName = getDisplayNameForParticipant({accountID: report?.ownerAccountID}) ?? '';
// eslint-disable-next-line @typescript-eslint/no-deprecated
return translateLocal('iou.payerSpentAmount', {payer: payerOrApproverName, amount: formattedAmount});
}
if (isProcessingReport(report) || isOpenExpenseReport(report) || isOpenInvoiceReport(report) || moneyRequestTotal === 0) {
// eslint-disable-next-line @typescript-eslint/no-deprecated
return translateLocal('iou.payerOwesAmount', {payer: payerOrApproverName, amount: formattedAmount});
}
return payerPaidAmountMessage;
} |
neil-marcellini
left a comment
There was a problem hiding this comment.
I'm happy with this now, thanks! There are some lint errors to fix and I left some non-blocking comments.
Please get this ready for a final review and lmk if you need any help due to editing permissions.
src/libs/ReportNameUtils.ts
Outdated
|
|
||
| let policyToUse = invoiceReceiverPolicy; | ||
| if (!policyToUse && invoiceReceiver?.policyID) { | ||
| policyToUse = getPolicy(invoiceReceiver.policyID); |
There was a problem hiding this comment.
NAB: Is it possible to only rely on the param invoiceReceiverPolicy vs using the deprecated getPolicy or accessing from allPolicies?
If it's too hard to safely change that, we'll have to add an eslint ignore line for this and explain that we added this as part of moving the function, so it's not really adding a new use.
tests/unit/ReportNameUtilsTest.ts
Outdated
| }); | ||
|
|
||
| describe('computeReportName - Policy expense chat', () => { | ||
| test('Returns policy expense chat name for own PEC', async () => { |
There was a problem hiding this comment.
NAB: remove "PEC", it's unnecessary and we try to avoid more acronyms than we already have.
tests/unit/ReportNameUtilsTest.ts
Outdated
| }); | ||
| }); | ||
|
|
||
| describe('computeReportName - reportNameValuePairsList archiving', () => { |
There was a problem hiding this comment.
NAB: remove "List" suffix
neil-marcellini
left a comment
There was a problem hiding this comment.
Good to go but pls add one test. Let's just open a new PR so you can edit it yourself, and I'll approve it right away. We can link to it here and close this one.
|
Closing in favor of this one so that the real author can easily edit it. |
Details
Fixed Issues
$ #72613
PROPOSAL: N/A
Tests
N/A
Offline tests
N/A
QA Steps
Same as tests
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectiontoggleReportand notonIconClick)myBool && <MyComponent />.src/languages/*files and using the translation methodWaiting for Copylabel for a copy review on the original GH to get the correct copy.STYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)/** comment above it */thisproperly so there are no scoping issues (i.e. foronClick={this.submit}the methodthis.submitshould be bound tothisin the constructor)thisare necessary to be bound (i.e. avoidthis.submit = this.submit.bind(this);ifthis.submitis never passed to a component event handler likeonClick)StyleUtils.getBackgroundAndBorderStyle(themeColors.componentBG))Avataris modified, I verified thatAvataris working as expected in all cases)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
Details