Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[HOLD] Feat: Update how GBR is determined for IOU/expense reports #29778

Merged
merged 15 commits into from
Oct 31, 2023
Merged
3 changes: 1 addition & 2 deletions src/components/LHNOptionsList/OptionRowLHN.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,7 @@ function OptionRowLHN(props) {

const hasBrickError = optionItem.brickRoadIndicator === CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR;
const defaultSubscriptSize = optionItem.isExpenseRequest ? CONST.AVATAR_SIZE.SMALL_NORMAL : CONST.AVATAR_SIZE.DEFAULT;
const shouldShowGreenDotIndicator =
!hasBrickError && (optionItem.isUnreadWithMention || optionItem.isWaitingForTaskCompleteFromAssignee || ReportUtils.isWaitingForIOUActionFromCurrentUser(optionItem));
const shouldShowGreenDotIndicator = !hasBrickError && ReportUtils.shouldShowGBR(optionItem);

/**
* Show the ReportActionContextMenu modal popover.
Expand Down
31 changes: 11 additions & 20 deletions src/libs/ReportUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -1324,12 +1324,15 @@ function getLastVisibleMessage(reportID, actionsToMerge = {}) {
}

/**
* Determines if a report has an IOU that is waiting for an action from the current user (either Pay or Add a credit bank account)
* Determines if a report should show a GBR (green dot) in the LHN. This can happen when the report:
- is unread and the user was mentioned in one of the unread comments
- is for an outstanding task waiting on the user
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't see the task case covered in the function. Shouldn't it be?

- has an outstanding child money request that is waiting for an action from the current user (e.g. pay, approve, add bank account)
*
* @param {Object} report (chatReport or iouReport)
* @returns {boolean}
*/
function isWaitingForIOUActionFromCurrentUser(report) {
function shouldShowGBR(report) {
if (!report) {
return false;
}
Expand All @@ -1338,20 +1341,8 @@ function isWaitingForIOUActionFromCurrentUser(report) {
return false;
}

const policy = getPolicy(report.policyID);
if (policy.type === CONST.POLICY.TYPE.CORPORATE) {
// If the report is already settled, there's no action required from any user.
if (isSettled(report.reportID)) {
return false;
}

// Report is pending approval and the current user is the manager
if (isReportManager(report) && !isReportApproved(report)) {
return true;
}

// Current user is an admin and the report has been approved but not settled yet
return policy.role === CONST.POLICY.ROLE.ADMIN && isReportApproved(report);
if (report.isUnreadWithMention) {
return true;
}

// Money request waiting for current user to add their credit bank account
Copy link
Contributor

Choose a reason for hiding this comment

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

@waterim actually, we've decided to treat this condition like the others. If a report is waiting on bank account, we will add the hasOutstandingChildRequest to the parent for the submitter and show the green dot on the parent. So let's remove this condition.

Copy link
Contributor

Choose a reason for hiding this comment

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

@waterim I think we should remove this whole block:

    // Money request waiting for current user to add their credit bank account
    // hasOutstandingIOU will be false if the user paid, but isWaitingOnBankAccount will be true if user don't have a wallet or bank account setup
    if (!report.hasOutstandingIOU && report.isWaitingOnBankAccount && report.ownerAccountID === currentUserAccountID) {
        return true;
    }

Expand All @@ -1360,8 +1351,8 @@ function isWaitingForIOUActionFromCurrentUser(report) {
return true;
}

// Money request waiting for current user to Pay (from expense or iou report)
if (report.hasOutstandingIOU && report.ownerAccountID && (report.ownerAccountID !== currentUserAccountID || currentUserAccountID === report.managerID)) {
// Has a child report that is awaiting action (e.g. approve, pay, add bank account) from current user
if (report.hasOutstandingChildRequest) {
return true;
}

Expand Down Expand Up @@ -3297,7 +3288,7 @@ function shouldReportBeInOptionList(report, currentReportId, isInGSDMode, betas,
}

// Include reports that are relevant to the user in any view mode. Criteria include having a draft, having an outstanding IOU, or being assigned to an open task.
if (report.hasDraft || isWaitingForIOUActionFromCurrentUser(report) || isWaitingForTaskCompleteFromAssignee(report)) {
if (report.hasDraft || shouldShowGBR(report) || isWaitingForTaskCompleteFromAssignee(report)) {
waterim marked this conversation as resolved.
Show resolved Hide resolved
return true;
}
const lastVisibleMessage = ReportActionsUtils.getLastVisibleMessage(report.reportID);
Expand Down Expand Up @@ -4155,7 +4146,7 @@ export {
isCurrentUserTheOnlyParticipant,
hasAutomatedExpensifyAccountIDs,
hasExpensifyGuidesEmails,
isWaitingForIOUActionFromCurrentUser,
shouldShowGBR,
isIOUOwnedByCurrentUser,
getMoneyRequestReimbursableTotal,
getMoneyRequestSpendBreakdown,
Expand Down
16 changes: 5 additions & 11 deletions src/libs/SidebarUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,6 @@ function getOrderedReportIDs(

// The LHN is split into five distinct groups, and each group is sorted a little differently. The groups will ALWAYS be in this order:
// 1. Pinned - Always sorted by reportDisplayName
// 2. Outstanding IOUs - Always sorted by iouReportAmount with the largest amounts at the top of the group
// 3. Drafts - Always sorted by reportDisplayName
// 4. Non-archived reports and settled IOUs
// - Sorted by lastVisibleActionCreated in default (most recent) view mode
Expand All @@ -179,15 +178,12 @@ function getOrderedReportIDs(
// - Sorted by lastVisibleActionCreated in default (most recent) view mode
// - Sorted by reportDisplayName in GSD (focus) view mode
const pinnedReports: Report[] = [];
const outstandingIOUReports: Report[] = [];
const draftReports: Report[] = [];
const nonArchivedReports: Report[] = [];
const archivedReports: Report[] = [];
reportsToDisplay.forEach((report) => {
if (report.isPinned) {
if (report.isPinned ?? ReportUtils.shouldShowGBR(report)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why ?? instead of ||?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

New lint rules

pinnedReports.push(report);
} else if (ReportUtils.isWaitingForIOUActionFromCurrentUser(report)) {
outstandingIOUReports.push(report);
} else if (report.hasDraft) {
draftReports.push(report);
} else if (ReportUtils.isArchivedRoom(report)) {
Expand All @@ -199,11 +195,6 @@ function getOrderedReportIDs(

// Sort each group of reports accordingly
pinnedReports.sort((a, b) => (a?.displayName && b?.displayName ? a.displayName.toLowerCase().localeCompare(b.displayName.toLowerCase()) : 0));
outstandingIOUReports.sort((a, b) => {
const compareAmounts = a?.iouReportAmount && b?.iouReportAmount ? b.iouReportAmount - a.iouReportAmount : 0;
const compareDisplayNames = a?.displayName && b?.displayName ? a.displayName.toLowerCase().localeCompare(b.displayName.toLowerCase()) : 0;
return compareAmounts || compareDisplayNames;
});
draftReports.sort((a, b) => (a?.displayName && b?.displayName ? a.displayName.toLowerCase().localeCompare(b.displayName.toLowerCase()) : 0));

if (isInDefaultMode) {
Expand All @@ -221,7 +212,7 @@ function getOrderedReportIDs(

// Now that we have all the reports grouped and sorted, they must be flattened into an array and only return the reportID.
// The order the arrays are concatenated in matters and will determine the order that the groups are displayed in the sidebar.
const LHNReports = [...pinnedReports, ...outstandingIOUReports, ...draftReports, ...nonArchivedReports, ...archivedReports].map((report) => report.reportID);
const LHNReports = [...pinnedReports, ...draftReports, ...nonArchivedReports, ...archivedReports].map((report) => report.reportID);
setWithLimit(reportIDsCache, cachedReportsKey, LHNReports);
return LHNReports;
}
Expand Down Expand Up @@ -254,6 +245,7 @@ type OptionData = {
searchText?: string | null;
isPinned?: boolean | null;
hasOutstandingIOU?: boolean | null;
hasOutstandingChildRequest?: boolean | null;
iouReportID?: string | null;
isIOUReportOwner?: boolean | null;
iouReportAmount?: number | null;
Expand Down Expand Up @@ -338,6 +330,7 @@ function getOptionData(
searchText: null,
isPinned: false,
hasOutstandingIOU: false,
hasOutstandingChildRequest: false,
iouReportID: null,
isIOUReportOwner: null,
iouReportAmount: 0,
Expand Down Expand Up @@ -382,6 +375,7 @@ function getOptionData(
result.keyForList = String(report.reportID);
result.tooltipText = ReportUtils.getReportParticipantsTitle(report.participantAccountIDs ?? []);
result.hasOutstandingIOU = report.hasOutstandingIOU;
result.hasOutstandingChildRequest = report.hasOutstandingChildRequest;
result.parentReportID = report.parentReportID ?? null;
result.isWaitingOnBankAccount = report.isWaitingOnBankAccount;
result.notificationPreference = report.notificationPreference ?? null;
Expand Down
1 change: 1 addition & 0 deletions src/pages/home/sidebar/SidebarLinksData.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ const chatReportSelector = (report) =>
total: report.total,
nonReimbursableTotal: report.nonReimbursableTotal,
hasOutstandingIOU: report.hasOutstandingIOU,
hasOutstandingChildRequest: report.hasOutstandingChildRequest,
isWaitingOnBankAccount: report.isWaitingOnBankAccount,
statusNum: report.statusNum,
stateNum: report.stateNum,
Expand Down
3 changes: 3 additions & 0 deletions src/types/onyx/Report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ type Report = {
/** Whether there is an outstanding amount in IOU */
hasOutstandingIOU?: boolean;

/** Whether the report has a child that is an outstanding money request that is awaiting action from the current user */
hasOutstandingChildRequest?: boolean;

/** List of icons for report participants */
icons?: OnyxCommon.Icon[];

Expand Down
19 changes: 10 additions & 9 deletions tests/unit/ReportUtilsTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -246,17 +246,17 @@ describe('ReportUtils', () => {
});
});

describe('isWaitingForIOUActionFromCurrentUser', () => {
describe('shouldShowGBR', () => {
it('returns false when there is no report', () => {
expect(ReportUtils.isWaitingForIOUActionFromCurrentUser()).toBe(false);
expect(ReportUtils.shouldShowGBR()).toBe(false);
});
it('returns false when the matched IOU report does not have an owner accountID', () => {
const report = {
...LHNTestUtils.getFakeReport(),
ownerAccountID: undefined,
hasOutstandingIOU: true,
};
expect(ReportUtils.isWaitingForIOUActionFromCurrentUser(report)).toBe(false);
expect(ReportUtils.shouldShowGBR(report)).toBe(false);
});
it('returns false when the linked iou report has an oustanding IOU', () => {
const report = {
Expand All @@ -268,7 +268,7 @@ describe('ReportUtils', () => {
ownerAccountID: 99,
hasOutstandingIOU: true,
}).then(() => {
expect(ReportUtils.isWaitingForIOUActionFromCurrentUser(report)).toBe(false);
expect(ReportUtils.shouldShowGBR(report)).toBe(false);
});
});
it('returns true when the report has no outstanding IOU but is waiting for a bank account and the logged user is the report owner', () => {
Expand All @@ -278,7 +278,7 @@ describe('ReportUtils', () => {
ownerAccountID: currentUserAccountID,
isWaitingOnBankAccount: true,
};
expect(ReportUtils.isWaitingForIOUActionFromCurrentUser(report)).toBe(true);
expect(ReportUtils.shouldShowGBR(report)).toBe(true);
});
it('returns false when the report has outstanding IOU and is not waiting for a bank account and the logged user is the report owner', () => {
const report = {
Expand All @@ -287,7 +287,7 @@ describe('ReportUtils', () => {
ownerAccountID: currentUserAccountID,
isWaitingOnBankAccount: false,
};
expect(ReportUtils.isWaitingForIOUActionFromCurrentUser(report)).toBe(false);
expect(ReportUtils.shouldShowGBR(report)).toBe(false);
});
it('returns false when the report has no oustanding IOU but is waiting for a bank account and the logged user is not the report owner', () => {
const report = {
Expand All @@ -296,16 +296,17 @@ describe('ReportUtils', () => {
ownerAccountID: 97,
isWaitingOnBankAccount: true,
};
expect(ReportUtils.isWaitingForIOUActionFromCurrentUser(report)).toBe(false);
expect(ReportUtils.shouldShowGBR(report)).toBe(false);
});
it('returns true when the report has oustanding IOU', () => {
it('returns true when the report has oustanding child request', () => {
const report = {
...LHNTestUtils.getFakeReport(),
ownerAccountID: 99,
hasOutstandingIOU: true,
hasOutstandingChildRequest: true,
isWaitingOnBankAccount: false,
};
expect(ReportUtils.isWaitingForIOUActionFromCurrentUser(report)).toBe(true);
expect(ReportUtils.shouldShowGBR(report)).toBe(true);
});
});

Expand Down
17 changes: 11 additions & 6 deletions tests/unit/SidebarOrderTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ describe('Sidebar', () => {
};
const report3 = {
...LHNTestUtils.getFakeReport([5, 6], 1),
hasOutstandingIOU: false,
hasOutstandingChildRequest: false,

// This has to be added after the IOU report is generated
iouReportID: null,
Expand All @@ -396,6 +396,7 @@ describe('Sidebar', () => {
ownerAccountID: 2,
managerID: 2,
hasOutstandingIOU: true,
hasOutstandingChildRequest: true,
total: 10000,
currency: 'USD',
chatReportID: report3.reportID,
Expand All @@ -404,7 +405,6 @@ describe('Sidebar', () => {
const currentReportId = report2.reportID;
const currentlyLoggedInUserAccountID = 9;
LHNTestUtils.getDefaultRenderedSidebarLinks(currentReportId);

return (
waitForBatchedUpdates()
// When Onyx is updated with the data and the sidebar re-renders
Expand All @@ -430,8 +430,8 @@ describe('Sidebar', () => {
expect(displayNames).toHaveLength(3);
expect(screen.queryAllByTestId('Pin Icon')).toHaveLength(1);
expect(screen.queryAllByTestId('Pencil Icon')).toHaveLength(1);
expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('One, Two');
expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('Email Two owes $100.00');
expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Email Two owes $100.00');
expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('One, Two');
expect(lodashGet(displayNames, [2, 'props', 'children'])).toBe('Three, Four');
})
);
Expand Down Expand Up @@ -734,6 +734,7 @@ describe('Sidebar', () => {
ownerAccountID: 2,
managerID: 2,
hasOutstandingIOU: true,
hasOutstandingChildRequest: true,
total: 10000,
currency: 'USD',
chatReportID: report3.reportID,
Expand All @@ -744,6 +745,7 @@ describe('Sidebar', () => {
ownerAccountID: 2,
managerID: 3,
hasOutstandingIOU: true,
hasOutstandingChildRequest: true,
total: 10000,
currency: 'USD',
chatReportID: report3.reportID,
Expand All @@ -754,6 +756,7 @@ describe('Sidebar', () => {
ownerAccountID: 2,
managerID: 4,
hasOutstandingIOU: true,
hasOutstandingChildRequest: true,
total: 100000,
currency: 'USD',
chatReportID: report3.reportID,
Expand All @@ -764,6 +767,7 @@ describe('Sidebar', () => {
ownerAccountID: 2,
managerID: 5,
hasOutstandingIOU: true,
hasOutstandingChildRequest: true,
total: 10000,
currency: 'USD',
chatReportID: report3.reportID,
Expand All @@ -774,6 +778,7 @@ describe('Sidebar', () => {
ownerAccountID: 2,
managerID: 6,
hasOutstandingIOU: true,
hasOutstandingChildRequest: true,
total: 10000,
currency: 'USD',
chatReportID: report3.reportID,
Expand Down Expand Up @@ -814,8 +819,8 @@ describe('Sidebar', () => {
const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
const displayNames = screen.queryAllByLabelText(hintText);
expect(displayNames).toHaveLength(5);
expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Email Four owes $1,000.00');
expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('Email Five owes $100.00');
expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Email Five owes $100.00');
expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('Email Four owes $1,000.00');
expect(lodashGet(displayNames, [2, 'props', 'children'])).toBe('Email Six owes $100.00');
expect(lodashGet(displayNames, [3, 'props', 'children'])).toBe('Email Three owes $100.00');
expect(lodashGet(displayNames, [4, 'props', 'children'])).toBe('Email Two owes $100.00');
Expand Down
Loading