Skip to content

perf: use requiresAttentionFromCurrentUser from a derived value #61586

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

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
8 changes: 3 additions & 5 deletions src/components/LHNOptionsList/LHNOptionsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import getPlatform from '@libs/getPlatform';
import Log from '@libs/Log';
import {getIOUReportIDOfLastAction, getLastMessageTextForReport, hasReportErrors} from '@libs/OptionsListUtils';
import {getOneTransactionThreadReportID, getOriginalMessage, getSortedReportActionsForDisplay, isMoneyRequestAction} from '@libs/ReportActionsUtils';
import {canUserPerformWriteAction, requiresAttentionFromCurrentUser} from '@libs/ReportUtils';
import {canUserPerformWriteAction, getReportAttributes} from '@libs/ReportUtils';
import isProductTrainingElementDismissed from '@libs/TooltipUtils';
import variables from '@styles/variables';
import CONST from '@src/CONST';
Expand Down Expand Up @@ -76,14 +76,12 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio
if (hasReportErrors(report, itemReportActions)) {
return true;
}
const itemParentReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report?.parentReportID}`];
const itemParentReportAction = report?.parentReportActionID ? itemParentReportActions?.[report?.parentReportActionID] : undefined;
const hasGBR = requiresAttentionFromCurrentUser(report, itemParentReportAction);
const hasGBR = getReportAttributes(report.reportID, reportAttributes)?.requiresAttention;
return hasGBR;
});

return firstReportWithGBRorRBR?.reportID;
}, [isGBRorRBRTooltipDismissed, data, reportActions]);
}, [isGBRorRBRTooltipDismissed, data, reportActions, reportAttributes]);

// When the first item renders we want to call the onFirstItemRendered callback.
// At this point in time we know that the list is actually displaying items.
Expand Down
2 changes: 1 addition & 1 deletion src/components/Navigation/NavigationTabBar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ function NavigationTabBar({selectedTab, isTooltipAllowed = false, isTopLevelBar
const {activeWorkspaceID} = useActiveWorkspace();
const {orderedReports} = useSidebarOrderedReports();
const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: false});
const [reportAttributes] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {canBeMissing: true});
const [reportAttributes] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {selector: (value) => value?.reports, canBeMissing: true});
const {shouldUseNarrowLayout} = useResponsiveLayout();
const [chatTabBrickRoad, setChatTabBrickRoad] = useState<BrickRoad>(undefined);
const platform = getPlatform();
Expand Down
18 changes: 10 additions & 8 deletions src/hooks/useSidebarOrderedReports.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,15 @@ function SidebarOrderedReportsContextProvider({
*/
currentReportIDForTests,
}: SidebarOrderedReportsContextProviderProps) {
const [priorityMode] = useOnyx(ONYXKEYS.NVP_PRIORITY_MODE, {initialValue: CONST.PRIORITY_MODE.DEFAULT});
const [chatReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT);
const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {selector: (c) => mapOnyxCollectionItems(c, policySelector)});
const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS);
const [reportNameValuePairs] = useOnyx(ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS);
const [reportsDrafts] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, {initialValue: {}});
const [priorityMode] = useOnyx(ONYXKEYS.NVP_PRIORITY_MODE, {initialValue: CONST.PRIORITY_MODE.DEFAULT, canBeMissing: true});
const [chatReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: true});
const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {selector: (c) => mapOnyxCollectionItems(c, policySelector), canBeMissing: true});
const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {canBeMissing: true});
const [reportNameValuePairs] = useOnyx(ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS, {canBeMissing: true});
const [reportsDrafts] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, {initialValue: {}, canBeMissing: true});
const draftAmount = Object.keys(reportsDrafts ?? {}).length;
const [betas] = useOnyx(ONYXKEYS.BETAS);
const [betas] = useOnyx(ONYXKEYS.BETAS, {canBeMissing: true});
const [reportAttributes] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {selector: (value) => value?.reports, canBeMissing: true});

const {shouldUseNarrowLayout} = useResponsiveLayout();
const {accountID} = useCurrentUserPersonalDetails();
Expand All @@ -80,10 +81,11 @@ function SidebarOrderedReportsContextProvider({
activeWorkspaceID,
policyMemberAccountIDs,
reportNameValuePairs,
reportAttributes,
),
// we need reports draft in deps array to reload the list when a draft is added or removed
// eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps
[chatReports, betas, policies, priorityMode, transactionViolations, activeWorkspaceID, policyMemberAccountIDs, draftAmount, reportNameValuePairs],
[chatReports, betas, policies, priorityMode, transactionViolations, activeWorkspaceID, policyMemberAccountIDs, draftAmount, reportNameValuePairs, reportAttributes],
);

const orderedReportIDs = useMemo(() => getOrderedReportIDs(), [getOrderedReportIDs]);
Expand Down
19 changes: 14 additions & 5 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -707,7 +707,6 @@ type OptionData = {
alternateText?: string;
allReportErrors?: Errors;
brickRoadIndicator?: ValueOf<typeof CONST.BRICK_ROAD_INDICATOR_STATUS> | '' | null;
shouldShowGreenDot?: boolean;
tooltipText?: string | null;
alternateTextMaxLines?: number;
boldStyle?: boolean;
Expand Down Expand Up @@ -1059,14 +1058,14 @@ Onyx.connect({
callback: (value) => (activePolicyID = value),
});

let reportAttributes: ReportAttributesDerivedValue['reports'];
let reportAttributesDerivedValue: ReportAttributesDerivedValue['reports'];
Onyx.connect({
key: ONYXKEYS.DERIVED.REPORT_ATTRIBUTES,
callback: (value) => {
if (!value) {
return;
}
reportAttributes = value.reports;
reportAttributesDerivedValue = value.reports;
},
});
Comment on lines -1062 to 1070
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe this is an antipattern now and we should always subscribe to the attributes in the component using useOnyx and pass them to the utils method - this should ensure the component is always re-rendered when the attribute changes and that the utils method remains pure.

@TMisiukiewicz @kacper-mikolajczak what do you think? Could you look into this to refactor its usages?

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 agree that it should be passed to the util methods from useOnyx. The only caveat is that in some cases, those values might be used deep within the function logic, not just as top-level arguments. Refactoring might result in passing them through multiple layers of intermediate calls.

I'll verify if it'll introduce the additional complexity


Expand Down Expand Up @@ -4647,11 +4646,11 @@ function getReportName(
parentReportActionParam?: OnyxInputOrEntry<ReportAction>,
personalDetails?: Partial<PersonalDetailsList>,
invoiceReceiverPolicy?: OnyxEntry<Policy>,
reportAttributesParam?: ReportAttributesDerivedValue['reports'],
reportAttributes?: ReportAttributesDerivedValue['reports'],
): string {
// Check if we can use report name in derived values - only when we have report but no other params
const canUseDerivedValue = report && policy === undefined && parentReportActionParam === undefined && personalDetails === undefined && invoiceReceiverPolicy === undefined;
const attributes = reportAttributesParam ?? reportAttributes;
const attributes = reportAttributes ?? reportAttributesDerivedValue;
const derivedNameExists = report && !!attributes?.[report.reportID]?.reportName;
if (canUseDerivedValue && derivedNameExists) {
return attributes[report.reportID].reportName;
Expand Down Expand Up @@ -10754,6 +10753,15 @@ function getReportPersonalDetailsParticipants(report: Report, personalDetailsPar
};
}

function getReportAttributes(reportID: string | undefined, reportAttributes?: ReportAttributesDerivedValue['reports']) {
const attributes = reportAttributes ?? reportAttributesDerivedValue;

if (!reportID || !attributes?.[reportID]) {
return;
}
return attributes[reportID];
}

Comment on lines +10756 to +10764
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 dont want to create these getters in general and always use the attributes fetched from onyx in the component

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Addressed this comment here: #62464

export {
addDomainToShortMention,
completeShortMention,
Expand Down Expand Up @@ -11130,6 +11138,7 @@ export {
getReportPersonalDetailsParticipants,
isAllowedToSubmitDraftExpenseReport,
isWorkspaceEligibleForReportChange,
getReportAttributes,
};

export type {
Expand Down
12 changes: 5 additions & 7 deletions src/libs/SidebarUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type {ValueOf} from 'type-fest';
import type {PartialPolicyForSidebar} from '@hooks/useSidebarOrderedReports';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {PersonalDetails, PersonalDetailsList, ReportActions, ReportNameValuePairs, TransactionViolation} from '@src/types/onyx';
import type {PersonalDetails, PersonalDetailsList, ReportActions, ReportAttributesDerivedValue, ReportNameValuePairs, TransactionViolation} from '@src/types/onyx';
import type Beta from '@src/types/onyx/Beta';
import type {ReportAttributes} from '@src/types/onyx/DerivedValues';
import type Policy from '@src/types/onyx/Policy';
Expand Down Expand Up @@ -76,6 +76,7 @@ import {
getIcons,
getParticipantsAccountIDsForDisplay,
getPolicyName,
getReportAttributes,
getReportDescription,
getReportName,
getReportNameValuePairs,
Expand Down Expand Up @@ -111,7 +112,6 @@ import {
isThread,
isUnread,
isUnreadWithMention,
requiresAttentionFromCurrentUser,
shouldDisplayViolationsRBRInLHN,
shouldReportBeInOptionList,
shouldReportShowSubscript,
Expand Down Expand Up @@ -202,6 +202,7 @@ function getOrderedReportIDs(
currentPolicyID = '',
policyMemberAccountIDs: number[] = [],
reportNameValuePairs?: OnyxCollection<ReportNameValuePairs>,
reportAttributes?: ReportAttributesDerivedValue['reports'],
): string[] {
Performance.markStart(CONST.TIMING.GET_ORDERED_REPORT_IDS);
const isInFocusMode = priorityMode === CONST.PRIORITY_MODE.GSD;
Expand Down Expand Up @@ -244,7 +245,7 @@ function getOrderedReportIDs(
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
report.isPinned ||
(!isInFocusMode && isArchivedReport(reportNameValuePairs)) ||
requiresAttentionFromCurrentUser(report, parentReportAction);
getReportAttributes(report.reportID, reportAttributes)?.requiresAttention;
if (isHidden && !shouldOverrideHidden) {
return;
}
Expand Down Expand Up @@ -292,9 +293,8 @@ function getOrderedReportIDs(
};

const isPinned = report?.isPinned ?? false;
const reportAction = getReportAction(report?.parentReportID, report?.parentReportActionID);
const rNVPs = reportNameValuePairs?.[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID}`];
if (isPinned || requiresAttentionFromCurrentUser(report, reportAction)) {
if (isPinned || getReportAttributes(report?.reportID, reportAttributes)?.requiresAttention) {
pinnedAndGBRReports.push(miniReport);
} else if (report?.hasErrorsOtherThanFailedReceipt) {
errorReports.push(miniReport);
Expand Down Expand Up @@ -473,7 +473,6 @@ function getOptionData({
isAllowedToComment: true,
isDeletedParentAction: false,
isConciergeChat: false,
shouldShowGreenDot: false,
};

const participantAccountIDs = getParticipantsAccountIDsForDisplay(report);
Expand Down Expand Up @@ -520,7 +519,6 @@ function getOptionData({
result.hasParentAccess = report.hasParentAccess;
result.isConciergeChat = isConciergeChatReport(report);
result.participants = report.participants;
result.shouldShowGreenDot = result.brickRoadIndicator !== CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR && requiresAttentionFromCurrentUser(report, parentReportAction);

const isExpense = isExpenseReport(report);
const hasMultipleParticipants = participantPersonalDetailList.length > 1 || result.isChatRoom || result.isPolicyExpenseChat || isExpense;
Expand Down
7 changes: 4 additions & 3 deletions src/libs/actions/OnyxDerived/configs/reportAttributes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,13 @@ export default createOnyxDerivedValueConfig({
dataToIterate = prepareReportKeys(updates);
recentlyUpdated = updates;
} else if (!!transactionsUpdates || !!transactionViolationsUpdates) {
let transactionReportIds: string[] = [];
let transactionReportIDs: string[] = [];
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Addressing this NAB comment from previous PR #61376 (comment) here cc @mountiny

if (transactionsUpdates) {
transactionReportIds = Object.values(transactionsUpdates).map((transaction) => `${ONYXKEYS.COLLECTION.REPORT}${transaction?.reportID}`);
transactionReportIDs = Object.values(transactionsUpdates).map((transaction) => `${ONYXKEYS.COLLECTION.REPORT}${transaction?.reportID}`);
}
// if transactions are updated, they might not be directly related to the reports yet (e.g. transaction is optimistically created)
// so we use report keys that were updated before to recompute the reports
const recentReportKeys = prepareReportKeys([...recentlyUpdated, ...transactionReportIds]);
const recentReportKeys = prepareReportKeys([...recentlyUpdated, ...transactionReportIDs]);
dataToIterate = recentReportKeys;
}
}
Expand Down Expand Up @@ -112,6 +112,7 @@ export default createOnyxDerivedValueConfig({
acc[report.reportID] = {
reportName: generateReportName(report),
brickRoadStatus,
requiresAttention,
};

return acc;
Expand Down
4 changes: 4 additions & 0 deletions src/types/onyx/DerivedValues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ type ReportAttributes = {
* The status of the brick road.
*/
brickRoadStatus: ValueOf<typeof CONST.BRICK_ROAD_INDICATOR_STATUS> | undefined;
/**
* Whether the report requires attention from current user.
*/
requiresAttention: boolean;
};

/**
Expand Down
8 changes: 5 additions & 3 deletions tests/unit/SidebarOrderTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Onyx from 'react-native-onyx';
import {addComment} from '@libs/actions/Report';
import DateUtils from '@libs/DateUtils';
import {translateLocal} from '@libs/Localize';
import initOnyxDerivedValues from '@userActions/OnyxDerived';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type * as OnyxTypes from '@src/types/onyx';
Expand All @@ -29,12 +30,13 @@ jest.mock('@react-navigation/native', () => ({
jest.mock('@components/ConfirmedRoute.tsx');

describe('Sidebar', () => {
beforeAll(() =>
beforeAll(() => {
Onyx.init({
keys: ONYXKEYS,
evictableKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS],
}),
);
});
initOnyxDerivedValues();
});

beforeEach(() => {
// Wrap Onyx each onyx action with waitForBatchedUpdates
Expand Down
Loading